├── .gitignore ├── Extra Materials ├── Clean Code │ └── Clean-Code.pdf └── Git │ └── guideToGit.pdf ├── README.md ├── lab-project └── inventorysystem │ ├── .gitignore │ ├── .idea │ ├── .gitignore │ ├── compiler.xml │ ├── encodings.xml │ ├── jarRepositories.xml │ ├── misc.xml │ ├── runConfigurations.xml │ ├── uiDesigner.xml │ └── vcs.xml │ ├── .vscode │ ├── launch.json │ └── settings.json │ ├── HELP.md │ ├── README.md │ ├── log.txt │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ ├── postman_collection.json │ ├── src │ ├── main │ │ ├── java │ │ │ └── bg │ │ │ │ └── fmi │ │ │ │ └── uni │ │ │ │ └── inventorysystem │ │ │ │ ├── InventorysystemApplication.java │ │ │ │ ├── config │ │ │ │ ├── AppConfig.java │ │ │ │ └── logger │ │ │ │ │ ├── Logger.java │ │ │ │ │ ├── LoggerFileScenarioImpl.java │ │ │ │ │ └── LoggerStdoutImpl.java │ │ │ │ ├── controller │ │ │ │ ├── ClubMemberController.java │ │ │ │ ├── DummyController.java │ │ │ │ ├── InventoryItemController.java │ │ │ │ └── TransactionController.java │ │ │ │ ├── dto │ │ │ │ ├── APIErrorDto.java │ │ │ │ ├── ClubMemberDto.java │ │ │ │ ├── InventoryItemDto.java │ │ │ │ ├── InventoryItemPatchRequest.java │ │ │ │ ├── InventoryItemRequest.java │ │ │ │ └── TransactionDto.java │ │ │ │ ├── exception │ │ │ │ ├── GlobalExceptionHandler.java │ │ │ │ └── ItemNotFoundException.java │ │ │ │ ├── model │ │ │ │ ├── ClubMember.java │ │ │ │ ├── InventoryItem.java │ │ │ │ └── Transaction.java │ │ │ │ ├── repository │ │ │ │ ├── ClubMemberRepository.java │ │ │ │ ├── InventoryItemRepository.java │ │ │ │ ├── TransactionRepository.java │ │ │ │ └── sequence │ │ │ │ │ └── InventoryItemSequence.java │ │ │ │ ├── service │ │ │ │ ├── ClubMemberService.java │ │ │ │ ├── InventoryItemService.java │ │ │ │ └── TransactionService.java │ │ │ │ └── vo │ │ │ │ ├── Category.java │ │ │ │ ├── LoggerLevel.java │ │ │ │ └── UnitOfMeasurement.java │ │ └── resources │ │ │ ├── application-dev.properties │ │ │ ├── application-local.properties │ │ │ ├── application.properties │ │ │ └── db │ │ │ └── migration │ │ │ ├── V1__init.sql │ │ │ └── V2__seed_data.sql │ └── test │ │ └── java │ │ └── bg │ │ └── fmi │ │ └── uni │ │ └── inventorysystem │ │ └── InventorysystemApplicationTests.java │ └── target │ ├── classes │ ├── application-dev.properties │ ├── application-local.properties │ ├── application.properties │ └── bg │ │ └── fmi │ │ └── uni │ │ └── inventorysystem │ │ ├── controller │ │ ├── ClubMemberController.class │ │ └── TransactionController$TransactionRequest.class │ │ ├── dto │ │ ├── APIErrorDto.class │ │ ├── ClubMemberDto.class │ │ └── TransactionDto.class │ │ ├── service │ │ └── ClubMemberService.class │ │ └── vo │ │ ├── Category.class │ │ └── UnitOfMeasurement.class │ ├── maven-status │ └── maven-compiler-plugin │ │ ├── compile │ │ └── default-compile │ │ │ ├── createdFiles.lst │ │ │ └── inputFiles.lst │ │ └── testCompile │ │ └── default-testCompile │ │ ├── createdFiles.lst │ │ └── inputFiles.lst │ └── surefire-reports │ ├── TEST-bg.fmi.uni.inventorysystem.InventorysystemApplicationTests.xml │ └── bg.fmi.uni.inventorysystem.InventorysystemApplicationTests.txt ├── week01 ├── class-diagram.png └── tasks.md ├── week02 ├── README.md └── additionalTasks.md ├── week03 ├── tasks.md └── uni-week03 │ ├── .gitignore │ ├── .idea │ ├── .gitignore │ ├── misc.xml │ ├── modules.xml │ └── sonarlint.xml │ ├── src │ ├── Main.java │ ├── controller │ │ └── InventoryController.java │ ├── model │ │ ├── ClubMember.java │ │ ├── InventoryItem.java │ │ └── Transaction.java │ ├── repository │ │ ├── ClubMemberRepository.java │ │ ├── InventoryItemRepository.java │ │ └── TransactionRepository.java │ └── service │ │ └── InventoryService.java │ └── uni-week03.iml ├── week04 ├── basic-guide.md ├── inventorysystem │ ├── HELP.md │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ ├── src │ │ ├── main │ │ │ ├── java │ │ │ │ └── bg │ │ │ │ │ └── fmi │ │ │ │ │ └── uni │ │ │ │ │ └── inventorysystem │ │ │ │ │ ├── InventorysystemApplication.java │ │ │ │ │ ├── controller │ │ │ │ │ └── InventoryController.java │ │ │ │ │ ├── model │ │ │ │ │ ├── ClubMember.java │ │ │ │ │ ├── InventoryItem.java │ │ │ │ │ └── Transaction.java │ │ │ │ │ ├── repository │ │ │ │ │ ├── ClubMemberRepository.java │ │ │ │ │ ├── InventoryItemRepository.java │ │ │ │ │ └── TransactionRepository.java │ │ │ │ │ └── service │ │ │ │ │ └── InventoryService.java │ │ │ └── resources │ │ │ │ └── application.properties │ │ └── test │ │ │ └── java │ │ │ └── bg │ │ │ └── fmi │ │ │ └── uni │ │ │ └── inventorysystem │ │ │ └── InventorysystemApplicationTests.java │ └── target │ │ └── classes │ │ └── application.properties ├── spring-initializr-tutorial.md └── tasks.md ├── week05 ├── logger │ ├── AppConfig.java │ ├── Logger.java │ ├── LoggerFileScenarioImpl.java │ ├── LoggerLevel.java │ └── LoggerStdoutImpl.java └── tasks.md ├── week06 └── tasks.md ├── week07 ├── Inventory Item API.postman_collection.json └── tasks.md ├── week09 └── tasks.md └── week10 └── tasks.md /.gitignore: -------------------------------------------------------------------------------- 1 | /lab-project/inventorysystem/target/classes/bg/fmi/uni/inventorysystem/*.class 2 | /lab-project/inventorysystem/target/classes/bg/fmi/uni/inventorysystem/model/*.class 3 | /lab-project/inventorysystem/target/classes/bg/fmi/uni/inventorysystem/repository/*.class 4 | -------------------------------------------------------------------------------- /Extra Materials/Clean Code/Clean-Code.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreamix-fmi-course-2025/web-development-with-java/92fcc272fae8577d4ff92c18f1f78768202881bf/Extra Materials/Clean Code/Clean-Code.pdf -------------------------------------------------------------------------------- /Extra Materials/Git/guideToGit.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreamix-fmi-course-2025/web-development-with-java/92fcc272fae8577d4ff92c18f1f78768202881bf/Extra Materials/Git/guideToGit.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # web-development-with-java-2025 -------------------------------------------------------------------------------- /lab-project/inventorysystem/.gitignore: -------------------------------------------------------------------------------- 1 | # Java & Maven 2 | /target/ 3 | *.class 4 | *.jar 5 | *.war 6 | *.ear 7 | 8 | # IntelliJ IDEA 9 | .idea/ 10 | *.iml 11 | out/ 12 | 13 | # VS Code 14 | .vscode/ 15 | 16 | # OS files 17 | .DS_Store 18 | Thumbs.db 19 | 20 | # Logs 21 | *.log 22 | 23 | # Environment 24 | .env 25 | 26 | # Database 27 | *.h2.db 28 | *.mv.db 29 | 30 | # Flyway 31 | flyway.conf 32 | 33 | # Misc 34 | *.swp 35 | 36 | # Exclude generated files 37 | /build/ 38 | /node_modules/ 39 | 40 | # Ignore sensitive files 41 | application-local.properties 42 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "java", 9 | "name": "Current File", 10 | "request": "launch", 11 | "mainClass": "${file}" 12 | }, 13 | { 14 | "type": "java", 15 | "name": "InventorysystemApplication", 16 | "request": "launch", 17 | "mainClass": "bg.fmi.uni.inventorysystem.InventorysystemApplication", 18 | "projectName": "inventorysystem" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /lab-project/inventorysystem/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "java.compile.nullAnalysis.mode": "disabled", 3 | "java.configuration.updateBuildConfiguration": "automatic" 4 | } -------------------------------------------------------------------------------- /lab-project/inventorysystem/HELP.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ### Reference Documentation 4 | For further reference, please consider the following sections: 5 | 6 | * [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) 7 | * [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/3.4.3/maven-plugin) 8 | * [Create an OCI image](https://docs.spring.io/spring-boot/3.4.3/maven-plugin/build-image.html) 9 | 10 | ### Maven Parent overrides 11 | 12 | Due to Maven's design, elements are inherited from the parent POM to the project POM. 13 | While most of the inheritance is fine, it also inherits unwanted elements like `` and `` from the parent. 14 | To prevent this, the project POM contains empty overrides for these elements. 15 | If you manually switch to a different parent and actually want the inheritance, you need to remove those overrides. 16 | 17 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/README.md: -------------------------------------------------------------------------------- 1 | # Inventory System 2 | 3 | ## Project Summary (Business Perspective) 4 | The Inventory System is designed to streamline and digitalize the management of club or organizational inventory. It allows clubs to efficiently track, lend, and consume items, manage club members, and monitor transactions. The system supports both borrowable items (like equipment) and consumable items (like spare parts), ensuring that all inventory movements are logged and transparent. This reduces manual errors, prevents losses, and improves accountability for resources. 5 | 6 | ## Project Technological Aspects 7 | - **Backend Framework:** Spring Boot (Java) 8 | - **ORM:** Hibernate/JPA 9 | - **Database:** PostgreSQL 10 | - **Database Migrations:** Flyway 11 | - **Build Tool:** Maven 12 | - **Logging:** Standard Java logging (see `Logger` usage in services). In development, logs are written to a file; in local (non-dev) environments, logs are output to stdout. 13 | - **API:** RESTful endpoints 14 | - **Testing:** Spring Boot Test 15 | 16 | ## Project Architecture 17 | - **Layered Architecture:** 18 | - **Controller Layer:** Handles HTTP requests and responses (REST API). 19 | - **Service Layer:** Business logic, validation, and transaction management. 20 | - **Repository Layer:** JPA repositories for database access. 21 | - **Model/Entity Layer:** JPA entities representing DB tables. 22 | - **DTO Layer:** Data Transfer Objects for API communication. 23 | - **Configuration:** Application and logging configuration, Flyway setup. 24 | 25 | - **Migrations:** 26 | - All DB schema and seed data managed via Flyway migrations (`db/migration`). 27 | 28 | - **Custom Logging:** 29 | - Logging is achieved by injecting a `Logger` instance into service classes. 30 | 31 | ## Project Functionalities 32 | - **Club Member Management:** 33 | - Create, update, delete, and list club members (with first/last name, email, phone). 34 | - **Inventory Management:** 35 | - Add, update, delete, and list inventory items (borrowable or consumable). 36 | - **Transaction Management:** 37 | - Record borrow/return transactions for borrowables. 38 | - Record consumption for consumables. 39 | - View all transactions and filter upcoming due returns (only for borrowables). 40 | - **Low Stock Alerts:** 41 | - Identify inventory items below a configurable stock threshold. 42 | - **Database Versioning:** 43 | - All schema and data changes are versioned and applied automatically. 44 | 45 | ## Database Structure (E/R Format) 46 | ``` 47 | [ClubMember] 1---* [Transaction] *---1 [InventoryItem] 48 | 49 | ClubMember 50 | - id (PK) 51 | - firstName 52 | - lastName 53 | - email 54 | - phoneNumber 55 | 56 | InventoryItem 57 | - id (PK) 58 | - name 59 | - description 60 | - quantity 61 | - serialNumber 62 | - unitOfMeasurement 63 | - category 64 | - borrowable (boolean) 65 | - addedDate 66 | 67 | Transaction 68 | - id (PK) 69 | - member_id (FK -> ClubMember) 70 | - item_id (FK -> InventoryItem) 71 | - transactionDate 72 | - dueDate (nullable for consumables) 73 | - quantityUsed 74 | - returnedDate (nullable) 75 | ``` 76 | 77 | ## Additional Notes 78 | - **Environment Variables:** 79 | - `INVENTORY_THRESHOLD` for low stock alerts (default: 10) 80 | - `config.transaction.reminder-safety-window-days` for due reminders (default: 10) 81 | - **API Docs:** 82 | - REST endpoints follow standard CRUD conventions for members, items, and transactions. 83 | - **Development:** 84 | - DB is auto-initialized and seeded by Flyway on startup. 85 | - `.gitignore` excludes secrets, build artifacts, and IDE files. 86 | 87 | --- 88 | 89 | ## API Specification & Sample Requests 90 | 91 | ### Club Members 92 | - **GET /api/members** — List all members 93 | - **POST /api/members** — Create member 94 | - Request: 95 | ```json 96 | { 97 | "firstName": "John", 98 | "lastName": "Doe", 99 | "email": "john@example.com", 100 | "phone": "+359888111222" 101 | } 102 | ``` 103 | - **PUT /api/members/{id}** — Update member 104 | - **DELETE /api/members/{id}** — Delete member 105 | 106 | ### Inventory Items 107 | - **GET /api/items** — List all items 108 | - **POST /api/items** — Create item 109 | - Request: 110 | ```json 111 | { 112 | "name": "3D Printer", 113 | "description": "MakerBot Replicator", 114 | "quantity": 2, 115 | "serialNumber": "SN123456", 116 | "unitOfMeasurement": "pcs", 117 | "category": "EQUIPMENT", 118 | "borrowable": true 119 | } 120 | ``` 121 | - **PUT /api/items/{id}** — Update item 122 | - **DELETE /api/items/{id}** — Delete item 123 | 124 | ### Transactions 125 | - **GET /api/transactions** — List all transactions 126 | - **POST /api/transactions/borrow** — Borrow item 127 | - Request: 128 | ```json 129 | { 130 | "memberId": 1, 131 | "itemId": 2, 132 | "quantityUsed": 1 133 | } 134 | ``` 135 | - **POST /api/transactions/consume** — Consume item 136 | - Request: 137 | ```json 138 | { 139 | "memberId": 1, 140 | "itemId": 3, 141 | "quantityUsed": 5 142 | } 143 | ``` 144 | - **POST /api/transactions/return/{transactionId}** — Return borrowed item 145 | 146 | ### Stock Alerts 147 | - **GET /api/items/low-stock** — List items below threshold 148 | 149 | ### Upcoming Due 150 | - **GET /api/transactions/upcoming-due** — List borrowable transactions due soon 151 | 152 | --- 153 | 154 | ## Visual Database Diagram 155 | 156 | ```mermaid 157 | erDiagram 158 | ClubMember ||--o{ Transaction : "makes" 159 | InventoryItem ||--o{ Transaction : "involved in" 160 | 161 | ClubMember { 162 | int id 163 | string firstName 164 | string lastName 165 | string email 166 | string phoneNumber 167 | } 168 | InventoryItem { 169 | int id 170 | string name 171 | string description 172 | int quantity 173 | string serialNumber 174 | string unitOfMeasurement 175 | string category 176 | boolean borrowable 177 | datetime addedDate 178 | } 179 | Transaction { 180 | int id 181 | datetime transactionDate 182 | datetime dueDate 183 | int quantityUsed 184 | datetime returnedDate 185 | } 186 | ``` 187 | 188 | --- 189 | 190 | ## Setup Instructions 191 | 192 | 1. **Clone the Repository** 193 | ```sh 194 | git clone 195 | cd inventorysystem 196 | ``` 197 | 2. **Configure the Database** 198 | - Ensure PostgreSQL is running on port 5433 (or update `application.properties`). 199 | - Create a database named `inventory-system`. 200 | - Set environment variables if needed (e.g., `INVENTORY_THRESHOLD`). 201 | 3. **Build and Run** 202 | ```sh 203 | mvn clean install 204 | mvn spring-boot:run 205 | ``` 206 | - The app will auto-migrate and seed the database via Flyway. 207 | 4. **Access the API** 208 | - Default: `http://localhost:8080/api/` 209 | 210 | --- 211 | 212 | ## Contribution Guidelines 213 | 214 | - Fork the repository and create a feature branch. 215 | - Follow the existing code style and naming conventions. 216 | - Write tests for new features. 217 | - Ensure all tests pass before submitting a pull request. 218 | - Document new endpoints or features in the README. 219 | 220 | --- 221 | 222 | ## Additional Notes 223 | - For a production setup, configure environment variables securely and use a persistent database. 224 | - For questions or feature requests, please open an issue or contact the maintainer. 225 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/log.txt: -------------------------------------------------------------------------------- 1 | Tue Apr 01 19:20:18 EEST 2025 [INFO] - Item: RC Car, Quantity: 5 2 | Tue Apr 01 19:20:18 EEST 2025 [INFO] - Item: Battery Pack, Quantity: 10 3 | Tue Apr 01 19:20:18 EEST 2025 [INFO] - Item: RC Car, Quantity: 8 4 | Tue Apr 01 19:20:18 EEST 2025 [INFO] - Item: Battery Pack, Quantity: 10 5 | Tue Apr 01 19:26:05 EEST 2025 [INFO] - 🚀 Application started successfully! 6 | Tue Apr 01 19:26:05 EEST 2025 [INFO] - java.lang.IllegalArgumentException: Serial number S1231 already in DB 7 | Tue Apr 01 19:26:05 EEST 2025 [INFO] - --------------------------------------- 8 | Tue Apr 01 19:26:05 EEST 2025 [INFO] - ✅ Inventory items added successfully! 9 | Tue Apr 01 19:26:05 EEST 2025 [INFO] - --------------------------------------- 10 | Tue Apr 01 19:26:05 EEST 2025 [INFO] - 📌 Displaying all inventory items: 11 | Tue Apr 01 19:26:05 EEST 2025 [INFO] - Item: RC Car, Quantity: 5 12 | Tue Apr 01 19:26:05 EEST 2025 [INFO] - Item: Battery Pack, Quantity: 10 13 | Tue Apr 01 19:26:05 EEST 2025 [INFO] - --------------------------------------- 14 | Tue Apr 01 19:26:05 EEST 2025 [INFO] - 🔄 Updating 'RC Car' quantity to 8... 15 | Tue Apr 01 19:26:05 EEST 2025 [INFO] - --------------------------------------- 16 | Tue Apr 01 19:26:05 EEST 2025 [INFO] - 📌 Displaying updated inventory items: 17 | Tue Apr 01 19:26:05 EEST 2025 [INFO] - Item: RC Car, Quantity: 8 18 | Tue Apr 01 19:26:05 EEST 2025 [INFO] - Item: Battery Pack, Quantity: 10 19 | Tue Apr 01 19:26:05 EEST 2025 [INFO] - --------------------------------------- 20 | Tue Apr 01 19:26:05 EEST 2025 [INFO] - 📌 Displaying all low stock items: 21 | Tue Apr 01 19:26:05 EEST 2025 [INFO] - --------------------------------------- 22 | Tue Apr 01 19:29:21 EEST 2025 [INFO] - 🚀 Application started successfully! 23 | Tue Apr 01 19:29:21 EEST 2025 [INFO] - java.lang.IllegalArgumentException: Serial number S1231 already in DB 24 | Tue Apr 01 19:29:21 EEST 2025 [INFO] - --------------------------------------- 25 | Tue Apr 01 19:29:21 EEST 2025 [INFO] - ✅ Inventory items added successfully! 26 | Tue Apr 01 19:29:21 EEST 2025 [INFO] - --------------------------------------- 27 | Tue Apr 01 19:29:21 EEST 2025 [INFO] - 📌 Displaying all inventory items: 28 | Tue Apr 01 19:29:21 EEST 2025 [INFO] - Item: RC Car, Quantity: 5 29 | Tue Apr 01 19:29:21 EEST 2025 [INFO] - Item: Battery Pack, Quantity: 10 30 | Tue Apr 01 19:29:21 EEST 2025 [INFO] - --------------------------------------- 31 | Tue Apr 01 19:29:21 EEST 2025 [INFO] - 🔄 Updating 'RC Car' quantity to 8... 32 | Tue Apr 01 19:29:21 EEST 2025 [DEBUG] - Item updated successfully. 33 | Tue Apr 01 19:29:21 EEST 2025 [INFO] - --------------------------------------- 34 | Tue Apr 01 19:29:21 EEST 2025 [INFO] - 📌 Displaying updated inventory items: 35 | Tue Apr 01 19:29:21 EEST 2025 [INFO] - Item: RC Car, Quantity: 8 36 | Tue Apr 01 19:29:21 EEST 2025 [INFO] - Item: Battery Pack, Quantity: 10 37 | Tue Apr 01 19:29:21 EEST 2025 [INFO] - --------------------------------------- 38 | Tue Apr 01 19:29:21 EEST 2025 [INFO] - 📌 Displaying all low stock items: 39 | Tue Apr 01 19:29:21 EEST 2025 [INFO] - InventoryItem{id=1, name='RC Car', description='High-speed remote control car', quantity=8, unitOfMeasurement='pcs', category='Vehicles', borrowable=true, addedDate=2025-04-01T19:29:21.002624} 40 | Tue Apr 01 19:29:21 EEST 2025 [INFO] - --------------------------------------- 41 | Tue Apr 01 19:43:34 EEST 2025 [INFO] - 🚀 Application started successfully! 42 | Tue Apr 01 19:43:34 EEST 2025 [INFO] - java.lang.IllegalArgumentException: Serial number S1231 already in DB 43 | Tue Apr 01 19:43:34 EEST 2025 [INFO] - --------------------------------------- 44 | Tue Apr 01 19:43:34 EEST 2025 [INFO] - ✅ Inventory items added successfully! 45 | Tue Apr 01 19:43:34 EEST 2025 [INFO] - --------------------------------------- 46 | Tue Apr 01 19:43:34 EEST 2025 [INFO] - 📌 Displaying all inventory items: 47 | Tue Apr 01 19:43:34 EEST 2025 [INFO] - Item: RC Car, Quantity: 5 48 | Tue Apr 01 19:43:34 EEST 2025 [INFO] - Item: Battery Pack, Quantity: 10 49 | Tue Apr 01 19:43:34 EEST 2025 [INFO] - --------------------------------------- 50 | Tue Apr 01 19:43:34 EEST 2025 [INFO] - 🔄 Updating 'RC Car' quantity to 8... 51 | Tue Apr 01 19:43:34 EEST 2025 [DEBUG] - Item updated successfully. 52 | Tue Apr 01 19:43:34 EEST 2025 [INFO] - --------------------------------------- 53 | Tue Apr 01 19:43:34 EEST 2025 [INFO] - 📌 Displaying updated inventory items: 54 | Tue Apr 01 19:43:34 EEST 2025 [INFO] - Item: RC Car, Quantity: 8 55 | Tue Apr 01 19:43:34 EEST 2025 [INFO] - Item: Battery Pack, Quantity: 10 56 | Tue Apr 01 19:43:34 EEST 2025 [INFO] - --------------------------------------- 57 | Tue Apr 01 19:43:34 EEST 2025 [INFO] - 📌 Displaying all low stock items: 58 | Tue Apr 01 19:43:34 EEST 2025 [INFO] - Low-stock value used: lowStockThreshold -> 7 59 | Tue Apr 01 19:43:34 EEST 2025 [INFO] - Low-stock value used: appConfig.getInventory().getLowStockThreshold() -> 7 60 | Tue Apr 01 19:43:34 EEST 2025 [INFO] - --------------------------------------- 61 | Tue Apr 01 19:44:27 EEST 2025 [INFO] - 🚀 Application started successfully! 62 | Tue Apr 01 19:44:27 EEST 2025 [INFO] - java.lang.IllegalArgumentException: Serial number S1231 already in DB 63 | Tue Apr 01 19:44:27 EEST 2025 [INFO] - --------------------------------------- 64 | Tue Apr 01 19:44:27 EEST 2025 [INFO] - ✅ Inventory items added successfully! 65 | Tue Apr 01 19:44:27 EEST 2025 [INFO] - --------------------------------------- 66 | Tue Apr 01 19:44:27 EEST 2025 [INFO] - 📌 Displaying all inventory items: 67 | Tue Apr 01 19:44:27 EEST 2025 [INFO] - Item: RC Car, Quantity: 5 68 | Tue Apr 01 19:44:27 EEST 2025 [INFO] - Item: Battery Pack, Quantity: 10 69 | Tue Apr 01 19:44:27 EEST 2025 [INFO] - --------------------------------------- 70 | Tue Apr 01 19:44:27 EEST 2025 [INFO] - 🔄 Updating 'RC Car' quantity to 8... 71 | Tue Apr 01 19:44:27 EEST 2025 [DEBUG] - Item updated successfully. 72 | Tue Apr 01 19:44:27 EEST 2025 [INFO] - --------------------------------------- 73 | Tue Apr 01 19:44:27 EEST 2025 [INFO] - 📌 Displaying updated inventory items: 74 | Tue Apr 01 19:44:27 EEST 2025 [INFO] - Item: RC Car, Quantity: 8 75 | Tue Apr 01 19:44:27 EEST 2025 [INFO] - Item: Battery Pack, Quantity: 10 76 | Tue Apr 01 19:44:27 EEST 2025 [INFO] - --------------------------------------- 77 | Tue Apr 01 19:44:27 EEST 2025 [INFO] - 📌 Displaying all low stock items: 78 | Tue Apr 01 19:44:27 EEST 2025 [INFO] - Low-stock value used: lowStockThreshold -> 7 79 | Tue Apr 01 19:44:27 EEST 2025 [INFO] - Low-stock value used: appConfig.getInventory().getLowStockThreshold() -> 7 80 | Tue Apr 01 19:44:27 EEST 2025 [INFO] - --------------------------------------- 81 | Tue Apr 01 19:44:38 EEST 2025 [INFO] - 🚀 Application started successfully! 82 | Tue Apr 01 19:44:38 EEST 2025 [INFO] - java.lang.IllegalArgumentException: Serial number S1231 already in DB 83 | Tue Apr 01 19:44:38 EEST 2025 [INFO] - --------------------------------------- 84 | Tue Apr 01 19:44:38 EEST 2025 [INFO] - ✅ Inventory items added successfully! 85 | Tue Apr 01 19:44:38 EEST 2025 [INFO] - --------------------------------------- 86 | Tue Apr 01 19:44:38 EEST 2025 [INFO] - 📌 Displaying all inventory items: 87 | Tue Apr 01 19:44:38 EEST 2025 [INFO] - Item: RC Car, Quantity: 5 88 | Tue Apr 01 19:44:38 EEST 2025 [INFO] - Item: Battery Pack, Quantity: 10 89 | Tue Apr 01 19:44:38 EEST 2025 [INFO] - --------------------------------------- 90 | Tue Apr 01 19:44:38 EEST 2025 [INFO] - 🔄 Updating 'RC Car' quantity to 8... 91 | Tue Apr 01 19:44:38 EEST 2025 [DEBUG] - Item updated successfully. 92 | Tue Apr 01 19:44:38 EEST 2025 [INFO] - --------------------------------------- 93 | Tue Apr 01 19:44:38 EEST 2025 [INFO] - 📌 Displaying updated inventory items: 94 | Tue Apr 01 19:44:38 EEST 2025 [INFO] - Item: RC Car, Quantity: 8 95 | Tue Apr 01 19:44:38 EEST 2025 [INFO] - Item: Battery Pack, Quantity: 10 96 | Tue Apr 01 19:44:38 EEST 2025 [INFO] - --------------------------------------- 97 | Tue Apr 01 19:44:38 EEST 2025 [INFO] - 📌 Displaying all low stock items: 98 | Tue Apr 01 19:44:38 EEST 2025 [INFO] - Low-stock value used: lowStockThreshold -> 15 99 | Tue Apr 01 19:44:38 EEST 2025 [INFO] - Low-stock value used: appConfig.getInventory().getLowStockThreshold() -> 15 100 | Tue Apr 01 19:44:38 EEST 2025 [INFO] - InventoryItem{id=1, name='RC Car', description='High-speed remote control car', quantity=8, unitOfMeasurement='pcs', category='Vehicles', borrowable=true, addedDate=2025-04-01T19:44:38.901332900} 101 | Tue Apr 01 19:44:38 EEST 2025 [INFO] - InventoryItem{id=2, name='Battery Pack', description='Rechargeable battery', quantity=10, unitOfMeasurement='pcs', category='Accessories', borrowable=true, addedDate=2025-04-01T19:44:38.901332900} 102 | Tue Apr 01 19:44:38 EEST 2025 [INFO] - --------------------------------------- 103 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/mvnw.cmd: -------------------------------------------------------------------------------- 1 | <# : batch portion 2 | @REM ---------------------------------------------------------------------------- 3 | @REM Licensed to the Apache Software Foundation (ASF) under one 4 | @REM or more contributor license agreements. See the NOTICE file 5 | @REM distributed with this work for additional information 6 | @REM regarding copyright ownership. The ASF licenses this file 7 | @REM to you under the Apache License, Version 2.0 (the 8 | @REM "License"); you may not use this file except in compliance 9 | @REM with the License. You may obtain a copy of the License at 10 | @REM 11 | @REM http://www.apache.org/licenses/LICENSE-2.0 12 | @REM 13 | @REM Unless required by applicable law or agreed to in writing, 14 | @REM software distributed under the License is distributed on an 15 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | @REM KIND, either express or implied. See the License for the 17 | @REM specific language governing permissions and limitations 18 | @REM under the License. 19 | @REM ---------------------------------------------------------------------------- 20 | 21 | @REM ---------------------------------------------------------------------------- 22 | @REM Apache Maven Wrapper startup batch script, version 3.3.2 23 | @REM 24 | @REM Optional ENV vars 25 | @REM MVNW_REPOURL - repo url base for downloading maven distribution 26 | @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 27 | @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output 28 | @REM ---------------------------------------------------------------------------- 29 | 30 | @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) 31 | @SET __MVNW_CMD__= 32 | @SET __MVNW_ERROR__= 33 | @SET __MVNW_PSMODULEP_SAVE=%PSModulePath% 34 | @SET PSModulePath= 35 | @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( 36 | IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) 37 | ) 38 | @SET PSModulePath=%__MVNW_PSMODULEP_SAVE% 39 | @SET __MVNW_PSMODULEP_SAVE= 40 | @SET __MVNW_ARG0_NAME__= 41 | @SET MVNW_USERNAME= 42 | @SET MVNW_PASSWORD= 43 | @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) 44 | @echo Cannot start maven from wrapper >&2 && exit /b 1 45 | @GOTO :EOF 46 | : end batch / begin powershell #> 47 | 48 | $ErrorActionPreference = "Stop" 49 | if ($env:MVNW_VERBOSE -eq "true") { 50 | $VerbosePreference = "Continue" 51 | } 52 | 53 | # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties 54 | $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl 55 | if (!$distributionUrl) { 56 | Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" 57 | } 58 | 59 | switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { 60 | "maven-mvnd-*" { 61 | $USE_MVND = $true 62 | $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" 63 | $MVN_CMD = "mvnd.cmd" 64 | break 65 | } 66 | default { 67 | $USE_MVND = $false 68 | $MVN_CMD = $script -replace '^mvnw','mvn' 69 | break 70 | } 71 | } 72 | 73 | # apply MVNW_REPOURL and calculate MAVEN_HOME 74 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 75 | if ($env:MVNW_REPOURL) { 76 | $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } 77 | $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" 78 | } 79 | $distributionUrlName = $distributionUrl -replace '^.*/','' 80 | $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' 81 | $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" 82 | if ($env:MAVEN_USER_HOME) { 83 | $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" 84 | } 85 | $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' 86 | $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" 87 | 88 | if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { 89 | Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" 90 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 91 | exit $? 92 | } 93 | 94 | if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { 95 | Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" 96 | } 97 | 98 | # prepare tmp dir 99 | $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile 100 | $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" 101 | $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null 102 | trap { 103 | if ($TMP_DOWNLOAD_DIR.Exists) { 104 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 105 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 106 | } 107 | } 108 | 109 | New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null 110 | 111 | # Download and Install Apache Maven 112 | Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 113 | Write-Verbose "Downloading from: $distributionUrl" 114 | Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 115 | 116 | $webclient = New-Object System.Net.WebClient 117 | if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { 118 | $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) 119 | } 120 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 121 | $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null 122 | 123 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 124 | $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum 125 | if ($distributionSha256Sum) { 126 | if ($USE_MVND) { 127 | Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." 128 | } 129 | Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash 130 | if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { 131 | Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." 132 | } 133 | } 134 | 135 | # unzip and move 136 | Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null 137 | Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null 138 | try { 139 | Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null 140 | } catch { 141 | if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { 142 | Write-Error "fail to move MAVEN_HOME" 143 | } 144 | } finally { 145 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 146 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 147 | } 148 | 149 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 150 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.4.3 9 | 10 | 11 | bg.fmi.uni 12 | inventorysystem 13 | 0.0.1-SNAPSHOT 14 | inventorysystem 15 | Demo project for Spring Boot 16 | 17 | 21 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-web 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-validation 28 | 29 | 30 | 31 | 32 | org.projectlombok 33 | lombok 34 | 1.18.36 35 | provided 36 | 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-test 41 | test 42 | 43 | 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-starter-data-jpa 48 | 49 | 50 | 51 | 52 | org.postgresql 53 | postgresql 54 | runtime 55 | 56 | 57 | 58 | 59 | org.flywaydb 60 | flyway-core 61 | 9.22.3 62 | 63 | 64 | 65 | 66 | 67 | 68 | org.springframework.boot 69 | spring-boot-maven-plugin 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/InventorysystemApplication.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem; 2 | 3 | import jakarta.annotation.PostConstruct; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | import java.util.TimeZone; 8 | 9 | @SpringBootApplication 10 | public class InventorysystemApplication { 11 | 12 | @PostConstruct 13 | public void init() { 14 | TimeZone.setDefault(TimeZone.getTimeZone("UTC")); 15 | } 16 | 17 | public static void main(String[] args) { 18 | SpringApplication.run(InventorysystemApplication.class, args); 19 | } 20 | } -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/config/AppConfig.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.config; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @ConfigurationProperties(prefix = "config") 8 | @Configuration 9 | @Data 10 | public class AppConfig { 11 | private final LoggerConfig logger = new LoggerConfig(); 12 | private final Inventory inventory = new Inventory(); 13 | private final Transaction transaction = new Transaction(); 14 | 15 | @Data 16 | @ConfigurationProperties(prefix = "logger") 17 | public static class LoggerConfig { 18 | private String level; 19 | } 20 | 21 | @Data 22 | @ConfigurationProperties(prefix = "inventory") 23 | public static class Inventory { 24 | // [week05/Task2] default value for properties can be defined in properties level with expression language 25 | private Integer lowStockThreshold; 26 | } 27 | 28 | @Data 29 | @ConfigurationProperties(prefix = "transaction") 30 | public static class Transaction { 31 | // [week05/Task3] default value for properties can be defined in properties level with expression language 32 | private Integer reminderSafetyWindowDays; 33 | } 34 | } -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/config/logger/Logger.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.config.logger; 2 | 3 | import bg.fmi.uni.inventorysystem.vo.LoggerLevel; 4 | 5 | public interface Logger { 6 | 7 | void info(Object toLog); 8 | 9 | void debug(Object toLog); 10 | 11 | void trace(Object toLog); 12 | 13 | void error(Object toLog); 14 | 15 | default boolean isLoggingAllowed(LoggerLevel loggerLevel, LoggerLevel systemLoggerLevel) { 16 | return loggerLevel.getCode().compareTo(systemLoggerLevel.getCode()) <= 0; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/config/logger/LoggerFileScenarioImpl.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.config.logger; 2 | 3 | import bg.fmi.uni.inventorysystem.config.AppConfig; 4 | import bg.fmi.uni.inventorysystem.vo.LoggerLevel; 5 | import jakarta.annotation.PostConstruct; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.context.annotation.Profile; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.io.File; 11 | import java.io.FileWriter; 12 | import java.io.IOException; 13 | import java.io.PrintWriter; 14 | import java.util.Date; 15 | 16 | @Profile("dev") 17 | @Component("fileLogger") // if you need to define named component 18 | @RequiredArgsConstructor 19 | public class LoggerFileScenarioImpl implements Logger { 20 | 21 | private LoggerLevel systemLoggerLevel; 22 | 23 | private final AppConfig appConfig; 24 | 25 | @PostConstruct 26 | public void setup() { 27 | System.out.println(">>>>>>>>>>>>>>> [LoggerFileScenarioImpl] appConfig.getLogger().getLevel() -> " + appConfig.getLogger().getLevel()); 28 | systemLoggerLevel = LoggerLevel.valueOf(appConfig.getLogger().getLevel()); 29 | } 30 | 31 | @Override 32 | public void info(Object toLog) { 33 | LoggerLevel currentLogger = LoggerLevel.INFO; 34 | if (isLoggingAllowed(currentLogger, systemLoggerLevel)) { // not needed because info is always printed by the rule 35 | logInformation(toLog, currentLogger); 36 | } 37 | } 38 | 39 | @Override 40 | public void debug(Object toLog) { 41 | LoggerLevel currentLogger = LoggerLevel.DEBUG; 42 | if (isLoggingAllowed(currentLogger, systemLoggerLevel)) { 43 | logInformation(toLog, currentLogger); 44 | } 45 | } 46 | 47 | @Override 48 | public void trace(Object toLog) { 49 | LoggerLevel currentLogger = LoggerLevel.TRACE; 50 | if (isLoggingAllowed(currentLogger, systemLoggerLevel)) { 51 | logInformation(toLog, currentLogger); 52 | } 53 | } 54 | 55 | @Override 56 | public void error(Object toLog) { 57 | logInformation(toLog, LoggerLevel.ERROR); 58 | } 59 | 60 | private void logInformation(Object toLog, LoggerLevel currentLoggerLevel) { 61 | File log = new File("log.txt"); 62 | try (PrintWriter out = new PrintWriter(new FileWriter(log, true))) { 63 | out.println(new Date() + " [" + currentLoggerLevel.getLabel() + "] - " + toLog); 64 | } catch (IOException e) { 65 | e.printStackTrace(); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/config/logger/LoggerStdoutImpl.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.config.logger; 2 | 3 | import bg.fmi.uni.inventorysystem.config.AppConfig; 4 | import bg.fmi.uni.inventorysystem.vo.LoggerLevel; 5 | 6 | import jakarta.annotation.PostConstruct; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.context.annotation.Primary; 9 | import org.springframework.context.annotation.Profile; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.util.Date; 13 | 14 | @Profile("local") 15 | @Component 16 | @Primary 17 | public class LoggerStdoutImpl implements Logger { 18 | 19 | private final AppConfig appConfig; 20 | 21 | @Autowired 22 | public LoggerStdoutImpl(AppConfig appConfig) { 23 | this.appConfig = appConfig; 24 | } 25 | 26 | private LoggerLevel systemLoggerLevel; 27 | 28 | /** 29 | * By using the Bean lifecycle we can set specific values for our business functionalities 30 | */ 31 | @PostConstruct 32 | public void setup() { 33 | System.out.println(">>>>>>>>>>>>>>> [LoggerStdoutImpl] appConfig.getLogger().getLevel() -> " + appConfig.getLogger().getLevel()); 34 | systemLoggerLevel = LoggerLevel.valueOf(appConfig.getLogger().getLevel()); 35 | } 36 | 37 | @Override 38 | public void info(Object toLog) { 39 | logInformation(toLog, LoggerLevel.INFO); 40 | } 41 | 42 | @Override 43 | public void debug(Object toLog) { 44 | LoggerLevel currentLogger = LoggerLevel.DEBUG; 45 | if (isLoggingAllowed(currentLogger, systemLoggerLevel)) { 46 | logInformation(toLog, currentLogger); 47 | } 48 | } 49 | 50 | @Override 51 | public void trace(Object toLog) { 52 | LoggerLevel currentLogger = LoggerLevel.TRACE; 53 | if (isLoggingAllowed(currentLogger, systemLoggerLevel)) { 54 | logInformation(toLog, currentLogger); 55 | } 56 | } 57 | 58 | @Override 59 | public void error(Object toLog) { 60 | logInformation(toLog, LoggerLevel.ERROR); 61 | } 62 | 63 | private void logInformation(Object toLog, LoggerLevel currentLoggerLevel) { 64 | System.out.println(new Date() + " [" + currentLoggerLevel.getLabel() + "] - " + toLog); 65 | } 66 | } -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/controller/ClubMemberController.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.controller; 2 | 3 | import bg.fmi.uni.inventorysystem.dto.ClubMemberDto; 4 | import bg.fmi.uni.inventorysystem.service.ClubMemberService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.*; 8 | 9 | import java.util.List; 10 | 11 | @RestController 12 | @RequestMapping("/api/club-members") 13 | public class ClubMemberController { 14 | private final ClubMemberService clubMemberService; 15 | 16 | @Autowired 17 | public ClubMemberController(ClubMemberService clubMemberService) { 18 | this.clubMemberService = clubMemberService; 19 | } 20 | 21 | @GetMapping 22 | public List getAllMembers() { 23 | return clubMemberService.getAllMembers(); 24 | } 25 | 26 | @GetMapping("/{id}") 27 | public ResponseEntity getMemberById(@PathVariable Integer id) { 28 | return clubMemberService.getMemberById(id) 29 | .map(ResponseEntity::ok) 30 | .orElse(ResponseEntity.notFound().build()); 31 | } 32 | 33 | @PostMapping 34 | public ResponseEntity createMember(@RequestBody ClubMemberDto dto) { 35 | ClubMemberDto created = clubMemberService.createMember(dto); 36 | return ResponseEntity.ok(created); 37 | } 38 | 39 | @PutMapping("/{id}") 40 | public ResponseEntity updateMember(@PathVariable Integer id, @RequestBody ClubMemberDto dto) { 41 | return clubMemberService.updateMember(id, dto) 42 | .map(ResponseEntity::ok) 43 | .orElse(ResponseEntity.notFound().build()); 44 | } 45 | 46 | @DeleteMapping("/{id}") 47 | public ResponseEntity deleteMember(@PathVariable Integer id) { 48 | if (clubMemberService.deleteMember(id)) { 49 | return ResponseEntity.noContent().build(); 50 | } else { 51 | return ResponseEntity.notFound().build(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/controller/DummyController.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.controller; 2 | 3 | 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.ResponseStatus; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | @RestController 11 | @RequestMapping("dummy") 12 | public class DummyController { 13 | 14 | // @ResponseStatus(HttpStatus.NO_CONTENT) 15 | // @ResponseStatus(HttpStatus.ACCEPTED) 16 | // @ResponseStatus(HttpStatus.CREATED) 17 | // @ResponseStatus(HttpStatus.BAD_REQUEST) 18 | @ResponseStatus(HttpStatus.OK) 19 | @GetMapping("/hello-world") 20 | public String helloWorld() { 21 | return "Hello World!"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/controller/InventoryItemController.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.controller; 2 | 3 | 4 | import bg.fmi.uni.inventorysystem.config.logger.Logger; 5 | import bg.fmi.uni.inventorysystem.dto.InventoryItemDto; 6 | import bg.fmi.uni.inventorysystem.dto.InventoryItemPatchRequest; 7 | import bg.fmi.uni.inventorysystem.dto.InventoryItemRequest; 8 | import bg.fmi.uni.inventorysystem.exception.ItemNotFoundException; 9 | import bg.fmi.uni.inventorysystem.service.InventoryItemService; 10 | import jakarta.validation.Valid; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.http.HttpStatus; 13 | import org.springframework.http.ResponseEntity; 14 | 15 | import org.springframework.web.bind.annotation.*; 16 | import java.util.List; 17 | 18 | 19 | 20 | /** 21 | * Controller for inventory operations. 22 | */ 23 | @RestController 24 | @RequestMapping("/api/items") 25 | @RequiredArgsConstructor 26 | public class InventoryItemController { 27 | private final InventoryItemService inventoryItemService; 28 | private final Logger logger; 29 | 30 | /** 31 | * Displays all available inventory items. 32 | */ 33 | @GetMapping 34 | public List getAllItems() { 35 | List items = inventoryItemService.getAllItems(); 36 | if (items.isEmpty()) { 37 | logger.info("No inventory items available."); 38 | } else { 39 | items.forEach(item -> logger.info("Item: " + item.name() + ", Quantity: " + item.quantity())); 40 | } 41 | return items; 42 | } 43 | 44 | @GetMapping("/{id}") 45 | public InventoryItemDto getItemById(@PathVariable Integer id) { 46 | logger.info("Get Item by id: " + id); 47 | return inventoryItemService.getItemById(id) 48 | .orElseThrow(() -> new ItemNotFoundException(id)); 49 | } 50 | 51 | // Week 8 CRUD implementation 52 | @PostMapping // ADDED 53 | public ResponseEntity createItem(@Valid @RequestBody InventoryItemRequest request) { 54 | logger.info("Create Item API POST"); 55 | InventoryItemDto created = inventoryItemService.createItem(request); 56 | return new ResponseEntity<>(created, HttpStatus.CREATED); 57 | } 58 | 59 | @PutMapping("/{id}") 60 | public ResponseEntity upsertItem(@PathVariable Integer id, 61 | @Valid @RequestBody InventoryItemRequest request) { 62 | logger.info("Update Item with id: " + id); 63 | InventoryItemDto result = inventoryItemService.upsertItem(id, request); 64 | return result.id() == id ? ResponseEntity.ok(result) : new ResponseEntity<>(result, HttpStatus.CREATED); 65 | } 66 | 67 | @PatchMapping("/{id}") 68 | public ResponseEntity patchItem(@PathVariable Integer id, 69 | @RequestBody InventoryItemPatchRequest patchRequest) { 70 | return inventoryItemService.patchItem(id, patchRequest) 71 | .map(ResponseEntity::ok) 72 | .orElseThrow(() -> new ItemNotFoundException(id)); 73 | } 74 | 75 | @DeleteMapping("/{id}") 76 | public ResponseEntity deleteItem(@PathVariable Integer id) { 77 | if (inventoryItemService.deleteItem(id)) { 78 | return ResponseEntity.noContent().build(); 79 | } else { 80 | throw new ItemNotFoundException(id); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/controller/TransactionController.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.controller; 2 | 3 | import bg.fmi.uni.inventorysystem.dto.TransactionDto; 4 | import bg.fmi.uni.inventorysystem.service.TransactionService; 5 | import org.springframework.web.bind.annotation.*; 6 | import org.springframework.http.ResponseEntity; 7 | 8 | import lombok.RequiredArgsConstructor; 9 | import java.util.List; 10 | 11 | @RestController 12 | @RequiredArgsConstructor 13 | @RequestMapping("/transactions") 14 | public class TransactionController { 15 | private final TransactionService transactionService; 16 | 17 | @PostMapping 18 | public ResponseEntity createTransaction(@RequestBody TransactionRequest request) { 19 | TransactionDto dto = transactionService.createTransaction(request.memberId(), request.itemId(), request.quantityUsed()); 20 | return ResponseEntity.ok(dto); 21 | } 22 | 23 | @GetMapping 24 | public List getAllTransactions() { 25 | return transactionService.getAllTransactions(); 26 | } 27 | 28 | @GetMapping("/upcoming-due") 29 | public List getUpcomingDueTransactions() { 30 | return transactionService.getUpcomingDueTransactions(); 31 | } 32 | 33 | @PutMapping("/{id}/return") 34 | public ResponseEntity returnTransaction(@PathVariable Integer id) { 35 | TransactionDto dto = transactionService.returnTransaction(id); 36 | return ResponseEntity.ok(dto); 37 | } 38 | 39 | @ExceptionHandler(IllegalStateException.class) 40 | public ResponseEntity handleIllegalState(IllegalStateException ex) { 41 | return ResponseEntity.badRequest().body(ex.getMessage()); 42 | } 43 | 44 | // Request DTO 45 | public record TransactionRequest(Integer memberId, Integer itemId, int quantityUsed) {} 46 | } -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/dto/APIErrorDto.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.dto; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | public record APIErrorDto( 6 | String message, 7 | int status, 8 | LocalDateTime timestamp 9 | ) {} 10 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/dto/ClubMemberDto.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.dto; 2 | 3 | import bg.fmi.uni.inventorysystem.model.ClubMember; 4 | 5 | public record ClubMemberDto( 6 | Integer id, 7 | String firstName, 8 | String lastName, 9 | String email, 10 | String phone 11 | ) { 12 | 13 | public static ClubMemberDto fromEntity(ClubMember member) { 14 | return new ClubMemberDto( 15 | member.getId(), 16 | member.getFirstName(), 17 | member.getLastName(), 18 | member.getEmail(), 19 | member.getPhoneNumber() 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/dto/InventoryItemDto.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.dto; 2 | 3 | import bg.fmi.uni.inventorysystem.model.InventoryItem; 4 | import lombok.Getter; 5 | 6 | import java.time.LocalDateTime; 7 | 8 | /** 9 | * DTO for transferring inventory item data over REST. 10 | */ 11 | public record InventoryItemDto( 12 | Integer id, 13 | String name, 14 | String description, 15 | int quantity, 16 | String serialNumber, 17 | String unitOfMeasurement, 18 | String category, 19 | boolean borrowable, 20 | LocalDateTime addedDate 21 | ) { 22 | // Optional: static factory method for mapping from entity 23 | public static InventoryItemDto fromEntity(InventoryItem item) { 24 | return new InventoryItemDto( 25 | item.getId(), 26 | item.getName(), 27 | item.getDescription(), 28 | item.getQuantity(), 29 | item.getSerialNumber(), 30 | item.getUnitOfMeasurement() != null ? item.getUnitOfMeasurement().name() : null, 31 | item.getCategory() != null ? item.getCategory().name() : null, 32 | item.isBorrowable(), 33 | item.getAddedDate() 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/dto/InventoryItemPatchRequest.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.dto; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Optional; 6 | 7 | @Data 8 | public class InventoryItemPatchRequest { 9 | private Optional name = Optional.empty(); 10 | private Optional description = Optional.empty(); 11 | private Optional quantity = Optional.empty(); 12 | private Optional unitOfMeasurement = Optional.empty(); 13 | private Optional category = Optional.empty(); 14 | private Optional borrowable = Optional.empty(); 15 | } 16 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/dto/InventoryItemRequest.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.dto; 2 | 3 | import jakarta.validation.constraints.Min; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.Size; 6 | 7 | public record InventoryItemRequest( 8 | @NotBlank(message = "Name is required") 9 | String name, 10 | 11 | @Size(max = 128, message = "Description must be under 128 characters") 12 | String description, 13 | 14 | @Min(value = 0, message = "Quantity must be 0 or greater") 15 | int quantity, 16 | 17 | @NotBlank(message = "Serial number is required") 18 | String serialNumber, 19 | 20 | @NotBlank(message = "Unit of measurement is required") 21 | String unitOfMeasurement, 22 | 23 | @NotBlank(message = "Category is required") 24 | String category, 25 | 26 | boolean borrowable 27 | ) {} 28 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/dto/TransactionDto.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.dto; 2 | 3 | import bg.fmi.uni.inventorysystem.model.Transaction; 4 | import java.time.LocalDateTime; 5 | 6 | public record TransactionDto( 7 | Integer id, 8 | Integer itemId, 9 | Integer memberId, 10 | int quantityUsed, 11 | LocalDateTime transactionDate, 12 | LocalDateTime dueDate, 13 | LocalDateTime returnedDate, 14 | String itemType // "BORROWABLE" or "CONSUMABLE" 15 | ) { 16 | public static TransactionDto fromEntity(Transaction transaction) { 17 | String itemType = transaction.getItem().isBorrowable() ? "BORROWABLE" : "CONSUMABLE"; 18 | return new TransactionDto( 19 | transaction.getId(), 20 | transaction.getItem().getId(), 21 | transaction.getMember().getId(), 22 | transaction.getQuantityUsed(), 23 | transaction.getTransactionDate(), 24 | transaction.getDueDate(), 25 | transaction.getReturnedDate(), 26 | itemType 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/exception/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.MethodArgumentNotValidException; 5 | import org.springframework.web.bind.annotation.ExceptionHandler; 6 | import org.springframework.web.bind.annotation.ResponseStatus; 7 | import org.springframework.web.bind.annotation.RestControllerAdvice; 8 | 9 | import java.time.LocalDateTime; 10 | import bg.fmi.uni.inventorysystem.dto.APIErrorDto; 11 | 12 | // Simple exception handler 13 | @RestControllerAdvice 14 | public class GlobalExceptionHandler { 15 | 16 | @ResponseStatus(HttpStatus.NOT_FOUND) 17 | @ExceptionHandler(ItemNotFoundException.class) 18 | public APIErrorDto handleNotFound(ItemNotFoundException ex) { 19 | return new APIErrorDto( 20 | ex.getMessage(), 21 | HttpStatus.NOT_FOUND.value(), 22 | LocalDateTime.now() 23 | ); 24 | } 25 | 26 | @ResponseStatus(HttpStatus.BAD_REQUEST) 27 | @ExceptionHandler(IllegalArgumentException.class) 28 | public APIErrorDto handleIllegalData(IllegalArgumentException ex) { 29 | return new APIErrorDto( 30 | ex.getMessage(), 31 | HttpStatus.BAD_REQUEST.value(), 32 | LocalDateTime.now() 33 | ); 34 | } 35 | 36 | @ResponseStatus(HttpStatus.BAD_REQUEST) 37 | @ExceptionHandler(MethodArgumentNotValidException.class) 38 | public APIErrorDto handleValidation(MethodArgumentNotValidException ex) { 39 | String errors = ex.getBindingResult().getFieldErrors().stream() 40 | .map(err -> err.getField() + ": " + err.getDefaultMessage()) 41 | .reduce((a, b) -> a + "; " + b).orElse(ex.getMessage()); 42 | return new APIErrorDto( 43 | errors, 44 | HttpStatus.BAD_REQUEST.value(), 45 | LocalDateTime.now() 46 | ); 47 | } 48 | 49 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 50 | @ExceptionHandler(Exception.class) 51 | public APIErrorDto handleGeneric(Exception ex) { 52 | return new APIErrorDto( 53 | ex.getMessage(), 54 | HttpStatus.INTERNAL_SERVER_ERROR.value(), 55 | LocalDateTime.now() 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/exception/ItemNotFoundException.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.exception; 2 | 3 | 4 | public class ItemNotFoundException extends RuntimeException { 5 | public ItemNotFoundException(Integer id) { 6 | super("Item not found with ID: " + id); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/model/ClubMember.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.model; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | 8 | import jakarta.persistence.*; 9 | 10 | @Entity 11 | @Data 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @Builder 15 | /** 16 | * Unidirectional: ClubMember does not reference Transaction. 17 | */ 18 | public class ClubMember { 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | private Integer id; 22 | private String firstName; 23 | private String lastName; 24 | private String email; 25 | private String phoneNumber; // New field 26 | } 27 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/model/InventoryItem.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.model; 2 | 3 | import bg.fmi.uni.inventorysystem.vo.Category; 4 | import bg.fmi.uni.inventorysystem.vo.UnitOfMeasurement; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.time.LocalDateTime; 9 | 10 | import jakarta.persistence.*; 11 | 12 | @Data 13 | @NoArgsConstructor 14 | @Entity 15 | /** 16 | * Unidirectional: InventoryItem does not reference Transaction. 17 | */ 18 | public class InventoryItem { 19 | 20 | @Id 21 | @GeneratedValue(strategy = GenerationType.IDENTITY) 22 | private Integer id; 23 | private String name; 24 | private String description; 25 | private int quantity; 26 | private String serialNumber; 27 | 28 | @Enumerated(EnumType.STRING) 29 | private UnitOfMeasurement unitOfMeasurement; 30 | 31 | @Enumerated(EnumType.STRING) 32 | private Category category; 33 | 34 | private boolean borrowable; 35 | private LocalDateTime addedDate; 36 | 37 | public InventoryItem(String name, String description, int quantity, String serialNumber, UnitOfMeasurement unitOfMeasurement, Category category, boolean borrowable) { 38 | this.name = name; 39 | this.description = description; 40 | this.quantity = quantity; 41 | this.serialNumber = serialNumber; 42 | this.unitOfMeasurement = unitOfMeasurement; 43 | this.category = category; 44 | this.borrowable = borrowable; 45 | this.addedDate = LocalDateTime.now(); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/model/Transaction.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.model; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | 6 | import java.time.LocalDateTime; 7 | 8 | import jakarta.persistence.*; 9 | 10 | @Entity 11 | @Data 12 | @NoArgsConstructor 13 | /** 14 | * Transaction is the owning side of unidirectional relationships to ClubMember and InventoryItem. 15 | */ 16 | public class Transaction { 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.IDENTITY) 19 | private Integer id; 20 | 21 | @ManyToOne 22 | private ClubMember member; 23 | 24 | @ManyToOne 25 | private InventoryItem item; 26 | 27 | private LocalDateTime transactionDate; 28 | private LocalDateTime dueDate; 29 | private Integer quantityUsed; 30 | private LocalDateTime returnedDate; // Nullable, only set for borrowables when returned 31 | 32 | public Transaction(ClubMember member, InventoryItem item, int days, int quantityUsed) { 33 | this.member = member; 34 | this.item = item; 35 | this.transactionDate = LocalDateTime.now(); 36 | if (item.isBorrowable()) { 37 | this.dueDate = transactionDate.plusDays(days); 38 | } else { 39 | this.dueDate = null; 40 | } 41 | this.quantityUsed = quantityUsed; 42 | this.returnedDate = null; 43 | } 44 | 45 | public LocalDateTime getReturnedDate() { 46 | return returnedDate; 47 | } 48 | 49 | public void setReturnedDate(LocalDateTime returnedDate) { 50 | this.returnedDate = returnedDate; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/repository/ClubMemberRepository.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.repository; 2 | 3 | import bg.fmi.uni.inventorysystem.model.ClubMember; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | 8 | @Repository 9 | public interface ClubMemberRepository extends JpaRepository { 10 | // JpaRepository provides basic CRUD methods 11 | } 12 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/repository/InventoryItemRepository.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.repository; 2 | 3 | import bg.fmi.uni.inventorysystem.model.InventoryItem; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface InventoryItemRepository extends JpaRepository { 9 | } 10 | 11 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/repository/TransactionRepository.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.repository; 2 | 3 | import bg.fmi.uni.inventorysystem.model.Transaction; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface TransactionRepository extends JpaRepository { 9 | } 10 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/repository/sequence/InventoryItemSequence.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.repository.sequence; 2 | 3 | public class InventoryItemSequence { 4 | private static Integer sequence = 1000; 5 | 6 | public static Integer getNextValue() { 7 | return sequence++; 8 | } 9 | } -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/service/ClubMemberService.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.service; 2 | 3 | import bg.fmi.uni.inventorysystem.dto.ClubMemberDto; 4 | import bg.fmi.uni.inventorysystem.model.ClubMember; 5 | import bg.fmi.uni.inventorysystem.repository.ClubMemberRepository; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.List; 10 | import java.util.Optional; 11 | import java.util.stream.Collectors; 12 | 13 | @Service 14 | public class ClubMemberService { 15 | private final ClubMemberRepository clubMemberRepository; 16 | 17 | @Autowired 18 | public ClubMemberService(ClubMemberRepository clubMemberRepository) { 19 | this.clubMemberRepository = clubMemberRepository; 20 | } 21 | 22 | public List getAllMembers() { 23 | return clubMemberRepository.findAll().stream() 24 | .map(ClubMemberDto::fromEntity) 25 | .collect(Collectors.toList()); 26 | } 27 | 28 | public Optional getMemberById(Integer id) { 29 | return clubMemberRepository.findById(id).map(ClubMemberDto::fromEntity); 30 | } 31 | 32 | public ClubMemberDto createMember(ClubMemberDto dto) { 33 | ClubMember member = ClubMember.builder() 34 | .firstName(dto.firstName()) 35 | .lastName(dto.lastName()) 36 | .email(dto.email()) 37 | .phoneNumber(dto.phone()) 38 | .build(); 39 | ClubMember saved = clubMemberRepository.save(member); 40 | return ClubMemberDto.fromEntity(saved); 41 | } 42 | 43 | public Optional updateMember(Integer id, ClubMemberDto dto) { 44 | Optional opt = clubMemberRepository.findById(id); 45 | if (opt.isPresent()) { 46 | ClubMember member = opt.get(); 47 | member.setFirstName(dto.firstName()); 48 | member.setLastName(dto.lastName()); 49 | member.setEmail(dto.email()); 50 | ClubMember updated = clubMemberRepository.save(member); 51 | return Optional.of(ClubMemberDto.fromEntity(updated)); 52 | } 53 | return Optional.empty(); 54 | } 55 | 56 | public boolean deleteMember(Integer id) { 57 | if (clubMemberRepository.existsById(id)) { 58 | clubMemberRepository.deleteById(id); 59 | return true; 60 | } 61 | return false; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/service/InventoryItemService.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.service; 2 | 3 | 4 | import bg.fmi.uni.inventorysystem.config.AppConfig; 5 | import bg.fmi.uni.inventorysystem.config.logger.Logger; 6 | import bg.fmi.uni.inventorysystem.dto.InventoryItemDto; 7 | import bg.fmi.uni.inventorysystem.dto.InventoryItemPatchRequest; 8 | import bg.fmi.uni.inventorysystem.dto.InventoryItemRequest; 9 | import bg.fmi.uni.inventorysystem.exception.ItemNotFoundException; 10 | import bg.fmi.uni.inventorysystem.model.InventoryItem; 11 | import bg.fmi.uni.inventorysystem.vo.UnitOfMeasurement; 12 | import bg.fmi.uni.inventorysystem.vo.Category; 13 | import bg.fmi.uni.inventorysystem.repository.InventoryItemRepository; 14 | import lombok.RequiredArgsConstructor; 15 | import lombok.extern.slf4j.Slf4j; 16 | import org.springframework.beans.factory.annotation.Value; 17 | import org.springframework.stereotype.Service; 18 | 19 | import java.util.List; 20 | import java.util.Optional; 21 | import java.util.stream.Collectors; 22 | 23 | /** 24 | * Service layer for handling inventory operations. 25 | */ 26 | @Slf4j 27 | @Service 28 | @RequiredArgsConstructor 29 | public class InventoryItemService { 30 | 31 | private final Logger logger; 32 | private final AppConfig appConfig; 33 | private final InventoryItemRepository itemRepository; 34 | 35 | @Value("${config.inventory.low-stock-threshold:10}") 36 | private Integer lowStockThreshold; 37 | 38 | /** 39 | * Retrieves all inventory items. 40 | * @return List of all items in inventory. 41 | */ 42 | public List getAllItems() { 43 | return itemRepository.findAll().stream() 44 | .map(InventoryItemDto::fromEntity) 45 | .collect(Collectors.toList()); 46 | } 47 | 48 | /** 49 | * Retrieves all inventory items that are low in stock. 50 | * @return List of inventory items below the threshold. 51 | */ 52 | public List getLowStockItems() { 53 | logger.info("Low-stock value used: lowStockThreshold -> " + lowStockThreshold); 54 | logger.info("Low-stock value used: appConfig.getInventory().getLowStockThreshold() -> " + lowStockThreshold); 55 | return itemRepository.findAll().stream() 56 | .filter(item -> item.getQuantity() < appConfig.getInventory().getLowStockThreshold()) 57 | .collect(Collectors.toList()); 58 | } 59 | 60 | /** 61 | * Retrieves an inventory item by its ID. 62 | * @param id The ID of the item to fetch. 63 | * @return Optional containing the item if found, or empty otherwise. 64 | */ 65 | public Optional getItemById(Integer id) { 66 | return itemRepository.findById(id) 67 | .map(InventoryItemDto::fromEntity); 68 | } 69 | 70 | 71 | public InventoryItemDto createItem(InventoryItemRequest request) { 72 | try { 73 | InventoryItem item = new InventoryItem( 74 | request.name(), 75 | request.description(), 76 | request.quantity(), 77 | request.serialNumber(), 78 | UnitOfMeasurement.parse(request.unitOfMeasurement()), 79 | Category.parse(request.category()), 80 | request.borrowable() 81 | ); 82 | itemRepository.save(item); 83 | return InventoryItemDto.fromEntity(item); 84 | } catch (IllegalArgumentException e) { 85 | logger.error("Invalid unit of measurement or category: " + e.getMessage()); 86 | throw new RuntimeException(e); 87 | } 88 | } 89 | 90 | public InventoryItemDto upsertItem(Integer id, InventoryItemRequest request) { 91 | Optional existing = itemRepository.findById(id); 92 | 93 | InventoryItem item; 94 | if (existing.isPresent()) { 95 | item = existing.get(); 96 | item.setName(request.name()); 97 | item.setDescription(request.description()); 98 | item.setQuantity(request.quantity()); 99 | item.setSerialNumber(request.serialNumber()); 100 | item.setUnitOfMeasurement(UnitOfMeasurement.parse(request.unitOfMeasurement())); 101 | item.setCategory(Category.parse(request.category())); 102 | item.setBorrowable(request.borrowable()); 103 | 104 | logger.debug("Existing item found with id " + id + ". Updating item based on the provided request"); 105 | itemRepository.save(item); 106 | } else { 107 | item = new InventoryItem( 108 | request.name(), request.description(), request.quantity(), 109 | request.serialNumber(), 110 | UnitOfMeasurement.parse(request.unitOfMeasurement()), 111 | Category.parse(request.category()), 112 | request.borrowable() 113 | ); 114 | logger.debug("No existing item found with id " + id + ". Creating new one"); 115 | itemRepository.save(item); 116 | } 117 | return InventoryItemDto.fromEntity(item); 118 | } 119 | 120 | public Optional patchItem(Integer id, InventoryItemPatchRequest patchRequest) { 121 | Optional optionalItem = itemRepository.findById(id); 122 | if (optionalItem.isEmpty()) { 123 | throw new ItemNotFoundException(id); 124 | } 125 | 126 | InventoryItem item = optionalItem.get(); 127 | 128 | patchRequest.getName().ifPresent(item::setName); 129 | patchRequest.getDescription().ifPresent(item::setDescription); 130 | patchRequest.getQuantity().ifPresent(item::setQuantity); 131 | patchRequest.getUnitOfMeasurement().ifPresent(unitStr -> item.setUnitOfMeasurement(UnitOfMeasurement.parse(unitStr))); 132 | patchRequest.getCategory().ifPresent(catStr -> item.setCategory(Category.parse(catStr))); 133 | patchRequest.getBorrowable().ifPresent(item::setBorrowable); 134 | 135 | itemRepository.save(item); 136 | 137 | return Optional.of(InventoryItemDto.fromEntity(item)); 138 | } 139 | 140 | // we can directly throw exception if element is not present or reuse the boolean flag in upper layer 141 | public boolean deleteItem(Integer id) { 142 | if (itemRepository.existsById(id)) { 143 | itemRepository.deleteById(id); 144 | return true; 145 | } 146 | return false; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/service/TransactionService.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.service; 2 | 3 | import bg.fmi.uni.inventorysystem.config.AppConfig; 4 | import bg.fmi.uni.inventorysystem.config.logger.Logger; 5 | import bg.fmi.uni.inventorysystem.dto.TransactionDto; 6 | import bg.fmi.uni.inventorysystem.model.InventoryItem; 7 | import bg.fmi.uni.inventorysystem.model.ClubMember; 8 | import bg.fmi.uni.inventorysystem.repository.InventoryItemRepository; 9 | import bg.fmi.uni.inventorysystem.repository.ClubMemberRepository; 10 | import org.springframework.transaction.annotation.Transactional; 11 | import bg.fmi.uni.inventorysystem.model.Transaction; 12 | import bg.fmi.uni.inventorysystem.repository.TransactionRepository; 13 | import lombok.RequiredArgsConstructor; 14 | import org.springframework.stereotype.Service; 15 | 16 | import java.time.LocalDate; 17 | import java.time.temporal.ChronoUnit; 18 | import java.util.List; 19 | import java.util.Optional; 20 | import java.util.function.Predicate; 21 | import java.util.stream.Collectors; 22 | 23 | import static java.util.Objects.isNull; 24 | 25 | @Service 26 | @RequiredArgsConstructor 27 | public class TransactionService { 28 | private final Logger logger; 29 | private final AppConfig appConfig; 30 | private final TransactionRepository transactionRepository; 31 | private final InventoryItemRepository inventoryItemRepository; 32 | private final ClubMemberRepository clubMemberRepository; 33 | 34 | public List getAllTransactions() { 35 | return transactionRepository.findAll().stream() 36 | .map(TransactionDto::fromEntity) 37 | .collect(Collectors.toList()); 38 | } 39 | 40 | @Transactional 41 | public TransactionDto createTransaction(Integer memberId, Integer itemId, int quantityUsed) { 42 | ClubMember member = clubMemberRepository.findById(memberId) 43 | .orElseThrow(() -> new IllegalStateException("Member not found")); 44 | InventoryItem item = inventoryItemRepository.findById(itemId) 45 | .orElseThrow(() -> new IllegalStateException("Item not found")); 46 | if (item.getQuantity() < quantityUsed) { 47 | throw new IllegalStateException("Not enough quantity for item: " + item.getName()); 48 | } 49 | item.setQuantity(item.getQuantity() - quantityUsed); 50 | inventoryItemRepository.save(item); 51 | // Assume 7 days for borrowable, 0 for consumable 52 | int days = item.isBorrowable() ? 7 : 0; 53 | Transaction transaction = new Transaction(member, item, days, quantityUsed); 54 | transactionRepository.save(transaction); 55 | return TransactionDto.fromEntity(transaction); 56 | } 57 | 58 | @Transactional 59 | public TransactionDto returnTransaction(Integer transactionId) { 60 | Transaction transaction = transactionRepository.findById(transactionId) 61 | .orElseThrow(() -> new IllegalStateException("Transaction not found")); 62 | if (transaction.getReturnedDate() != null) { 63 | throw new IllegalStateException("Transaction already marked as returned"); 64 | } 65 | transaction.setReturnedDate(java.time.LocalDateTime.now()); 66 | transactionRepository.save(transaction); 67 | return TransactionDto.fromEntity(transaction); 68 | } 69 | 70 | public Optional getTransactionById(Integer id) { 71 | return transactionRepository.findById(id).map(TransactionDto::fromEntity); 72 | } 73 | 74 | public TransactionDto createTransaction(Transaction transaction) { 75 | Transaction saved = transactionRepository.save(transaction); 76 | return TransactionDto.fromEntity(saved); 77 | } 78 | 79 | public Optional updateTransaction(Integer id, Transaction transaction) { 80 | Optional opt = transactionRepository.findById(id); 81 | if (opt.isPresent()) { 82 | Transaction existing = opt.get(); 83 | // update fields as needed 84 | existing.setMember(transaction.getMember()); 85 | existing.setItem(transaction.getItem()); 86 | existing.setQuantityUsed(transaction.getQuantityUsed()); 87 | existing.setTransactionDate(transaction.getTransactionDate()); 88 | existing.setDueDate(transaction.getDueDate()); 89 | if (transaction.getReturnedDate() != null) { 90 | existing.setReturnedDate(transaction.getReturnedDate()); 91 | } 92 | transactionRepository.save(existing); 93 | return Optional.of(TransactionDto.fromEntity(existing)); 94 | } 95 | return Optional.empty(); 96 | } 97 | 98 | public boolean deleteTransaction(Integer id) { 99 | if (transactionRepository.existsById(id)) { 100 | transactionRepository.deleteById(id); 101 | return true; 102 | } 103 | return false; 104 | } 105 | 106 | public List getUpcomingDueTransactions() { 107 | LocalDate today = LocalDate.now(); 108 | int reminderWindow = appConfig.getTransaction().getReminderSafetyWindowDays(); 109 | 110 | Predicate notReturned = t -> isNull(t.getReturnedDate()); 111 | Predicate isBorrowable = t -> t.getItem().isBorrowable(); 112 | Predicate dueWithinWindow = t -> { 113 | long daysUntilDue = ChronoUnit.DAYS.between(today, t.getDueDate().toLocalDate()); 114 | return daysUntilDue >= 0 && daysUntilDue <= reminderWindow; 115 | }; 116 | 117 | // usually you will prefer for the DB to calculate the result, for lab purposes we will query for all Transactions 118 | List soonDue = transactionRepository.findAll().stream() 119 | .filter(notReturned.and(isBorrowable).and(dueWithinWindow)) 120 | .map(TransactionDto::fromEntity) 121 | .toList(); 122 | if (soonDue.isEmpty()) { 123 | logger.info("No upcoming due transactions found."); 124 | } else { 125 | logger.info(String.format("[INFO] Found %d transactions due within %d days. Transaction IDs: %s%n", 126 | soonDue.size(), reminderWindow, 127 | soonDue.stream().map(TransactionDto::id).map(Object::toString).collect(Collectors.joining(", "))) 128 | ); 129 | } 130 | return soonDue; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/vo/Category.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.vo; 2 | 3 | public enum Category { 4 | AIRPLANE, 5 | HELICOPTER, 6 | DRONE, 7 | CAR, 8 | BOAT, 9 | TOOL, 10 | ACCESSORY, 11 | POWER_SOURCE; 12 | 13 | public static Category parse(String category) { 14 | try { 15 | return Category.valueOf(category.trim().toUpperCase()); 16 | } catch (Exception e) { 17 | throw new IllegalArgumentException("Invalid category: " + category); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/vo/LoggerLevel.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.vo; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum LoggerLevel { 7 | 8 | INFO(1, "INFO"), 9 | DEBUG(2, "DEBUG"), 10 | TRACE(3, "TRACE"), 11 | ERROR(0, "ERROR"); 12 | 13 | private final Integer code; 14 | public final String label; 15 | 16 | LoggerLevel(Integer code, String label) { 17 | this.code = code; 18 | this.label = label; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/vo/UnitOfMeasurement.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.vo; 2 | 3 | public enum UnitOfMeasurement { 4 | PIECE, 5 | SET, 6 | GRAM, 7 | KILOGRAM, 8 | LITER, 9 | METER; 10 | 11 | public static UnitOfMeasurement parse(String value) { 12 | try { 13 | return UnitOfMeasurement.valueOf(value.trim().toUpperCase()); 14 | } catch (Exception e) { 15 | throw new IllegalArgumentException("Invalid unit of measurement: " + value); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=inventorysystem 2 | 3 | config.logger.level=DEBUG 4 | 5 | # you can override parameters per profile 6 | config.inventory.low-stock-threshold=4 -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/resources/application-local.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=inventorysystem 2 | 3 | config.logger.level=TRACE -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=inventorysystem 2 | server.port=8081 3 | 4 | spring.profiles.active=local 5 | 6 | config.logger.level=TRACE 7 | 8 | # [week05/Task2] default value for properties can be defined in properties level with expression language 9 | # In order to add environment variable INVENTORY_THRESHOLD with IntelliJ go to Edit Configurations...>Environment variables>+ 10 | config.inventory.low-stock-threshold=${INVENTORY_THRESHOLD:10} 11 | 12 | config.transaction.reminder-safety-window-days=10 13 | 14 | spring.datasource.url=jdbc:postgresql://localhost:5433/inventory-system 15 | spring.datasource.username=postgres 16 | spring.datasource.password=pgadmin 17 | spring.datasource.driver-class-name=org.postgresql.Driver 18 | 19 | spring.jpa.hibernate.ddl-auto=validate 20 | spring.jpa.show-sql=true 21 | spring.jpa.properties.hibernate.format_sql=true 22 | 23 | # Load initial schema and data 24 | # Flyway will handle DB migrations and data seeding 25 | spring.flyway.enabled=true 26 | spring.flyway.locations=classpath:db/migration -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/resources/db/migration/V1__init.sql: -------------------------------------------------------------------------------- 1 | -- Flyway migration: Initial schema only 2 | 3 | -- Create table for ClubMember 4 | CREATE TABLE IF NOT EXISTS club_member ( 5 | id SERIAL PRIMARY KEY, 6 | first_name VARCHAR(255) NOT NULL, 7 | last_name VARCHAR(255) NOT NULL, 8 | email VARCHAR(255) NOT NULL UNIQUE, 9 | phone_number VARCHAR(32) 10 | ); 11 | 12 | -- Create table for InventoryItem 13 | CREATE TABLE IF NOT EXISTS inventory_item ( 14 | id SERIAL PRIMARY KEY, 15 | name VARCHAR(255) NOT NULL, 16 | description TEXT, 17 | quantity INTEGER NOT NULL, 18 | serial_number VARCHAR(255), 19 | unit_of_measurement VARCHAR(50) NOT NULL, 20 | category VARCHAR(50) NOT NULL, 21 | borrowable BOOLEAN NOT NULL, 22 | added_date TIMESTAMP NOT NULL 23 | ); 24 | 25 | -- Create table for Transaction 26 | CREATE TABLE IF NOT EXISTS transaction ( 27 | id SERIAL PRIMARY KEY, 28 | member_id INTEGER NOT NULL REFERENCES club_member(id), 29 | item_id INTEGER NOT NULL REFERENCES inventory_item(id), 30 | transaction_date TIMESTAMP NOT NULL, 31 | due_date TIMESTAMP NOT NULL, 32 | quantity_used INTEGER NOT NULL, 33 | returned_date TIMESTAMP 34 | ); 35 | 36 | -- Add indexes for performance 37 | CREATE INDEX IF NOT EXISTS idx_transaction_member_id ON transaction(member_id); 38 | CREATE INDEX IF NOT EXISTS idx_transaction_item_id ON transaction(item_id); 39 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/main/resources/db/migration/V2__seed_data.sql: -------------------------------------------------------------------------------- 1 | -- Flyway migration: Seed initial data 2 | 3 | -- Insert initial ClubMembers 4 | INSERT INTO club_member (first_name, last_name, email, phone_number) VALUES 5 | ('John', 'Doe', 'john.doe@example.com', '+359888111222'), 6 | ('Jane', 'Smith', 'jane.smith@example.com', '+359888333444'); 7 | 8 | -- Insert initial InventoryItems 9 | INSERT INTO inventory_item (name, description, quantity, serial_number, unit_of_measurement, category, borrowable, added_date) VALUES 10 | ('RC Airplane', 'Remote controlled airplane', 10, 'SN12345', 'PIECE', 'AIRPLANE', true, NOW()), 11 | ('Drone Set', 'Quadcopter drone set', 5, 'SN67890', 'SET', 'DRONE', true, NOW()), 12 | ('Propeller', 'Spare propeller for airplane', 100, 'SN54321', 'PIECE', 'ACCESSORY', false, NOW()); 13 | 14 | -- Insert initial Transactions 15 | INSERT INTO transaction (member_id, item_id, transaction_date, due_date, quantity_used, returned_date) VALUES 16 | (1, 1, NOW(), NOW() + INTERVAL '7 days', 1, NULL), 17 | (2, 2, NOW(), NOW() + INTERVAL '7 days', 1, NULL), 18 | -- Consumable usage: member 1 uses 3 Propellers (item_id 3, which is not borrowable) 19 | (1, 3, NOW(), NOW(), 3, NULL); 20 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/src/test/java/bg/fmi/uni/inventorysystem/InventorysystemApplicationTests.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class InventorysystemApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/target/classes/application-dev.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=inventorysystem 2 | 3 | config.logger.level=DEBUG 4 | 5 | # you can override parameters per profile 6 | config.inventory.low-stock-threshold=4 -------------------------------------------------------------------------------- /lab-project/inventorysystem/target/classes/application-local.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=inventorysystem 2 | 3 | config.logger.level=TRACE -------------------------------------------------------------------------------- /lab-project/inventorysystem/target/classes/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=inventorysystem 2 | server.port=8081 3 | 4 | spring.profiles.active=local 5 | 6 | config.logger.level=TRACE 7 | 8 | # [week05/Task2] default value for properties can be defined in properties level with expression language 9 | # In order to add environment variable INVENTORY_THRESHOLD with IntelliJ go to Edit Configurations...>Environment variables>+ 10 | config.inventory.low-stock-threshold=${INVENTORY_THRESHOLD:10} 11 | 12 | config.transaction.reminder-safety-window-days=10 13 | 14 | spring.datasource.url=jdbc:postgresql://localhost:5433/inventory-system 15 | spring.datasource.username=postgres 16 | spring.datasource.password=pgadmin 17 | spring.datasource.driver-class-name=org.postgresql.Driver 18 | 19 | spring.jpa.hibernate.ddl-auto=validate 20 | spring.jpa.show-sql=true 21 | spring.jpa.properties.hibernate.format_sql=true 22 | 23 | # Load initial schema and data 24 | # Flyway will handle DB migrations and data seeding 25 | spring.flyway.enabled=true 26 | spring.flyway.locations=classpath:db/migration -------------------------------------------------------------------------------- /lab-project/inventorysystem/target/classes/bg/fmi/uni/inventorysystem/controller/ClubMemberController.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreamix-fmi-course-2025/web-development-with-java/92fcc272fae8577d4ff92c18f1f78768202881bf/lab-project/inventorysystem/target/classes/bg/fmi/uni/inventorysystem/controller/ClubMemberController.class -------------------------------------------------------------------------------- /lab-project/inventorysystem/target/classes/bg/fmi/uni/inventorysystem/controller/TransactionController$TransactionRequest.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreamix-fmi-course-2025/web-development-with-java/92fcc272fae8577d4ff92c18f1f78768202881bf/lab-project/inventorysystem/target/classes/bg/fmi/uni/inventorysystem/controller/TransactionController$TransactionRequest.class -------------------------------------------------------------------------------- /lab-project/inventorysystem/target/classes/bg/fmi/uni/inventorysystem/dto/APIErrorDto.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreamix-fmi-course-2025/web-development-with-java/92fcc272fae8577d4ff92c18f1f78768202881bf/lab-project/inventorysystem/target/classes/bg/fmi/uni/inventorysystem/dto/APIErrorDto.class -------------------------------------------------------------------------------- /lab-project/inventorysystem/target/classes/bg/fmi/uni/inventorysystem/dto/ClubMemberDto.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreamix-fmi-course-2025/web-development-with-java/92fcc272fae8577d4ff92c18f1f78768202881bf/lab-project/inventorysystem/target/classes/bg/fmi/uni/inventorysystem/dto/ClubMemberDto.class -------------------------------------------------------------------------------- /lab-project/inventorysystem/target/classes/bg/fmi/uni/inventorysystem/dto/TransactionDto.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreamix-fmi-course-2025/web-development-with-java/92fcc272fae8577d4ff92c18f1f78768202881bf/lab-project/inventorysystem/target/classes/bg/fmi/uni/inventorysystem/dto/TransactionDto.class -------------------------------------------------------------------------------- /lab-project/inventorysystem/target/classes/bg/fmi/uni/inventorysystem/service/ClubMemberService.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreamix-fmi-course-2025/web-development-with-java/92fcc272fae8577d4ff92c18f1f78768202881bf/lab-project/inventorysystem/target/classes/bg/fmi/uni/inventorysystem/service/ClubMemberService.class -------------------------------------------------------------------------------- /lab-project/inventorysystem/target/classes/bg/fmi/uni/inventorysystem/vo/Category.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreamix-fmi-course-2025/web-development-with-java/92fcc272fae8577d4ff92c18f1f78768202881bf/lab-project/inventorysystem/target/classes/bg/fmi/uni/inventorysystem/vo/Category.class -------------------------------------------------------------------------------- /lab-project/inventorysystem/target/classes/bg/fmi/uni/inventorysystem/vo/UnitOfMeasurement.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreamix-fmi-course-2025/web-development-with-java/92fcc272fae8577d4ff92c18f1f78768202881bf/lab-project/inventorysystem/target/classes/bg/fmi/uni/inventorysystem/vo/UnitOfMeasurement.class -------------------------------------------------------------------------------- /lab-project/inventorysystem/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst: -------------------------------------------------------------------------------- 1 | bg/fmi/uni/inventorysystem/model/Category.class 2 | bg/fmi/uni/inventorysystem/config/logger/LoggerFileScenarioImpl.class 3 | bg/fmi/uni/inventorysystem/controller/TransactionController.class 4 | bg/fmi/uni/inventorysystem/service/TransactionService.class 5 | bg/fmi/uni/inventorysystem/repository/TransactionRepository.class 6 | bg/fmi/uni/inventorysystem/model/ClubMember.class 7 | bg/fmi/uni/inventorysystem/model/ClubMember$ClubMemberBuilder.class 8 | bg/fmi/uni/inventorysystem/service/InventoryItemService.class 9 | bg/fmi/uni/inventorysystem/dto/ClubMemberDto.class 10 | bg/fmi/uni/inventorysystem/config/logger/Logger.class 11 | bg/fmi/uni/inventorysystem/model/Transaction.class 12 | bg/fmi/uni/inventorysystem/dto/InventoryItemRequest.class 13 | bg/fmi/uni/inventorysystem/dto/InventoryItemPatchRequest.class 14 | bg/fmi/uni/inventorysystem/exception/ItemNotFoundException.class 15 | bg/fmi/uni/inventorysystem/config/AppConfig$LoggerConfig.class 16 | bg/fmi/uni/inventorysystem/dto/InventoryItemDto.class 17 | bg/fmi/uni/inventorysystem/InventorysystemApplication.class 18 | bg/fmi/uni/inventorysystem/config/AppConfig.class 19 | bg/fmi/uni/inventorysystem/controller/DummyController.class 20 | bg/fmi/uni/inventorysystem/dto/APIErrorDto.class 21 | bg/fmi/uni/inventorysystem/config/logger/LoggerStdoutImpl.class 22 | bg/fmi/uni/inventorysystem/controller/ClubMemberController.class 23 | bg/fmi/uni/inventorysystem/service/ClubMemberService.class 24 | bg/fmi/uni/inventorysystem/controller/TransactionController$TransactionRequest.class 25 | bg/fmi/uni/inventorysystem/model/InventoryItem.class 26 | bg/fmi/uni/inventorysystem/model/UnitOfMeasurement.class 27 | bg/fmi/uni/inventorysystem/vo/LoggerLevel.class 28 | bg/fmi/uni/inventorysystem/repository/sequence/InventoryItemSequence.class 29 | bg/fmi/uni/inventorysystem/exception/GlobalExceptionHandler.class 30 | bg/fmi/uni/inventorysystem/config/AppConfig$Inventory.class 31 | bg/fmi/uni/inventorysystem/config/AppConfig$Transaction.class 32 | bg/fmi/uni/inventorysystem/controller/InventoryItemController.class 33 | bg/fmi/uni/inventorysystem/repository/InventoryItemRepository.class 34 | bg/fmi/uni/inventorysystem/dto/TransactionDto.class 35 | bg/fmi/uni/inventorysystem/repository/ClubMemberRepository.class 36 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst: -------------------------------------------------------------------------------- 1 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/InventorysystemApplication.java 2 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/config/AppConfig.java 3 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/config/logger/Logger.java 4 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/config/logger/LoggerFileScenarioImpl.java 5 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/config/logger/LoggerStdoutImpl.java 6 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/controller/ClubMemberController.java 7 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/controller/DummyController.java 8 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/controller/InventoryItemController.java 9 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/controller/TransactionController.java 10 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/dto/APIErrorDto.java 11 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/dto/ClubMemberDto.java 12 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/dto/InventoryItemDto.java 13 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/dto/InventoryItemPatchRequest.java 14 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/dto/InventoryItemRequest.java 15 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/dto/TransactionDto.java 16 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/exception/GlobalExceptionHandler.java 17 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/exception/ItemNotFoundException.java 18 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/model/Category.java 19 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/model/ClubMember.java 20 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/model/InventoryItem.java 21 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/model/Transaction.java 22 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/model/UnitOfMeasurement.java 23 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/repository/ClubMemberRepository.java 24 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/repository/InventoryItemRepository.java 25 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/repository/TransactionRepository.java 26 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/repository/sequence/InventoryItemSequence.java 27 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/service/ClubMemberService.java 28 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/service/InventoryItemService.java 29 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/service/TransactionService.java 30 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/vo/LoggerLevel.java 31 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst: -------------------------------------------------------------------------------- 1 | bg/fmi/uni/inventorysystem/InventorysystemApplicationTests.class 2 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst: -------------------------------------------------------------------------------- 1 | /Users/georgiminkov/Projects/FMICourse2025/web-development-with-java/lab-project/inventorysystem/src/test/java/bg/fmi/uni/inventorysystem/InventorysystemApplicationTests.java 2 | -------------------------------------------------------------------------------- /lab-project/inventorysystem/target/surefire-reports/bg.fmi.uni.inventorysystem.InventorysystemApplicationTests.txt: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------- 2 | Test set: bg.fmi.uni.inventorysystem.InventorysystemApplicationTests 3 | ------------------------------------------------------------------------------- 4 | Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.935 s <<< FAILURE! -- in bg.fmi.uni.inventorysystem.InventorysystemApplicationTests 5 | bg.fmi.uni.inventorysystem.InventorysystemApplicationTests.contextLoads -- Time elapsed: 0.004 s <<< ERROR! 6 | java.lang.IllegalStateException: Failed to load ApplicationContext for [WebMergedContextConfiguration@5dfe23e8 testClass = bg.fmi.uni.inventorysystem.InventorysystemApplicationTests, locations = [], classes = [bg.fmi.uni.inventorysystem.InventorysystemApplication], contextInitializerClasses = [], activeProfiles = [], propertySourceDescriptors = [], propertySourceProperties = ["org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true"], contextCustomizers = [org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@30f842ca, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@25df00a0, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@1be2019a, org.springframework.boot.test.web.reactor.netty.DisableReactorResourceFactoryGlobalResourcesContextCustomizerFactory$DisableReactorResourceFactoryGlobalResourcesContextCustomizerCustomizer@7fb4f2a9, org.springframework.boot.test.autoconfigure.OnFailureConditionReportContextCustomizerFactory$OnFailureConditionReportContextCustomizer@7dfd3c81, org.springframework.boot.test.autoconfigure.actuate.observability.ObservabilityContextCustomizerFactory$DisableObservabilityContextCustomizer@1f, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizer@f58853c, org.springframework.test.context.support.DynamicPropertiesContextCustomizer@0, org.springframework.boot.test.context.SpringBootTestAnnotation@44778bbd], resourceBasePath = "src/main/webapp", contextLoader = org.springframework.boot.test.context.SpringBootContextLoader, parent = null] 7 | at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:180) 8 | at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:130) 9 | at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:200) 10 | at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:139) 11 | at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:260) 12 | at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:160) 13 | at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) 14 | at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) 15 | at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) 16 | at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) 17 | at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1708) 18 | at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) 19 | at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) 20 | at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) 21 | at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) 22 | at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) 23 | at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) 24 | at java.base/java.util.Optional.orElseGet(Optional.java:364) 25 | at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) 26 | at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) 27 | Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Failed to initialize dependency 'dataSourceScriptDatabaseInitializer' of LoadTimeWeaverAware bean 'entityManagerFactory': Error creating bean with name 'dataSourceScriptDatabaseInitializer' defined in class path resource [org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.class]: No schema scripts found at location 'classpath:db/init.sql' 28 | at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:328) 29 | at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:207) 30 | at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:970) 31 | at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627) 32 | at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) 33 | at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) 34 | at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) 35 | at org.springframework.boot.test.context.SpringBootContextLoader.lambda$loadContext$3(SpringBootContextLoader.java:137) 36 | at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58) 37 | at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46) 38 | at org.springframework.boot.SpringApplication.withHook(SpringApplication.java:1461) 39 | at org.springframework.boot.test.context.SpringBootContextLoader$ContextLoaderHook.run(SpringBootContextLoader.java:553) 40 | at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:137) 41 | at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:108) 42 | at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:225) 43 | at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:152) 44 | ... 19 more 45 | Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSourceScriptDatabaseInitializer' defined in class path resource [org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.class]: No schema scripts found at location 'classpath:db/init.sql' 46 | at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1812) 47 | at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:601) 48 | at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523) 49 | at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) 50 | at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:346) 51 | at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) 52 | at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) 53 | at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) 54 | ... 34 more 55 | Caused by: java.lang.IllegalStateException: No schema scripts found at location 'classpath:db/init.sql' 56 | at org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer.getScripts(AbstractScriptDatabaseInitializer.java:129) 57 | at org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer.applyScripts(AbstractScriptDatabaseInitializer.java:106) 58 | at org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer.applySchemaScripts(AbstractScriptDatabaseInitializer.java:98) 59 | at org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer.initializeDatabase(AbstractScriptDatabaseInitializer.java:76) 60 | at org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer.afterPropertiesSet(AbstractScriptDatabaseInitializer.java:66) 61 | at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1859) 62 | at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1808) 63 | ... 41 more 64 | 65 | -------------------------------------------------------------------------------- /week01/class-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreamix-fmi-course-2025/web-development-with-java/92fcc272fae8577d4ff92c18f1f78768202881bf/week01/class-diagram.png -------------------------------------------------------------------------------- /week02/README.md: -------------------------------------------------------------------------------- 1 | # Java tasks & Git 2 | 3 | 4 | # [Git] Task 0 Setup your GitHub account and prepare your repository 5 | - Create GitHub account 6 | - Download and install git [git-scm](https://git-scm.com/downloads) 7 | - Generate SSH key for GitHub authentication [ssh-gen-paper](https://www.purdue.edu/science/scienceit/ssh-keys-windows.html) 8 | Example for ssh command with Windows 9 | ``` 10 | ssh-keygen -t rsa -b 4096 -C "your_email@example.com" 11 | ``` 12 | for mac it's located inside '/Users/YourUsername/.ssh/id_rsa' for windows 'C:\Users\YourUsername\.ssh\id_rsa' 13 | - Setup SSH with GitHub [documentation](https://docs.github.com/en/authentication/connecting-to-github-with-ssh) 14 | - Create repository and clone it or init local repository and later on connect you remote repository 15 | 16 | # [Git] Practice merge on local repository (together or with the cheatsheet) 17 | 1. Create folder 'git-lab-02' and init repository inside it 18 |
19 | Steps 20 | 21 | ```sh 22 | mkdir git-lab-02 23 | cd git-lab-02 24 | git init 25 | ``` 26 | 27 |
28 | 29 | 2. Create initial commit creating a text file 30 |
31 | Steps 32 | 33 | ```sh 34 | echo "Hello, Git World" > greeting.txt 35 | git add greeting.txt 36 | git commit -m "Initial commit for adding the first line of greeting.txt" 37 | ``` 38 | 39 |
40 | 41 | 3. Create a feature branch. You receive a task in which you need to add one more line to the greeting.txt 42 |
43 | Steps 44 | 45 | 46 | ## Create and switch to a new branch 47 | ```sh 48 | git checkout -b feature-branch 49 | ``` 50 | ## Make changes in the feature branch 51 | ```sh 52 | echo "Hello from the feature branch" > greeting.txt 53 | git add greeting.txt 54 | git commit -m "Update from feature branch to greeting.txt" 55 | ``` 56 | 57 |
58 | 59 | 4. Return to master branch and alter greeting.txt file 60 |
61 | Steps 62 | 63 | 64 | ## Switch back to the master branch 65 | ```sh 66 | git checkout master 67 | ``` 68 | ## Make conflicting changes in the master branch 69 | ```sh 70 | echo "Hello from the master branch" > greeting.txt 71 | git add greeting.txt 72 | git commit -m "Update from master branch" 73 | 74 | ``` 75 | 76 |
77 | 78 | 5. Merge feature-branh to master 79 |
80 | Steps 81 | 82 | 83 | ## Attempt to merge feature-branch into master 84 | ```sh 85 | git merge feature-branch 86 | ``` 87 | 88 | ## Result 89 | ``` 90 | Auto-merging greeting.txt 91 | CONFLICT (content): Merge conflict in greeting.txt 92 | Automatic merge failed; fix conflicts and then commit the result. 93 | ``` 94 |
95 | 96 | 6. Resolve conflicts 97 | 98 |
99 | Steps 100 | 101 | ## Validate where are your conflicted lines 102 | Open the conflicting file in a text editor and resolve the conflicts 103 | For example, the file might look like this: 104 | ``` 105 | <<<<<<< HEAD 106 | Hello from the master branch 107 | ======= 108 | Hello from the feature branch 109 | >>>>>>> feature-branch 110 | ``` 111 | Remove the lines and add: 'Hello from both branches' 112 | 113 | ```sh 114 | vi greeting.txt 115 | #alter file by entering 'i' (edit mode), delete lines and add the appropriate line. Close with :wq 116 | ``` 117 | 118 | ## Execute after save 119 | After resolving it could look like this: 120 | Hello from both branches 121 | 122 | ## After resolving the conflict, add the file and commit the resolution 123 | ```sh 124 | git add greeting.txt 125 | git commit -m "Resolve merge conflict between master and feature branch" 126 | git log --all --decorate --oneline --graph 127 | ``` 128 | 129 |
130 | 131 | 7. You are taking a new feature task - feature-branch-2. Start by creating a branch 132 |
133 | Steps 134 | 135 | 136 | ## Reset your starting point 137 | ```sh 138 | git checkout master 139 | ``` 140 | 141 | ## Create a new feature branch 142 | ```sh 143 | git checkout -b feature-branch-2 144 | ``` 145 | 146 | ## Make changes in the new feature branch 147 | ```sh 148 | echo "Another feature update" > feature.txt 149 | git add feature.txt 150 | git commit -m "Add feature in feature-branch-2" 151 | ``` 152 |
153 | 154 | 8. Create changes to the feature file inside master (create the file) 155 |
156 | Steps 157 | 158 | 159 | ## Switch to master 160 | ```sh 161 | git checkout master 162 | ``` 163 | 164 | ## Add text line to feature.txt 165 | ```sh 166 | echo "Updates from master for conflict" > feature.txt 167 | git add feature.txt 168 | git commit -m "Update feature.txt on master for rebase conflict" 169 | ``` 170 | 171 | ``` 172 | georgiminkov@Georgis-MacBook-Pro git-lab-01 % git log --all --decorate --oneline --graph 173 | * c5d244a (HEAD -> master) Update feature.txt on master for rebase conflict 174 | | * 59ca35e (feature-branch-2) Add feature in feature-branch-2 175 | |/ 176 | * 74e0483 Resolve merge conflict between master and feature branch 177 | |\ 178 | | * 271e26a (feature-branch) Update from feature branch to greeting.txt 179 | * | 38dc12b Update from master branch 180 | |/ 181 | * cec5ff8 Initial commit for adding the first line of greeting.txt 182 | ``` 183 |
184 | 185 | 9. Rebase feature-branch-2 over master 186 |
187 | Steps 188 | 189 | 190 | ## Switch to feature-branch-2 to start the rebase 191 | ```sh 192 | git checkout feature-branch-2 193 | ``` 194 | 195 | ## Begin the rebase 196 | ```sh 197 | git rebase master 198 | ``` 199 | ``` 200 | georgiminkov@Georgis-MacBook-Pro git-lab-01 % git rebase master 201 | CONFLICT (add/add): Merge conflict in feature.txt 202 | Auto-merging feature.txt 203 | error: could not apply 59ca35e... Add feature in feature-branch-2 204 | Resolve all conflicts manually, mark them as resolved with 205 | "git add/rm ", then run "git rebase --continue". 206 | You can instead skip this commit: run "git rebase --skip". 207 | To abort and get back to the state before "git rebase", run "git rebase --abort". 208 | Could not apply 59ca35e... Add feature in feature-branch-2 209 | ``` 210 |
211 | 212 | 10. Resolve conflicts 213 |
214 | Steps 215 | 216 | 217 | ## Switch to feature-branch-2 to start the rebase 218 | ## Resolve the conflict in feature.txt manually 219 | ``` 220 | Git marked the file like this: 221 | <<<<<<< HEAD 222 | Another feature update 223 | ======= 224 | Updates from master for conflict 225 | >>>>>>> master 226 | ``` 227 | 228 | ## Combined updates from both master and feature-branch-2 229 | 230 | ## After resolving the conflict, add the file to mark it as resolved 231 | ```sh 232 | git add feature.txt 233 | ``` 234 | 235 | ## Continue the rebase after resolving the conflict 236 | 237 | ```sh 238 | git rebase --continue 239 | ``` 240 | 241 | ## Add commit message and exit with :wq 242 | 243 | ## Final tree 244 | ``` 245 | * b9cc4f8 (HEAD -> feature-branch-2) Add feature in feature-branch-2 246 | * c5d244a (master) Update feature.txt on master for rebase conflict 247 | * 74e0483 Resolve merge conflict between master and feature branch 248 | |\ 249 | | * 271e26a (feature-branch) Update from feature branch to greeting.txt 250 | * | 38dc12b Update from master branch 251 | |/ 252 | * cec5ff8 Initial commit for adding the first line of greeting.txt 253 | ``` 254 |
255 | 256 | 257 | # Task 2 Setup GUI 258 | - IntelliJ 259 | - TortoiseGIT [download](https://tortoisegit.org/download/) 260 | - SourceTree [download](https://www.sourcetreeapp.com) 261 | - GitKraken [download](https://www.gitkraken.com) 262 | 263 | # Task 3 264 | Finsh tasks from [lab01](https://github.com/dreamix-fmi-course-2024/web-development-with-java-lab/blob/main/lab01/tasks.md#additional-tasks) and push them to your repository -------------------------------------------------------------------------------- /week02/additionalTasks.md: -------------------------------------------------------------------------------- 1 | # Additional Tasks 2 | 3 | ## 1. Advanced Filtering and Mapping 4 | ### Task 1: Filter and Transform a List of Transactions 5 | You have a `Transaction` class: 6 | 7 | ```java 8 | public class Transaction { 9 | private String id; 10 | private String type; // "DEPOSIT" or "WITHDRAWAL" 11 | private double amount; 12 | private LocalDateTime timestamp; 13 | 14 | // Constructor, Getters, Setters 15 | } 16 | ``` 17 | 18 | **Objective:** 19 | 1. **Filter** transactions that occurred in the last **7 days**. 20 | 2. **Convert** all `"WITHDRAWAL"` transactions to negative values. 21 | 3. **Sort** transactions by timestamp **descending**. 22 | 23 | **Method Signature:** 24 | ```java 25 | public List processTransactions(List transactions) 26 | ``` 27 | 28 | --- 29 | 30 | ## 2. Grouping and Aggregation 31 | ### Task 2: Group Transactions by Type and Sum Their Amounts 32 | Using the same `Transaction` class, implement a method that: 33 | - Groups transactions by their `type` (`DEPOSIT`, `WITHDRAWAL`). 34 | - Computes the **total amount** for each type. 35 | 36 | **Expected Output Example:** 37 | ``` 38 | { 39 | "DEPOSIT": 12000.50, 40 | "WITHDRAWAL": -5400.75 41 | } 42 | ``` 43 | 44 | **Method Signature:** 45 | ```java 46 | public Map sumByTransactionType(List transactions) 47 | ``` 48 | 49 | --- 50 | 51 | ## 3. Nested Streams and FlatMapping 52 | ### Task 3: Extract Unique Skills from a List of Employees 53 | You have an `Employee` class: 54 | 55 | ```java 56 | public class Employee { 57 | private String name; 58 | private List skills; 59 | 60 | // Constructor, Getters, Setters 61 | } 62 | ``` 63 | 64 | **Objective:** 65 | 1. Extract all unique skills from a list of employees. 66 | 2. Return them **sorted alphabetically**. 67 | 68 | **Method Signature:** 69 | ```java 70 | public List getUniqueSortedSkills(List employees) 71 | ``` 72 | 73 | **Hint:** Use `flatMap()` and `distinct()`. 74 | 75 | --- 76 | 77 | ## 4. Reduction and Custom Collectors 78 | ### Task 4: Find the Employee with the Highest Salary 79 | You have an `Employee` class: 80 | 81 | ```java 82 | public class Employee { 83 | private String name; 84 | private double salary; 85 | 86 | // Constructor, Getters, Setters 87 | } 88 | ``` 89 | 90 | **Objective:** 91 | - Find the **employee with the highest salary** using **Stream.reduce()**. 92 | 93 | **Method Signature:** 94 | ```java 95 | public Optional findHighestPaidEmployee(List employees) 96 | ``` 97 | 98 | --- 99 | 100 | ## 5. Parallel Streams 101 | ### Task 5: Parallel Processing - Compute Large Prime Numbers 102 | Create a method that: 103 | - Filters out **prime numbers** from a list of `integers`. 104 | - Uses **parallel streams** for performance. 105 | 106 | **Method Signature:** 107 | ```java 108 | public List findPrimeNumbers(List numbers) 109 | ``` 110 | 111 | **Hint:** Use `IntStream.rangeClosed(2, sqrt(n))` to check primality efficiently. 112 | 113 | --- 114 | 115 | ## 6. Custom Collector 116 | ### Task 6: Compute Employee Salary Statistics 117 | Using the same `Employee` class: 118 | - Compute **average**, **min**, and **max salary** using a **custom collector**. 119 | 120 | **Expected Output Example:** 121 | ``` 122 | { 123 | "average": 45000.5, 124 | "min": 32000.0, 125 | "max": 78000.0 126 | } 127 | ``` 128 | 129 | **Method Signature:** 130 | ```java 131 | public Map computeSalaryStatistics(List employees) 132 | ``` 133 | 134 | **Hint:** Use `Collector.of()` for a custom collector. 135 | 136 | --- 137 | 138 | ## 7. Stream-Based File Processing 139 | ### Task 7: Count Word Frequency in a Large File 140 | You have a large text file where each line contains words. Implement a method that: 141 | - Reads the file **line by line** using `Files.lines(Path)`. 142 | - Splits each line into words. 143 | - Counts **word occurrences**. 144 | 145 | **Method Signature:** 146 | ```java 147 | public Map countWordFrequency(Path filePath) 148 | ``` 149 | 150 | **Hint:** Use `Collectors.groupingBy()`. 151 | 152 | --- 153 | 154 | ## 8. Combination of Streams 155 | ### Task 8: Find the Top 3 Most Expensive Products in Each Category 156 | You have a `Product` class: 157 | 158 | ```java 159 | public class Product { 160 | private String name; 161 | private String category; 162 | private double price; 163 | 164 | // Constructor, Getters, Setters 165 | } 166 | ``` 167 | 168 | **Objective:** 169 | - Group products by `category`. 170 | - Sort products **by price descending** within each category. 171 | - Return the **top 3 most expensive** products per category. 172 | 173 | **Method Signature:** 174 | ```java 175 | public Map> topExpensiveProductsByCategory(List products) 176 | ``` 177 | 178 | **Hint:** Use `groupingBy()` and `limit()` inside a `Collector`. 179 | 180 | --- -------------------------------------------------------------------------------- /week03/uni-week03/.gitignore: -------------------------------------------------------------------------------- 1 | ### IntelliJ IDEA ### 2 | out/ 3 | !**/src/main/**/out/ 4 | !**/src/test/**/out/ 5 | 6 | ### Eclipse ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | .sts4-cache 14 | bin/ 15 | !**/src/main/**/bin/ 16 | !**/src/test/**/bin/ 17 | 18 | ### NetBeans ### 19 | /nbproject/private/ 20 | /nbbuild/ 21 | /dist/ 22 | /nbdist/ 23 | /.nb-gradle/ 24 | 25 | ### VS Code ### 26 | .vscode/ 27 | 28 | ### Mac OS ### 29 | .DS_Store -------------------------------------------------------------------------------- /week03/uni-week03/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /week03/uni-week03/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /week03/uni-week03/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /week03/uni-week03/.idea/sonarlint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /week03/uni-week03/src/Main.java: -------------------------------------------------------------------------------- 1 | import controller.InventoryController; 2 | import model.InventoryItem; 3 | import repository.InventoryItemRepository; 4 | import service.InventoryService; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Entry point of the RC Club Inventory Management System. 10 | */ 11 | public class Main { 12 | public static void main(String[] args) { 13 | // Initialize repositories 14 | InventoryItemRepository itemRepository = new InventoryItemRepository(); 15 | 16 | // Initialize services 17 | InventoryService inventoryService = new InventoryService(itemRepository); 18 | 19 | // Initialize controllers 20 | InventoryController inventoryController = new InventoryController(inventoryService); 21 | 22 | System.out.println("🚀 Application started successfully!"); 23 | 24 | // Add Inventory Items 25 | inventoryService.addItem("RC Car", "High-speed remote control car", 5, "S123", "pcs", "Vehicles", true); 26 | inventoryService.addItem("Battery Pack", "Rechargeable battery", 10, "S1231", "pcs", "Accessories", true); 27 | 28 | try { 29 | inventoryService.addItem("Battery Pack Duplicate Serial Number", "Rechargeable battery", 10, "S1231", "pcs", "Accessories", true); 30 | } catch (IllegalArgumentException ex) { 31 | System.out.println(ex); 32 | } 33 | 34 | System.out.println("---------------------------------------"); 35 | System.out.println("✅ Inventory items added successfully!"); 36 | System.out.println("---------------------------------------"); 37 | 38 | // Display All Items 39 | System.out.println("📌 Displaying all inventory items:"); 40 | inventoryController.displayAllItems(); 41 | System.out.println("---------------------------------------"); 42 | 43 | System.out.println("🔄 Updating 'RC Car' quantity to 8..."); 44 | inventoryController.updateItem(1, "RC Car", "High-speed remote control car", 8, "Vehicles", true); 45 | 46 | System.out.println("---------------------------------------"); 47 | System.out.println("📌 Displaying updated inventory items:"); 48 | inventoryController.displayAllItems(); 49 | 50 | System.out.println("---------------------------------------"); 51 | System.out.println("📌 Displaying all low stock items:"); 52 | int threshold = 10; 53 | List lowCost = inventoryController.getLowStockItems(threshold); 54 | lowCost.stream().forEach(System.out::println); 55 | System.out.println("---------------------------------------"); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /week03/uni-week03/src/controller/InventoryController.java: -------------------------------------------------------------------------------- 1 | package controller; 2 | 3 | import model.InventoryItem; 4 | import service.InventoryService; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Controller for inventory operations. 10 | */ 11 | public class InventoryController { 12 | private final InventoryService inventoryService; 13 | 14 | public InventoryController(InventoryService inventoryService) { 15 | this.inventoryService = inventoryService; 16 | } 17 | 18 | /** 19 | * Displays all available inventory items. 20 | */ 21 | public void displayAllItems() { 22 | List items = inventoryService.getAllItems(); 23 | if (items.isEmpty()) { 24 | System.out.println("No inventory items available."); 25 | } else { 26 | items.forEach(item -> System.out.println("Item: " + item.getName() + ", Quantity: " + item.getQuantity())); 27 | } 28 | } 29 | 30 | public List getLowStockItems(int threshold) { 31 | return inventoryService.getLowStockItems(threshold); 32 | } 33 | 34 | public void updateItem(Integer id, String name, String description, int quantity, String category, boolean borrowable) { 35 | boolean success = inventoryService.updateItem(id, name, description, quantity, category, borrowable); 36 | if (success) { 37 | System.out.println("Item updated successfully."); 38 | } else { 39 | System.out.println("Update failed. Item not found."); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /week03/uni-week03/src/model/ClubMember.java: -------------------------------------------------------------------------------- 1 | package model; 2 | 3 | public class ClubMember { 4 | private static int idCounter = 1; 5 | 6 | private Integer id; 7 | private String firstName; 8 | private String lastName; 9 | private String email; 10 | 11 | public ClubMember(String firstName, String lastName, String email) { 12 | this.id = idCounter++; 13 | this.firstName = firstName; 14 | this.lastName = lastName; 15 | this.email = email; 16 | } 17 | 18 | public Integer getId() { 19 | return id; 20 | } 21 | 22 | public String getFirstName() { 23 | return firstName; 24 | } 25 | 26 | public String getLastName() { 27 | return lastName; 28 | } 29 | 30 | public String getEmail() { 31 | return email; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /week03/uni-week03/src/model/InventoryItem.java: -------------------------------------------------------------------------------- 1 | package model; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | public class InventoryItem { 6 | private static int idCounter = 1; 7 | 8 | private Integer id; 9 | private String name; 10 | private String description; 11 | private int quantity; 12 | 13 | private String serialNumber; 14 | private String unitOfMeasurement; 15 | private String category; 16 | private boolean borrowable; 17 | private LocalDateTime addedDate; 18 | 19 | public InventoryItem(String name, String description, int quantity, String serialNumber, String unit, String category, boolean borrowable) { 20 | this.id = idCounter++; 21 | this.name = name; 22 | this.description = description; 23 | this.quantity = quantity; 24 | this.serialNumber = serialNumber; 25 | this.unitOfMeasurement = unit; 26 | this.category = category; 27 | this.borrowable = borrowable; 28 | this.addedDate = LocalDateTime.now(); 29 | } 30 | 31 | public Integer getId() { return id; } 32 | public String getName() { return name; } 33 | public String getDescription() { return description; } 34 | public int getQuantity() { return quantity; } 35 | public String getSerialNumber() { return serialNumber; } 36 | public String getUnitOfMeasurement() { return unitOfMeasurement; } 37 | public String getCategory() { return category; } 38 | public boolean isBorrowable() { return borrowable; } 39 | public LocalDateTime getAddedDate() { return addedDate; } 40 | 41 | public void setName(String name) { this.name = name; } 42 | public void setDescription(String description) { this.description = description; } 43 | public void setQuantity(int quantity) { this.quantity = quantity; } 44 | public void setCategory(String category) { this.category = category; } 45 | public void setBorrowable(boolean borrowable) { this.borrowable = borrowable; } 46 | 47 | @Override 48 | public String toString() { 49 | return "InventoryItem{" + 50 | "id=" + id + 51 | ", name='" + name + '\'' + 52 | ", description='" + description + '\'' + 53 | ", quantity=" + quantity + 54 | ", unitOfMeasurement='" + unitOfMeasurement + '\'' + 55 | ", category='" + category + '\'' + 56 | ", borrowable=" + borrowable + 57 | ", addedDate=" + addedDate + 58 | '}'; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /week03/uni-week03/src/model/Transaction.java: -------------------------------------------------------------------------------- 1 | package model; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | public class Transaction { 6 | private static int idCounter = 1; 7 | 8 | private Integer id; 9 | private ClubMember member; 10 | private InventoryItem item; 11 | private LocalDateTime borrowedDate; 12 | private LocalDateTime dueDate; 13 | private boolean returned; 14 | 15 | public Transaction(ClubMember member, InventoryItem item, int days) { 16 | this.id = idCounter++; 17 | this.member = member; 18 | this.item = item; 19 | this.borrowedDate = LocalDateTime.now(); 20 | this.dueDate = borrowedDate.plusDays(days); 21 | this.returned = false; 22 | } 23 | 24 | public Integer getId() { 25 | return id; 26 | } 27 | 28 | public ClubMember getMember() { 29 | return member; 30 | } 31 | 32 | public InventoryItem getItem() { 33 | return item; 34 | } 35 | 36 | public LocalDateTime getBorrowedDate() { 37 | return borrowedDate; 38 | } 39 | 40 | public LocalDateTime getDueDate() { 41 | return dueDate; 42 | } 43 | 44 | public boolean isReturned() { 45 | return returned; 46 | } 47 | 48 | public void setReturned(boolean returned) { 49 | this.returned = returned; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /week03/uni-week03/src/repository/ClubMemberRepository.java: -------------------------------------------------------------------------------- 1 | package repository; 2 | 3 | import model.ClubMember; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.Optional; 8 | 9 | public class ClubMemberRepository { 10 | private static Map memberTable = new HashMap<>(); 11 | 12 | public void addMember(ClubMember member) { 13 | memberTable.put(member.getId(), member); 14 | } 15 | 16 | public Optional getMemberById(Integer id) { 17 | return Optional.ofNullable(memberTable.get(id)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /week03/uni-week03/src/repository/InventoryItemRepository.java: -------------------------------------------------------------------------------- 1 | package repository; 2 | 3 | import model.InventoryItem; 4 | 5 | import java.util.*; 6 | 7 | /** 8 | * Repository for managing inventory items. 9 | */ 10 | public class InventoryItemRepository { 11 | private static Map itemTable = new HashMap<>(); 12 | 13 | /** 14 | * Adds a new inventory item to the repository. 15 | * @param item The item to be added. 16 | */ 17 | public void addItem(InventoryItem item) { 18 | if (itemTable.values().stream().anyMatch(el -> el.getSerialNumber().equals(item.getSerialNumber()))) { 19 | throw new IllegalArgumentException(String.format("Serial number %s already in DB", item.getSerialNumber())); 20 | } 21 | itemTable.put(item.getId(), item); 22 | } 23 | 24 | /** 25 | * Deletes an inventory item by its ID. 26 | * @param id The ID of the item to be deleted. 27 | * @return true if item was successfully deleted, false otherwise. 28 | */ 29 | public boolean deleteItemById(Integer id) { 30 | return itemTable.remove(id) != null; 31 | } 32 | 33 | /** 34 | * Retrieves an inventory item by its ID. 35 | * @param id The ID of the item. 36 | * @return An Optional containing the item if found. 37 | */ 38 | public Optional getItemById(Integer id) { 39 | return Optional.ofNullable(itemTable.get(id)); 40 | } 41 | 42 | /** 43 | * Returns all inventory items in the repository. 44 | * @return List of all inventory items. 45 | */ 46 | public List getAllItems() { 47 | return new ArrayList<>(itemTable.values()); 48 | } 49 | 50 | /** 51 | * Updates an existing inventory item. 52 | * @param updatedItem The updated item details. 53 | * @return true if update was successful, false otherwise. 54 | */ 55 | public boolean updateItem(InventoryItem updatedItem) { 56 | if (itemTable.containsKey(updatedItem.getId())) { 57 | itemTable.put(updatedItem.getId(), updatedItem); 58 | return true; 59 | } 60 | return false; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /week03/uni-week03/src/repository/TransactionRepository.java: -------------------------------------------------------------------------------- 1 | package repository; 2 | 3 | import model.Transaction; 4 | 5 | import java.time.LocalDateTime; 6 | import java.util.ArrayList; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.stream.Collectors; 11 | 12 | public class TransactionRepository { 13 | private static Map transactionTable = new HashMap<>(); 14 | 15 | public void addTransaction(Transaction transaction) { 16 | transactionTable.put(transaction.getId(), transaction); 17 | } 18 | 19 | public List getAllTransactions() { 20 | return new ArrayList<>(transactionTable.values()); 21 | } 22 | 23 | public List getOverdueTransactions() { 24 | return transactionTable.values().stream() 25 | .filter(tr -> !tr.isReturned() && tr.getDueDate().isBefore(LocalDateTime.now())) 26 | .collect(Collectors.toList()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /week03/uni-week03/src/service/InventoryService.java: -------------------------------------------------------------------------------- 1 | package service; 2 | 3 | import model.InventoryItem; 4 | import repository.InventoryItemRepository; 5 | 6 | import java.util.List; 7 | import java.util.Optional; 8 | import java.util.stream.Collectors; 9 | 10 | /** 11 | * Service layer for handling inventory operations. 12 | */ 13 | public class InventoryService { 14 | private final InventoryItemRepository itemRepository; 15 | 16 | public InventoryService(InventoryItemRepository itemRepository) { 17 | this.itemRepository = itemRepository; 18 | } 19 | 20 | /** 21 | * Retrieves all inventory items. 22 | * @return List of all items in inventory. 23 | */ 24 | public List getAllItems() { 25 | return itemRepository.getAllItems(); 26 | } 27 | 28 | /** 29 | * Adds a new item to the inventory. 30 | * @param name The name of the item. 31 | * @param description The item's description. 32 | * @param quantity The quantity available. 33 | * @param unit The unit of measurement. 34 | * @param category The category of the item. 35 | * @param borrowable Whether the item can be borrowed. 36 | */ 37 | public void addItem(String name, String description, int quantity, String serialNumber, String unit, String category, boolean borrowable) { 38 | InventoryItem item = new InventoryItem(name, description, quantity, serialNumber, unit, category, borrowable); 39 | itemRepository.addItem(item); 40 | } 41 | 42 | /** 43 | * Retrieves all inventory items that are low in stock. 44 | * @param threshold The threshold quantity to determine low stock. 45 | * @return List of inventory items below the threshold. 46 | */ 47 | public List getLowStockItems(int threshold) { 48 | return itemRepository.getAllItems().stream() 49 | .filter(item -> item.getQuantity() < threshold) 50 | .collect(Collectors.toList()); 51 | } 52 | 53 | /** 54 | * Updates an existing inventory item. 55 | * @param id The ID of the item to update. 56 | * @param name New name. 57 | * @param description New description. 58 | * @param quantity New quantity. 59 | * @param category New category. 60 | * @param borrowable Updated borrowable status. 61 | * @return true if updated successfully, false otherwise. 62 | */ 63 | public boolean updateItem(Integer id, String name, String description, int quantity, String category, boolean borrowable) { 64 | Optional existingItem = itemRepository.getItemById(id); 65 | if (existingItem.isPresent()) { 66 | InventoryItem item = existingItem.get(); 67 | item.setName(name); 68 | item.setDescription(description); 69 | item.setQuantity(quantity); 70 | item.setCategory(category); 71 | item.setBorrowable(borrowable); 72 | return itemRepository.updateItem(item); 73 | } 74 | return false; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /week03/uni-week03/uni-week03.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /week04/basic-guide.md: -------------------------------------------------------------------------------- 1 | # Basic Guide to Spring/Spring Boot/Maven 2 | 3 | ## Spring and Spring Boot 4 | 5 | Full read: [baeldung-resource](https://www.baeldung.com/spring-vs-spring-boot) 6 | 7 | ### Spring 8 | The Spring framework provides comprehensive infrastructure support for developing Java applications. 9 | 10 | It’s packed features like Dependency Injection, and out of the box modules like: 11 | 12 | - **Spring JDBC**: Simplifies database access and error handling. It provides a template pattern approach to interact with a database, reducing the need for boilerplate code. 13 | 14 | - **Spring MVC**: A framework for building web applications in Java. It follows the Model-View-Controller design pattern and offers features for developing web pages, RESTful web services, and more. 15 | 16 | - **Spring Security**: Provides comprehensive security services for Java applications. It offers a wide range of authentication and authorization features to secure applications. 17 | 18 | - **Spring AOP** (Aspect-Oriented Programming): Allows the separation of cross-cutting concerns (such as logging, transaction management) from the business logic, making the code cleaner and more maintainable. 19 | 20 | - **Spring ORM** (Object-Relational Mapping): Integrates with ORM frameworks like Hibernate, JPA, and JDO. It simplifies the data access process while working with databases in object-oriented programming languages. 21 | 22 | - **Spring Test**: Offers testing support for Spring components. It simplifies the process of writing unit and integration tests by providing mock objects and testing frameworks integration. 23 | 24 | ## Spring Bean 25 | 26 | A **Spring Bean** is an object that is instantiated, assembled, and otherwise managed by the Spring IoC (Inversion of Control) container. At its core, it's a component of your application that Spring is aware of and manages throughout its lifecycle. Here’s a quick breakdown: 27 | 28 | Additional information -> [spring-bean](https://www.baeldung.com/spring-bean) 29 | 30 | ### Instantiation 31 | 32 | Spring creates instances of classes that are defined as beans. This creation process is typically configured in a Spring configuration file using XML or through annotations in the code. 33 | 34 | ### Assembly 35 | 36 | Spring injects dependencies into the beans as needed. A dependency is another object that a bean requires to perform its tasks. For instance, a `UserService` bean might depend on a `UserRepository` bean to retrieve user information. 37 | 38 | ### Management 39 | 40 | After beans are created and assembled, Spring takes care of their entire lifecycle. This includes instantiating bean objects, invoking initialization methods, and destroying them when necessary. 41 | 42 | Beans are defined in the Spring configuration file (XML) or via annotations. They can be accessed and manipulated through the Spring application context. Central to the Spring framework, beans enable the construction of an application in an interchangeable and reusable manner, forming the application's backbone. 43 | 44 | ## Spring Boot 45 | Spring Boot is basically an extension of the Spring framework, which eliminates the boilerplate configurations required for setting up a Spring application. 46 | 47 | *It takes an opinionated view of the Spring platform, which paves the way for a faster and more efficient development ecosystem.* 48 | 49 | Features 50 | - Create stand-alone Spring applications 51 | - Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files) 52 | - Provide opinionated 'starter' dependencies to simplify your build configuration 53 | - Automatically configure Spring and 3rd party libraries whenever possible 54 | - Provide production-ready features such as metrics, health checks, and externalized configuration 55 | - Absolutely no code generation and no requirement for XML configuration 56 | 57 | Spring Boot provides a number of starter dependencies for different Spring modules. Some of the most commonly used ones are: 58 | 59 | - spring-boot-starter-data-jpa 60 | - spring-boot-starter-security 61 | - spring-boot-starter-test 62 | - spring-boot-starter-web 63 | 64 | ## Maven Explained 65 | 66 | Apache Maven is a comprehensive project management tool used primarily for Java projects. It automates the process of building and managing projects, adhering to the principle of convention over configuration. Maven simplifies tasks such as compiling code, packaging binaries, and managing dependencies through the use of a Project Object Model (POM) file, typically named `pom.xml`. 67 | 68 | ### Build Lifecycle 69 | 70 | Maven organizes the build process into a series of phases known as a build lifecycle. Key lifecycles include: 71 | 72 | - `validate` - validate the project is correct and all necessary information is available 73 | - `compile` - compile the source code of the project 74 | - `test` - test the compiled source code using a suitable unit testing framework. These tests should not require the code be packaged or deployed 75 | - `package` - take the compiled code and package it in its distributable format, such as a JAR. 76 | - `verify` - run any checks on results of integration tests to ensure quality criteria are met 77 | - `install` - install the package into the local repository, for use as a dependency in other projects locally 78 | - `deploy` - done in the build environment, copies the final package to the remote repository for sharing with other developers and projects. 79 | 80 | Each lifecycle is made up of phases, for example, the default lifecycle includes `compile`, `test`, `package`, and more. 81 | 82 | ### Goals 83 | 84 | Goals are specific tasks within a build phase, such as compiling code or creating a JAR file. Executing a Maven phase triggers all goals associated with that phase. 85 | 86 | ### Project Object Model (POM) 87 | 88 | The `pom.xml` file is at the heart of a project's Maven configuration. It contains project information and configuration details for Maven to build the project. Essential elements of a POM file include dependencies, repositories, plugins, and profiles. 89 | 90 | #### Dependencies 91 | 92 | Dependencies are external Java libraries required by the project. Each dependency is identified by its `groupId`, `artifactId`, and `version`. 93 | 94 | ```xml 95 | 96 | 97 | com.example 98 | example-library 99 | 1.0.0 100 | 101 | 102 | ``` 103 | 104 | #### Repositories 105 | 106 | Repositories are sources from which Maven can download dependencies. If a dependency is not present in the local repository, Maven will attempt to download it from a configured remote repository. 107 | 108 | ```xml 109 | 110 | 111 | example-repo 112 | http://example.com/maven2 113 | 114 | 115 | ``` 116 | 117 | #### Dependency Scopes 118 | 119 | Dependency scopes define the classpath visibility and lifecycle of a dependency: 120 | 121 | - **compile**: Default scope, available on all classpaths. 122 | - **provided**: Expected to be provided by the JDK or a container at runtime. 123 | - **runtime**: Required for execution but not for compilation. 124 | - **test**: Only available for the test compilation and execution. 125 | - **system**: Similar to provided but the JAR must be explicitly provided. 126 | - **import**: Used in a dependency of type `pom` to import dependency management information. 127 | 128 | #### Versioning 129 | 130 | Maven supports several strategies for specifying dependency versions: 131 | 132 | - **Fixed Version**: Specifies an exact version. 133 | - **Version Ranges**: Allows the use of a range of versions. 134 | - **Snapshot Versions**: Development versions that can be updated with newer snapshots. 135 | 136 | ### Profiles 137 | 138 | Profiles enable customization of the build for different environments or configurations. They can override settings, dependencies, and plugins for specific scenarios. 139 | 140 | ```xml 141 | 142 | 143 | development 144 | 145 | true 146 | 147 | 148 | 149 | 150 | 151 | 152 | production 153 | 154 | 155 | 156 | ``` 157 | 158 | Maven's structure and its `pom.xml` configuration file provide a robust and flexible framework for managing project builds, dependencies, and documentation across different environments and stages of development. -------------------------------------------------------------------------------- /week04/inventorysystem/HELP.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ### Reference Documentation 4 | For further reference, please consider the following sections: 5 | 6 | * [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) 7 | * [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/3.4.3/maven-plugin) 8 | * [Create an OCI image](https://docs.spring.io/spring-boot/3.4.3/maven-plugin/build-image.html) 9 | 10 | ### Maven Parent overrides 11 | 12 | Due to Maven's design, elements are inherited from the parent POM to the project POM. 13 | While most of the inheritance is fine, it also inherits unwanted elements like `` and `` from the parent. 14 | To prevent this, the project POM contains empty overrides for these elements. 15 | If you manually switch to a different parent and actually want the inheritance, you need to remove those overrides. 16 | 17 | -------------------------------------------------------------------------------- /week04/inventorysystem/mvnw.cmd: -------------------------------------------------------------------------------- 1 | <# : batch portion 2 | @REM ---------------------------------------------------------------------------- 3 | @REM Licensed to the Apache Software Foundation (ASF) under one 4 | @REM or more contributor license agreements. See the NOTICE file 5 | @REM distributed with this work for additional information 6 | @REM regarding copyright ownership. The ASF licenses this file 7 | @REM to you under the Apache License, Version 2.0 (the 8 | @REM "License"); you may not use this file except in compliance 9 | @REM with the License. You may obtain a copy of the License at 10 | @REM 11 | @REM http://www.apache.org/licenses/LICENSE-2.0 12 | @REM 13 | @REM Unless required by applicable law or agreed to in writing, 14 | @REM software distributed under the License is distributed on an 15 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | @REM KIND, either express or implied. See the License for the 17 | @REM specific language governing permissions and limitations 18 | @REM under the License. 19 | @REM ---------------------------------------------------------------------------- 20 | 21 | @REM ---------------------------------------------------------------------------- 22 | @REM Apache Maven Wrapper startup batch script, version 3.3.2 23 | @REM 24 | @REM Optional ENV vars 25 | @REM MVNW_REPOURL - repo url base for downloading maven distribution 26 | @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 27 | @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output 28 | @REM ---------------------------------------------------------------------------- 29 | 30 | @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) 31 | @SET __MVNW_CMD__= 32 | @SET __MVNW_ERROR__= 33 | @SET __MVNW_PSMODULEP_SAVE=%PSModulePath% 34 | @SET PSModulePath= 35 | @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( 36 | IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) 37 | ) 38 | @SET PSModulePath=%__MVNW_PSMODULEP_SAVE% 39 | @SET __MVNW_PSMODULEP_SAVE= 40 | @SET __MVNW_ARG0_NAME__= 41 | @SET MVNW_USERNAME= 42 | @SET MVNW_PASSWORD= 43 | @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) 44 | @echo Cannot start maven from wrapper >&2 && exit /b 1 45 | @GOTO :EOF 46 | : end batch / begin powershell #> 47 | 48 | $ErrorActionPreference = "Stop" 49 | if ($env:MVNW_VERBOSE -eq "true") { 50 | $VerbosePreference = "Continue" 51 | } 52 | 53 | # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties 54 | $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl 55 | if (!$distributionUrl) { 56 | Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" 57 | } 58 | 59 | switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { 60 | "maven-mvnd-*" { 61 | $USE_MVND = $true 62 | $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" 63 | $MVN_CMD = "mvnd.cmd" 64 | break 65 | } 66 | default { 67 | $USE_MVND = $false 68 | $MVN_CMD = $script -replace '^mvnw','mvn' 69 | break 70 | } 71 | } 72 | 73 | # apply MVNW_REPOURL and calculate MAVEN_HOME 74 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 75 | if ($env:MVNW_REPOURL) { 76 | $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } 77 | $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" 78 | } 79 | $distributionUrlName = $distributionUrl -replace '^.*/','' 80 | $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' 81 | $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" 82 | if ($env:MAVEN_USER_HOME) { 83 | $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" 84 | } 85 | $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' 86 | $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" 87 | 88 | if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { 89 | Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" 90 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 91 | exit $? 92 | } 93 | 94 | if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { 95 | Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" 96 | } 97 | 98 | # prepare tmp dir 99 | $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile 100 | $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" 101 | $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null 102 | trap { 103 | if ($TMP_DOWNLOAD_DIR.Exists) { 104 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 105 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 106 | } 107 | } 108 | 109 | New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null 110 | 111 | # Download and Install Apache Maven 112 | Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 113 | Write-Verbose "Downloading from: $distributionUrl" 114 | Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 115 | 116 | $webclient = New-Object System.Net.WebClient 117 | if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { 118 | $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) 119 | } 120 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 121 | $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null 122 | 123 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 124 | $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum 125 | if ($distributionSha256Sum) { 126 | if ($USE_MVND) { 127 | Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." 128 | } 129 | Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash 130 | if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { 131 | Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." 132 | } 133 | } 134 | 135 | # unzip and move 136 | Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null 137 | Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null 138 | try { 139 | Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null 140 | } catch { 141 | if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { 142 | Write-Error "fail to move MAVEN_HOME" 143 | } 144 | } finally { 145 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 146 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 147 | } 148 | 149 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 150 | -------------------------------------------------------------------------------- /week04/inventorysystem/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.4.3 9 | 10 | 11 | bg.fmi.uni 12 | inventorysystem 13 | 0.0.1-SNAPSHOT 14 | inventorysystem 15 | Demo project for Spring Boot 16 | 17 | 17 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter 23 | 24 | 25 | 26 | org.projectlombok 27 | lombok 28 | 1.18.36 29 | provided 30 | 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-test 35 | test 36 | 37 | 38 | 39 | 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-maven-plugin 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /week04/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/InventorysystemApplication.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem; 2 | 3 | import bg.fmi.uni.inventorysystem.controller.InventoryController; 4 | import bg.fmi.uni.inventorysystem.model.InventoryItem; 5 | import bg.fmi.uni.inventorysystem.service.InventoryService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.CommandLineRunner; 8 | import org.springframework.boot.SpringApplication; 9 | import org.springframework.boot.autoconfigure.SpringBootApplication; 10 | 11 | import java.util.List; 12 | 13 | @SpringBootApplication 14 | public class InventorysystemApplication implements CommandLineRunner { 15 | 16 | @Autowired 17 | private InventoryService inventoryService; 18 | 19 | @Autowired 20 | private InventoryController inventoryController; 21 | 22 | public static void main(String[] args) { 23 | SpringApplication.run(InventorysystemApplication.class, args); 24 | } 25 | 26 | @Override 27 | public void run(String... args) throws Exception { 28 | System.out.println("🚀 Application started successfully!"); 29 | 30 | // Add Inventory Items 31 | inventoryService.addItem("RC Car", "High-speed remote control car", 5, "S123", "pcs", "Vehicles", true); 32 | inventoryService.addItem("Battery Pack", "Rechargeable battery", 10, "S1231", "pcs", "Accessories", true); 33 | 34 | try { 35 | inventoryService.addItem("Battery Pack Duplicate Serial Number", "Rechargeable battery", 10, "S1231", "pcs", "Accessories", true); 36 | } catch (IllegalArgumentException ex) { 37 | System.out.println(ex); 38 | } 39 | 40 | System.out.println("---------------------------------------"); 41 | System.out.println("✅ Inventory items added successfully!"); 42 | System.out.println("---------------------------------------"); 43 | 44 | // Display All Items 45 | System.out.println("📌 Displaying all inventory items:"); 46 | inventoryController.displayAllItems(); 47 | System.out.println("---------------------------------------"); 48 | 49 | System.out.println("🔄 Updating 'RC Car' quantity to 8..."); 50 | inventoryController.updateItem(1, "RC Car", "High-speed remote control car", 8, "Vehicles", true); 51 | 52 | System.out.println("---------------------------------------"); 53 | System.out.println("📌 Displaying updated inventory items:"); 54 | inventoryController.displayAllItems(); 55 | 56 | System.out.println("---------------------------------------"); 57 | System.out.println("📌 Displaying all low stock items:"); 58 | int threshold = 10; 59 | List lowCost = inventoryController.getLowStockItems(threshold); 60 | lowCost.stream().forEach(System.out::println); 61 | System.out.println("---------------------------------------"); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /week04/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/controller/InventoryController.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.controller; 2 | 3 | 4 | import bg.fmi.uni.inventorysystem.model.InventoryItem; 5 | import bg.fmi.uni.inventorysystem.service.InventoryService; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.stereotype.Controller; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * Controller for inventory operations. 13 | */ 14 | @Component 15 | public class InventoryController { 16 | private final InventoryService inventoryService; 17 | 18 | public InventoryController(InventoryService inventoryService) { 19 | this.inventoryService = inventoryService; 20 | } 21 | 22 | /** 23 | * Displays all available inventory items. 24 | */ 25 | public void displayAllItems() { 26 | List items = inventoryService.getAllItems(); 27 | if (items.isEmpty()) { 28 | System.out.println("No inventory items available."); 29 | } else { 30 | items.forEach(item -> System.out.println("Item: " + item.getName() + ", Quantity: " + item.getQuantity())); 31 | } 32 | } 33 | 34 | public List getLowStockItems(int threshold) { 35 | return inventoryService.getLowStockItems(threshold); 36 | } 37 | 38 | public void updateItem(Integer id, String name, String description, int quantity, String category, boolean borrowable) { 39 | boolean success = inventoryService.updateItem(id, name, description, quantity, category, borrowable); 40 | if (success) { 41 | System.out.println("Item updated successfully."); 42 | } else { 43 | System.out.println("Update failed. Item not found."); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /week04/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/model/ClubMember.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.model; 2 | 3 | public class ClubMember { 4 | private static int idCounter = 1; 5 | 6 | private Integer id; 7 | private String firstName; 8 | private String lastName; 9 | private String email; 10 | 11 | public ClubMember(String firstName, String lastName, String email) { 12 | this.id = idCounter++; 13 | this.firstName = firstName; 14 | this.lastName = lastName; 15 | this.email = email; 16 | } 17 | 18 | public Integer getId() { 19 | return id; 20 | } 21 | 22 | public String getFirstName() { 23 | return firstName; 24 | } 25 | 26 | public String getLastName() { 27 | return lastName; 28 | } 29 | 30 | public String getEmail() { 31 | return email; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /week04/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/model/InventoryItem.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.model; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | public class InventoryItem { 6 | private static int idCounter = 1; 7 | 8 | private Integer id; 9 | private String name; 10 | private String description; 11 | private int quantity; 12 | 13 | private String serialNumber; 14 | private String unitOfMeasurement; 15 | private String category; 16 | private boolean borrowable; 17 | private LocalDateTime addedDate; 18 | 19 | public InventoryItem(String name, String description, int quantity, String serialNumber, String unit, String category, boolean borrowable) { 20 | this.id = idCounter++; 21 | this.name = name; 22 | this.description = description; 23 | this.quantity = quantity; 24 | this.serialNumber = serialNumber; 25 | this.unitOfMeasurement = unit; 26 | this.category = category; 27 | this.borrowable = borrowable; 28 | this.addedDate = LocalDateTime.now(); 29 | } 30 | 31 | public Integer getId() { return id; } 32 | public String getName() { return name; } 33 | public String getDescription() { return description; } 34 | public int getQuantity() { return quantity; } 35 | public String getSerialNumber() { return serialNumber; } 36 | public String getUnitOfMeasurement() { return unitOfMeasurement; } 37 | public String getCategory() { return category; } 38 | public boolean isBorrowable() { return borrowable; } 39 | public LocalDateTime getAddedDate() { return addedDate; } 40 | 41 | public void setName(String name) { this.name = name; } 42 | public void setDescription(String description) { this.description = description; } 43 | public void setQuantity(int quantity) { this.quantity = quantity; } 44 | public void setCategory(String category) { this.category = category; } 45 | public void setBorrowable(boolean borrowable) { this.borrowable = borrowable; } 46 | 47 | @Override 48 | public String toString() { 49 | return "InventoryItem{" + 50 | "id=" + id + 51 | ", name='" + name + '\'' + 52 | ", description='" + description + '\'' + 53 | ", quantity=" + quantity + 54 | ", unitOfMeasurement='" + unitOfMeasurement + '\'' + 55 | ", category='" + category + '\'' + 56 | ", borrowable=" + borrowable + 57 | ", addedDate=" + addedDate + 58 | '}'; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /week04/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/model/Transaction.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.model; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | public class Transaction { 6 | private static int idCounter = 1; 7 | 8 | private Integer id; 9 | private ClubMember member; 10 | private InventoryItem item; 11 | private LocalDateTime borrowedDate; 12 | private LocalDateTime dueDate; 13 | private boolean returned; 14 | 15 | public Transaction(ClubMember member, InventoryItem item, int days) { 16 | this.id = idCounter++; 17 | this.member = member; 18 | this.item = item; 19 | this.borrowedDate = LocalDateTime.now(); 20 | this.dueDate = borrowedDate.plusDays(days); 21 | this.returned = false; 22 | } 23 | 24 | public Integer getId() { 25 | return id; 26 | } 27 | 28 | public ClubMember getMember() { 29 | return member; 30 | } 31 | 32 | public InventoryItem getItem() { 33 | return item; 34 | } 35 | 36 | public LocalDateTime getBorrowedDate() { 37 | return borrowedDate; 38 | } 39 | 40 | public LocalDateTime getDueDate() { 41 | return dueDate; 42 | } 43 | 44 | public boolean isReturned() { 45 | return returned; 46 | } 47 | 48 | public void setReturned(boolean returned) { 49 | this.returned = returned; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /week04/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/repository/ClubMemberRepository.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.repository; 2 | 3 | import bg.fmi.uni.inventorysystem.model.ClubMember; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.Optional; 9 | 10 | @Repository 11 | public class ClubMemberRepository { 12 | private static Map memberTable = new HashMap<>(); 13 | 14 | public void addMember(ClubMember member) { 15 | memberTable.put(member.getId(), member); 16 | } 17 | 18 | public Optional getMemberById(Integer id) { 19 | return Optional.ofNullable(memberTable.get(id)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /week04/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/repository/InventoryItemRepository.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.repository; 2 | 3 | import bg.fmi.uni.inventorysystem.model.InventoryItem; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.*; 8 | 9 | /** 10 | * Repository for managing inventory items. 11 | */ 12 | @Slf4j 13 | @Repository 14 | public class InventoryItemRepository { 15 | private static Map itemTable = new HashMap<>(); 16 | 17 | /** 18 | * Adds a new inventory item to the repository. 19 | * @param item The item to be added. 20 | */ 21 | public void addItem(InventoryItem item) { 22 | log.info("alabala"); 23 | if (itemTable.values().stream().anyMatch(el -> el.getSerialNumber().equals(item.getSerialNumber()))) { 24 | throw new IllegalArgumentException(String.format("Serial number %s already in DB", item.getSerialNumber())); 25 | } 26 | itemTable.put(item.getId(), item); 27 | } 28 | 29 | /** 30 | * Deletes an inventory item by its ID. 31 | * @param id The ID of the item to be deleted. 32 | * @return true if item was successfully deleted, false otherwise. 33 | */ 34 | public boolean deleteItemById(Integer id) { 35 | return itemTable.remove(id) != null; 36 | } 37 | 38 | /** 39 | * Retrieves an inventory item by its ID. 40 | * @param id The ID of the item. 41 | * @return An Optional containing the item if found. 42 | */ 43 | public Optional getItemById(Integer id) { 44 | return Optional.ofNullable(itemTable.get(id)); 45 | } 46 | 47 | /** 48 | * Returns all inventory items in the repository. 49 | * @return List of all inventory items. 50 | */ 51 | public List getAllItems() { 52 | return new ArrayList<>(itemTable.values()); 53 | } 54 | 55 | /** 56 | * Updates an existing inventory item. 57 | * @param updatedItem The updated item details. 58 | * @return true if update was successful, false otherwise. 59 | */ 60 | public boolean updateItem(InventoryItem updatedItem) { 61 | if (itemTable.containsKey(updatedItem.getId())) { 62 | itemTable.put(updatedItem.getId(), updatedItem); 63 | return true; 64 | } 65 | return false; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /week04/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/repository/TransactionRepository.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.repository; 2 | 3 | import bg.fmi.uni.inventorysystem.model.Transaction; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.ArrayList; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.stream.Collectors; 12 | 13 | @Repository 14 | public class TransactionRepository { 15 | private static Map transactionTable = new HashMap<>(); 16 | 17 | public void addTransaction(Transaction transaction) { 18 | transactionTable.put(transaction.getId(), transaction); 19 | } 20 | 21 | public List getAllTransactions() { 22 | return new ArrayList<>(transactionTable.values()); 23 | } 24 | 25 | public List getOverdueTransactions() { 26 | return transactionTable.values().stream() 27 | .filter(tr -> !tr.isReturned() && tr.getDueDate().isBefore(LocalDateTime.now())) 28 | .collect(Collectors.toList()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /week04/inventorysystem/src/main/java/bg/fmi/uni/inventorysystem/service/InventoryService.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.service; 2 | 3 | 4 | import bg.fmi.uni.inventorysystem.model.InventoryItem; 5 | import bg.fmi.uni.inventorysystem.repository.InventoryItemRepository; 6 | import lombok.RequiredArgsConstructor; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.List; 12 | import java.util.Optional; 13 | import java.util.stream.Collectors; 14 | 15 | /** 16 | * Service layer for handling inventory operations. 17 | */ 18 | @Slf4j 19 | @Service 20 | @RequiredArgsConstructor 21 | public class InventoryService { 22 | private final InventoryItemRepository itemRepository; 23 | 24 | /** 25 | * Retrieves all inventory items. 26 | * @return List of all items in inventory. 27 | */ 28 | public List getAllItems() { 29 | return itemRepository.getAllItems(); 30 | } 31 | 32 | /** 33 | * Adds a new item to the inventory. 34 | * @param name The name of the item. 35 | * @param description The item's description. 36 | * @param quantity The quantity available. 37 | * @param unit The unit of measurement. 38 | * @param category The category of the item. 39 | * @param borrowable Whether the item can be borrowed. 40 | */ 41 | public void addItem(String name, String description, int quantity, String serialNumber, String unit, String category, boolean borrowable) { 42 | InventoryItem item = new InventoryItem(name, description, quantity, serialNumber, unit, category, borrowable); 43 | itemRepository.addItem(item); 44 | } 45 | 46 | /** 47 | * Retrieves all inventory items that are low in stock. 48 | * @param threshold The threshold quantity to determine low stock. 49 | * @return List of inventory items below the threshold. 50 | */ 51 | public List getLowStockItems(int threshold) { 52 | return itemRepository.getAllItems().stream() 53 | .filter(item -> item.getQuantity() < threshold) 54 | .collect(Collectors.toList()); 55 | } 56 | 57 | /** 58 | * Updates an existing inventory item. 59 | * @param id The ID of the item to update. 60 | * @param name New name. 61 | * @param description New description. 62 | * @param quantity New quantity. 63 | * @param category New category. 64 | * @param borrowable Updated borrowable status. 65 | * @return true if updated successfully, false otherwise. 66 | */ 67 | public boolean updateItem(Integer id, String name, String description, int quantity, String category, boolean borrowable) { 68 | Optional existingItem = itemRepository.getItemById(id); 69 | if (existingItem.isPresent()) { 70 | InventoryItem item = existingItem.get(); 71 | item.setName(name); 72 | item.setDescription(description); 73 | item.setQuantity(quantity); 74 | item.setCategory(category); 75 | item.setBorrowable(borrowable); 76 | return itemRepository.updateItem(item); 77 | } 78 | return false; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /week04/inventorysystem/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=inventorysystem 2 | -------------------------------------------------------------------------------- /week04/inventorysystem/src/test/java/bg/fmi/uni/inventorysystem/InventorysystemApplicationTests.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class InventorysystemApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /week04/inventorysystem/target/classes/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=inventorysystem 2 | -------------------------------------------------------------------------------- /week04/spring-initializr-tutorial.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Spring Initializr 2 | 3 | Spring Initializr is a convenient web-based tool provided by the Spring team to quickly bootstrap new Spring projects. It allows you to generate a project structure with your chosen dependencies, packaging, Java version, and more. Here's how you can use it: 4 | 5 | ## Step 1: Open Spring Initializr 6 | 7 | Navigate to [Spring Initializr's website](https://start.spring.io/) in your web browser. 8 | 9 | ## Step 2: Project Metadata 10 | 11 | Fill in the project metadata fields: 12 | 13 | - **Project**: Choose the type of project. Spring Boot supports Maven and Gradle as build tools. 14 | - **Language**: Select the programming language (Java, Kotlin, or Groovy) for your project. 15 | - **Spring Boot**: Choose the version of Spring Boot you want to use. It's generally recommended to use the latest stable release. 16 | - **Group**: Enter your organization's group ID (similar to a package name, e.g., `com.example`). 17 | - **Artifact**: Define the name of your project (e.g., `demo`). 18 | - **Name**: The name of your application, which also becomes the default name for the generated project directory. 19 | - **Description**: A brief description of your project. 20 | - **Package name**: The base package name under which your classes will be generated (auto-filled based on your Group and Artifact). 21 | - **Packaging**: Choose between Jar (Java Archive) and War (Web Application Archive). Jar is more common for microservices. 22 | - **Java**: Select the Java version your project will use. 23 | 24 | ## Step 3: Project Dependencies 25 | 26 | Click on "Add Dependencies" to open a dialog where you can search for and add the necessary Spring Boot Starter dependencies for your project. Common starters include: 27 | 28 | - **Spring Web**: For building web and RESTful applications. 29 | - **Spring Data JPA**: To integrate Spring applications with Java Persistence API (JPA) data sources. 30 | - **Spring Security**: For authentication and access control. 31 | - **Thymeleaf**: For server-side Java templating. 32 | 33 | ## Step 4: Generate the Project 34 | 35 | Once you've configured your project's settings and dependencies: 36 | 37 | 1. Click on "Generate" at the bottom of the page. 38 | 2. This will download a `.zip` file containing your project skeleton. 39 | 3. Extract the `.zip` file to your local development environment. 40 | 41 | ## Step 5: Open and Run Your Project 42 | 43 | 1. Open the extracted project folder in your favorite IDE (e.g., IntelliJ IDEA, Eclipse, Visual Studio Code). 44 | 2. If your IDE supports Spring Boot, it will automatically recognize the project structure. 45 | 3. Run the application by executing the `main` method in the `Application` class. 46 | 47 | You should now have a running Spring Boot application! 48 | 49 | ## Next Steps 50 | 51 | - Explore the project structure and the generated `pom.xml` or `build.gradle` files to understand how dependencies are managed. 52 | - Start adding your business logic, controllers, data models, and other components to build your application. 53 | - Consult the [Spring Boot documentation](https://docs.spring.io/spring-boot/docs/current/reference/html/) for guidance on further customization and advanced features. 54 | 55 | Spring Initializr provides a quick and easy way to generate a project structure, letting you focus on implementing your application's functionality right away. -------------------------------------------------------------------------------- /week04/tasks.md: -------------------------------------------------------------------------------- 1 | # Inventory Management System - Spring Boot Integration 2 | 3 | ## Task 1 - migrate your Inventory Management System to a Spring Boot application 4 | 1. To achieve this task you will need to create a Spring Boot application with IntelliJ Ultimate or [spring-initializr](https://start.spring.io/) to create a Spring Boot application. 5 | 6 |
  7 | If you are having trouble with the starter you can check the following [documentation](https://github.com/dreamix-fmi-course-2025/web-development-with-java/tree/main/week04/spring-initializr-tutorial.md)
  8 | 
9 | 10 |
 11 | In case you need additional information about Spring/Spring Boot/Maven, use the following [documentation](https://github.com/dreamix-fmi-course-2025/web-development-with-java/tree/main/week04/basic-guide.md)
 12 | 
13 | 14 | 15 | 2. Create the proper Bean definition and connect all project dependencies 16 |
17 | Hints 18 | 19 | ```md 20 | - make use of @Autowired and @Component annotation 21 | - Connect the respectful classes (e.g Service is containing Repository, Service can contain one or more Services) 22 | ``` 23 | 24 |
25 | 26 | If you don't have the code for the InventoryManagementSystem project you can use the code from [week03](https://github.com/dreamix-fmi-course-2025/web-development-with-java/tree/main/week03/uni-week03). 27 | For the purpose of this lab you can start only with the Racer domain logic (model, repository and service) 28 | 29 | ## Task 2 - showcase the usage of the beans in together with CommandLineRunner interface 30 | For this test we will use the InventoryService and InventoryController to showcase the autowire and the usage of beans (and as it's funtionalities are simple to manipulate). 31 | 32 | In your main class implement the `CommandLineRunner` interface. This will give you the abbility to play with your Spring Boot application 33 | 34 | 1. Add Inventory Items 35 | 2. Display all items. 36 | 3. Update Item with ID 1 37 | 4. Display all low stock items 38 | Example: 39 | ```java 40 | @Override 41 | public void run(String... args) throws Exception { 42 | 43 | System.out.println("🚀 Application started successfully!"); 44 | 45 | // Add Inventory Items 46 | inventoryService.addItem("RC Car", "High-speed remote control car", 5, "S123", "pcs", "Vehicles", true); 47 | inventoryService.addItem("Battery Pack", "Rechargeable battery", 10, "S1231", "pcs", "Accessories", true); 48 | 49 | try { 50 | inventoryService.addItem("Battery Pack Duplicate Serial Number", "Rechargeable battery", 10, "S1231", "pcs", "Accessories", true); 51 | } catch (IllegalArgumentException ex) { 52 | System.out.println(ex); 53 | } 54 | 55 | System.out.println("---------------------------------------"); 56 | System.out.println("✅ Inventory items added successfully!"); 57 | System.out.println("---------------------------------------"); 58 | 59 | // Display All Items 60 | System.out.println("📌 Displaying all inventory items:"); 61 | inventoryController.displayAllItems(); 62 | System.out.println("---------------------------------------"); 63 | 64 | System.out.println("🔄 Updating 'RC Car' quantity to 8..."); 65 | inventoryController.updateItem(1, "RC Car", "High-speed remote control car", 8, "Vehicles", true); 66 | 67 | System.out.println("---------------------------------------"); 68 | System.out.println("📌 Displaying updated inventory items:"); 69 | inventoryController.displayAllItems(); 70 | 71 | System.out.println("---------------------------------------"); 72 | System.out.println("📌 Displaying all low stock items:"); 73 | int threshold = 10; 74 | List lowCost = inventoryController.getLowStockItems(threshold); 75 | lowCost.stream().forEach(System.out::println); 76 | System.out.println("---------------------------------------"); 77 | } 78 | 79 | ``` 80 | 5. Use ApplicationContext to print all loaded beans 81 | 82 |
83 | Hints 84 | 85 | ```java 86 | @SpringBootApplication 87 | public class ClubManagementSystemApplication implements CommandLineRunner { 88 | @Autowired 89 | private InventoryController inventoryController; 90 | 91 | @Autowired 92 | private InventoryService inventoryService; 93 | 94 | @Autowired 95 | private ApplicationContext context; 96 | 97 | public static void main(String[] args) { 98 | SpringApplication.run(ClubManagementSystemApplication.class, args); 99 | } 100 | 101 | @Override 102 | public void run(String... args) throws Exception { 103 | 104 | System.out.println("🚀 Application started successfully!"); 105 | 106 | // Add Inventory Items 107 | inventoryService.addItem("RC Car", "High-speed remote control car", 5, "S123", "pcs", "Vehicles", true); 108 | inventoryService.addItem("Battery Pack", "Rechargeable battery", 10, "S1231", "pcs", "Accessories", true); 109 | 110 | try { 111 | inventoryService.addItem("Battery Pack Duplicate Serial Number", "Rechargeable battery", 10, "S1231", "pcs", "Accessories", true); 112 | } catch (IllegalArgumentException ex) { 113 | System.out.println(ex); 114 | } 115 | 116 | System.out.println("---------------------------------------"); 117 | System.out.println("✅ Inventory items added successfully!"); 118 | System.out.println("---------------------------------------"); 119 | 120 | // Display All Items 121 | System.out.println("📌 Displaying all inventory items:"); 122 | inventoryController.displayAllItems(); 123 | System.out.println("---------------------------------------"); 124 | 125 | System.out.println("🔄 Updating 'RC Car' quantity to 8..."); 126 | inventoryController.updateItem(1, "RC Car", "High-speed remote control car", 8, "Vehicles", true); 127 | 128 | System.out.println("---------------------------------------"); 129 | System.out.println("📌 Displaying updated inventory items:"); 130 | inventoryController.displayAllItems(); 131 | 132 | System.out.println("---------------------------------------"); 133 | System.out.println("📌 Displaying all low stock items:"); 134 | int threshold = 10; 135 | List lowCost = inventoryController.getLowStockItems(threshold); 136 | lowCost.stream().forEach(System.out::println); 137 | System.out.println("---------------------------------------"); 138 | 139 | System.out.println("List of Beans provided by Spring Boot:"); 140 | 141 | String[] beanNames = context.getBeanDefinitionNames(); 142 | List beanClasses = Stream.of(beanNames) 143 | .map(el -> context.getBean(el).getClass().toString()) 144 | .filter(el -> el.contains("bg.uni.fmi")) 145 | .toList(); 146 | beanClasses.forEach(System.out::println); 147 | 148 | // uncomment to see all loaded beans 149 | // Arrays.sort(beanNames); 150 | // for (String beanName : beanNames) { 151 | // System.out.println(beanName + " - " + context.getBean(beanName).getClass()); 152 | // } 153 | } 154 | } 155 | 156 | ``` 157 | 158 |
159 | 160 | 161 | ## Task 3 - add lombok dependency and remove System print logs if any 162 | 1. Search for lombok inside [mvn-repository](https://mvnrepository.com/) 163 | 2. Copy the `dependency` 164 | 3. Add it inside `pom.xml` 165 | 4. Create verion property instead of the hardcoded one (e.g 1.18.32) 166 | 167 |
168 | Hints 169 | 170 | ```md 171 | - [lombok-repo](https://mvnrepository.com/artifact/org.projectlombok/lombok) 172 | - modify `properties` in order to introduce `lombok.version` 173 | ``` 174 | 175 |
176 | 177 | ## Task 4 - Ensure that you have basic CRUD operations written for all services 178 | Implement all Services connecting them to Repositories using the Spring approach -------------------------------------------------------------------------------- /week05/logger/AppConfig.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.config; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @ConfigurationProperties(prefix = "config") 8 | @Configuration 9 | @Data 10 | public class AppConfig { 11 | private final LoggerConfig logger = new LoggerConfig(); 12 | 13 | @Data 14 | @ConfigurationProperties(prefix = "logger.info") 15 | public static class LoggerConfig { 16 | private String level; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /week05/logger/Logger.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.config.logger; 2 | 3 | import bg.fmi.uni.inventorysystem.vo.LoggerLevel; 4 | 5 | public interface Logger { 6 | 7 | void info(Object toLog); 8 | 9 | void debug(Object toLog); 10 | 11 | void trace(Object toLog); 12 | 13 | void error(Object toLog); 14 | 15 | default boolean isLoggingAllowed(LoggerLevel loggerLevel, LoggerLevel systemLoggerLevel) { 16 | return loggerLevel.getCode().compareTo(systemLoggerLevel.getCode()) <= 0; 17 | } 18 | } -------------------------------------------------------------------------------- /week05/logger/LoggerFileScenarioImpl.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.config.logger; 2 | 3 | import bg.fmi.uni.inventorysystem.config.AppConfig; 4 | import bg.fmi.uni.inventorysystem.vo.LoggerLevel; 5 | import jakarta.annotation.PostConstruct; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.context.annotation.Profile; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.io.File; 11 | import java.io.FileWriter; 12 | import java.io.IOException; 13 | import java.io.PrintWriter; 14 | import java.util.Date; 15 | 16 | @Profile("dev") 17 | @Component("fileLogger") // if you need to define named component 18 | @RequiredArgsConstructor 19 | public class LoggerFileScenarioImpl implements Logger { 20 | 21 | private LoggerLevel loggerLevel; 22 | 23 | private final AppConfig appConfig; 24 | 25 | @PostConstruct 26 | public void setup() { 27 | System.out.println(">>>>>>>>>>>>>>> appConfig.getLogger().getLevel() -> " + appConfig.getLogger().getLevel()); 28 | loggerLevel = LoggerLevel.valueOf(appConfig.getLogger().getLevel()); 29 | } 30 | 31 | @Override 32 | public void info(Object toLog) { 33 | LoggerLevel currentLogger = LoggerLevel.INFO; 34 | if (isLoggingAllowed(currentLogger)) { // not needed because info is always printed by the rule 35 | logInformation(toLog, currentLogger); 36 | } 37 | } 38 | 39 | @Override 40 | public void debug(Object toLog) { 41 | LoggerLevel currentLogger = LoggerLevel.DEBUG; 42 | if (isLoggingAllowed(currentLogger)) { 43 | logInformation(toLog, currentLogger); 44 | } 45 | } 46 | 47 | @Override 48 | public void trace(Object toLog) { 49 | LoggerLevel currentLogger = LoggerLevel.TRACE; 50 | if (isLoggingAllowed(currentLogger)) { 51 | logInformation(toLog, currentLogger); 52 | } 53 | } 54 | 55 | @Override 56 | public void error(Object toLog) { 57 | logInformation(toLog, LoggerLevel.ERROR); 58 | } 59 | 60 | private boolean isLoggingAllowed(LoggerLevel loggerLevel) { 61 | return loggerLevel.getCode().compareTo(this.loggerLevel.getCode()) <= 0; 62 | } 63 | 64 | private void logInformation(Object toLog, LoggerLevel currentLoggerLevel) { 65 | File log = new File("log.txt"); 66 | try (PrintWriter out = new PrintWriter(new FileWriter(log, true))) { 67 | out.println(new Date() + " [" + currentLoggerLevel.getLabel() + "] - " + toLog); 68 | } catch (IOException e) { 69 | e.printStackTrace(); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /week05/logger/LoggerLevel.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.vo; 2 | 3 | 4 | public enum LoggerLevel { 5 | 6 | INFO(1, "INFO"), 7 | DEBUG(2, "DEBUG"), 8 | TRACE(3, "TRACE"), 9 | ERROR(0, "ERROR"); 10 | 11 | private final Integer code; 12 | public final String label; 13 | 14 | LoggerLevel(Integer code, String label) { 15 | this.code = code; 16 | this.label = label; 17 | } 18 | 19 | public Integer getCode() { 20 | return code; 21 | } 22 | 23 | public final String getLabel() { 24 | return label; 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /week05/logger/LoggerStdoutImpl.java: -------------------------------------------------------------------------------- 1 | package bg.fmi.uni.inventorysystem.config.logger; 2 | 3 | import bg.fmi.uni.inventorysystem.config.AppConfig; 4 | import bg.fmi.uni.inventorysystem.vo.LoggerLevel; 5 | import jakarta.annotation.PostConstruct; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.context.annotation.Primary; 8 | import org.springframework.context.annotation.Profile; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Profile("local") 12 | @Component 13 | @Primary 14 | public class LoggerStdoutImpl implements Logger { 15 | 16 | private final AppConfig appConfig; 17 | 18 | @Autowired 19 | public LoggerStdoutImpl(AppConfig appConfig) { 20 | this.appConfig = appConfig; 21 | } 22 | 23 | private LoggerLevel systemLoggerLevel; 24 | 25 | /** 26 | * By using the Bean lifecycle we can set specific values for our business functionalities 27 | */ 28 | @PostConstruct 29 | public void setup() { 30 | System.out.println(">>>>>>>>>>>>>>> appConfig.getLogger().getLevel() -> " + appConfig.getLogger().getLevel()); 31 | systemLoggerLevel = LoggerLevel.valueOf(appConfig.getLogger().getLevel()); 32 | } 33 | 34 | @Override 35 | public void info(Object toLog) { 36 | System.out.println(toLog); 37 | } 38 | 39 | @Override 40 | public void debug(Object toLog) { 41 | if (isLoggingAllowed(LoggerLevel.DEBUG, systemLoggerLevel)) { 42 | System.out.println(toLog); 43 | } 44 | } 45 | 46 | @Override 47 | public void trace(Object toLog) { 48 | if(isLoggingAllowed(LoggerLevel.TRACE, systemLoggerLevel)) { 49 | System.out.println(toLog); 50 | } 51 | } 52 | 53 | @Override 54 | public void error(Object toLog) { 55 | System.out.println(toLog); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /week05/tasks.md: -------------------------------------------------------------------------------- 1 | # Tasks 2 | 3 | Before continuing, ensure that you have at least one full domain thread (controller -> service -> repository) from the last week tasks 4 | 5 | 6 | ## Task 0 7 | Implement two [logging](https://www.graylog.org/post/server-log-files-in-a-nutshell#:~:text=A%20server%20log%20file%20is,or%20the%20application%20was%20accessed.) mechanism to show data from your application - to a console and to a file 8 | 9 | Your custom loggers must implement the following interface: 10 | ``` 11 | public interface Logger { 12 | 13 | void info(Object toLog); 14 | 15 | void debug(Object toLog); 16 | 17 | void trace(Object toLog); 18 | 19 | void error(Object toLog); 20 | } 21 | ``` 22 | 23 | The first implementation should use the STDOUT (standard output -> System.out.println) 24 | 25 | The second one should store information inside file (use the code snippet bellow) 26 | ``` 27 | private void logInformation(Object toLog, LoggerLevel currentLoggerLevel) { 28 | File log = new File("log.txt"); 29 | try (PrintWriter out = new PrintWriter(new FileWriter(log, true))) { 30 | out.println(new Date() + " [" + currentLoggerLevel.getLabel() + "] - " + toLog); 31 | } catch (IOException e) { 32 | e.printStackTrace(); 33 | } 34 | } 35 | 36 | ``` 37 | 38 | Information for the logger level can be store inside enum like: 39 | ``` 40 | public enum LoggerLevel { 41 | 42 | INFO(1, "INFO"), 43 | DEBUG(2, "DEBUG"), 44 | TRACE(3, "TRACE"), 45 | ERROR(0, "ERROR"); 46 | 47 | private final Integer code; 48 | public final String label; 49 | 50 | LoggerLevel(Integer code, String label) { 51 | this.code = code; 52 | this.label = label; 53 | } 54 | 55 | public Integer getCode() { 56 | return code; 57 | } 58 | 59 | public final String getLabel() { 60 | return label; 61 | } 62 | 63 | } 64 | ``` 65 | 66 | Based on the logging level defined inside the application.propeties limit the information that you log inside your application. 67 | 68 | Logging parameters: INFO, DEBUG, TRACE 69 | ``` 70 | INFO level log only INFO 71 | DEBUG level log INFO & DEBUG 72 | TRACE level log INFO & DEBUG & TRACE 73 | ``` 74 | error log is always shown. 75 | 76 | 77 | Based on your active spring profile you must use the appropriate implementation 78 | For example 79 | ``` 80 | local profile to use the STDOUT implementation 81 | dev profile to use the FILE implementation 82 | ``` 83 | 84 | ## Task 1 85 | Use your custom logger inside ClubManagementSystem. 86 | 87 | *Example*: log data when you enter creation method -> logger.info("addItem triggered"); 88 | 89 | 90 | ## Task 2 91 | *Note:* to finish this task you need InventoryService implementation. 92 | 93 | Implement property data reading from the application.properties file. 94 | 95 | In our ClubManagementSystem system we want to redefine what low-stock threshold means. An low-stock threshold is quantity which is below 7 units (old implementation getLowStockItems(7)). The usage of threshold passed to the service is no longer required (make sure that if the property is not set a default of 10 is set) 96 | 97 | Property example: 98 | 99 | ``` 100 | config.inventory.low-stock-threshold=7 101 | ``` 102 | 103 | Tips: you can use @Value or AppConfig class from lecture example. 104 | [similar class implementation](https://github.com/GeorgiMinkov/smart-garden/blob/master/ms-smart-garden/src/main/java/bg/unisofia/fmi/robotcourse/config/AppConfig.java) and 105 | [property file](https://github.com/GeorgiMinkov/smart-garden/blob/master/ms-smart-garden/src/main/resources/application.properties) 106 | 107 | ## Task 3 - Reminder for Soon-to-Be Overdue Transactions 108 | 109 | ### User Story 110 | **Title:** Implement Alerting Mechanism for Soon-to-Be Overdue Borrowable Items 111 | **As** a system administrator, 112 | **I want** the system to notify me when items are about to become overdue, 113 | **So that** I can contact members and reduce overdue returns. 114 | 115 | --- 116 | 117 | ### Acceptance Criteria 118 | 119 | - A configurable property `config.transaction.reminder-safety-window-days` determines the reminder threshold. 120 | - Default value is `3` days. 121 | - Unreturned transactions are considered "soon to be overdue" if: 122 | ``` 123 | dueDate - today <= reminderWindow 124 | ``` 125 | - A REST endpoint returns all such transactions: 126 | **GET /api/transactions/reminder** 127 | - Log the result of every check: 128 | - ✅ If matches: log count and transaction IDs. 129 | - ❌ If none: log "No upcoming due transactions found." 130 | 131 | ## Bonus Task (REST API {lec06}) 132 | For your management platform create a simple get all inventory items REST endpoint 133 | 134 | Steps: 135 | 136 | - add web starter dependency (Additional info: Starter of Spring web uses Spring MVC, REST and Tomcat as a default embedded server. The single spring-boot-starter-web dependency transitively pulls in all dependencies related to web development. It also reduces the build dependency count.) 137 | ``` 138 | 139 | org.springframework.boot 140 | spring-boot-starter-web 141 | 142 | ``` 143 | 144 | - create a controller package 145 | - use @RestController (Additional info: you can use @Controller but, @RestController annotation in order to simplify the creation of RESTful web services. It's a convenient annotation that combines @Controller and @ResponseBody, which eliminates the need to annotate every request handling method of the controller class with the @ResponseBody annotation.) 146 | - use @GetMapping (Additional info: Annotation for mapping HTTP GET requests onto specific handler methods. Specifically, @GetMapping is a composed annotation that acts as a shortcut for @RequestMapping(method = RequestMethod. GET) .) 147 | 148 | Example: 149 | ``` 150 | @RestController 151 | @RequestMapping("example") 152 | public class SimpleBookRestController { 153 | 154 | @GetMapping("/books") 155 | public Book getBook() { 156 | return service.getBooks(); 157 | } 158 | } 159 | ``` -------------------------------------------------------------------------------- /week06/tasks.md: -------------------------------------------------------------------------------- 1 | # Task 00 2 | *To call REST endpoitns we will use Postman tool.* 3 | 4 | Download from **[here](https://www.postman.com/downloads/)** 5 | 6 | Consume data from random-data-api API about beer information. 7 | Additional information about the RDA API [project](https://random-data-api.com/documentation) 8 | 9 | How to setup a request 10 | 1. Create collection - this will hold all your requests 11 | ![](https://github.com/GeorgiMinkov/web-development-with-Java/blob/main/week06/images/collection.png) 12 | ![](https://github.com/GeorgiMinkov/web-development-with-Java/blob/main/week06/images/collection_name.png) 13 | 2. Add new request inside collection - this will be your HTTP request 14 | ![](https://github.com/GeorgiMinkov/web-development-with-Java/blob/main/week06/images/add_request.png) 15 | 3. Set URL -> https://random-data-api.com/api/beer/random_beer 16 | 4. Set HTTP method -> GET 17 | ![](https://github.com/GeorgiMinkov/web-development-with-Java/blob/main/week06/images/request_method_url.png) 18 | 5. If you have parameters or body enter them here. For our case we do not have any 19 | 6. Click on 'Send' and see the result 20 | ![](https://github.com/GeorgiMinkov/web-development-with-Java/blob/main/week06/images/result.png) 21 | 22 | # Task 01 - Defining controllers in the Inventory System 23 | In the next task we will use our Inventory system. (you can use implementation from this [repo](https://github.com/dreamix-fmi-course-2025/web-development-with-java/blob/main/lab-project) {pay attention: not all services are implemented}) 24 | 25 | ***NB:*** *DTO* pattern and *Mappers* will be discussed in depth inside the next lecture 26 | 27 | ## Part 01 - Create dummy controller - together 28 | 0. Delete the CommandLineRunner from your project 29 | 1. Add spring boot starter web dependency inside pom.xml (refresh maven to fetch your new dependency) 30 | 2. Create package controller in which you will add your files. 31 | 3. Create file DummyController 32 | 4. Annotate it with @RestController - this will tell Spring that this will be REST controller and will be accessable over the HTTP and with @RequestMapping("/dummy") - thiw will say what is the base path 33 | 5. Create a simple public method that will return String value 34 | 6. Annotate it with @GetMapping("/hello-world") - this will say, create an endpoint which will be accesable over HTTP GET method with path /hello-world (using the base above /dummy/hello-world) 35 | 7. Create an endpoint which will recieve a path variable and return some String value 36 | 8. Call every endpoint from Postman 37 | 38 | ## Part 02 39 | 1. Expose every **GET** functionality from the InventoryItem service as web REST endpoint. 40 | In the next points we will define steps for shaping the REST endpoint (ex for InventoryItem) 41 | - first we need to define structure for our REST API 42 | - path -> localhost:8080/items 43 | - getItemById - get item by it's identifier 44 | - path -> localhost:8080/items/{id} 45 | - HTTP method -> GET (we will get information again) 46 | - patch param 47 | - Returned Code 200 OK 48 | - ***NB:*** [spring-requestparam-vs-pathvariable](https://www.baeldung.com/spring-requestparam-vs-pathvariable) 49 | 2. For exercise expose every functionality from the other Services 50 | 51 | ## Task 02 52 | Find a way to change the default port of your Spring Boot application 53 | -------------------------------------------------------------------------------- /week07/Inventory Item API.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "0031b04c-b7dc-487f-94cb-b56604f0d3b0", 4 | "name": "Inventory Item API", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", 6 | "_exporter_id": "6315544" 7 | }, 8 | "item": [ 9 | { 10 | "name": "Get All Items", 11 | "request": { 12 | "method": "GET", 13 | "header": [], 14 | "url": { 15 | "raw": "http://localhost:8080/api/items", 16 | "protocol": "http", 17 | "host": [ 18 | "localhost" 19 | ], 20 | "port": "8080", 21 | "path": [ 22 | "api", 23 | "items" 24 | ] 25 | } 26 | }, 27 | "response": [] 28 | }, 29 | { 30 | "name": "Get Item by ID", 31 | "request": { 32 | "method": "GET", 33 | "header": [], 34 | "url": { 35 | "raw": "http://localhost:8080/api/items/1", 36 | "protocol": "http", 37 | "host": [ 38 | "localhost" 39 | ], 40 | "port": "8080", 41 | "path": [ 42 | "api", 43 | "items", 44 | "1" 45 | ] 46 | } 47 | }, 48 | "response": [] 49 | }, 50 | { 51 | "name": "Create Item", 52 | "request": { 53 | "method": "POST", 54 | "header": [ 55 | { 56 | "key": "Content-Type", 57 | "value": "application/json" 58 | } 59 | ], 60 | "body": { 61 | "mode": "raw", 62 | "raw": "{\n \"name\": \"LiPo Battery\",\n \"description\": \"Rechargeable 2200mAh\",\n \"quantity\": 15,\n \"serialNumber\": \"SN00123\",\n \"unitOfMeasurement\": \"pcs\",\n \"category\": \"Power\",\n \"borrowable\": true\n}" 63 | }, 64 | "url": { 65 | "raw": "http://localhost:8080/api/items", 66 | "protocol": "http", 67 | "host": [ 68 | "localhost" 69 | ], 70 | "port": "8080", 71 | "path": [ 72 | "api", 73 | "items" 74 | ] 75 | } 76 | }, 77 | "response": [] 78 | }, 79 | { 80 | "name": "Update Item (PUT)", 81 | "request": { 82 | "method": "PUT", 83 | "header": [ 84 | { 85 | "key": "Content-Type", 86 | "value": "application/json" 87 | } 88 | ], 89 | "body": { 90 | "mode": "raw", 91 | "raw": "{\n \"name\": \"Updated Battery\",\n \"description\": \"Long-lasting power\",\n \"quantity\": 20,\n \"serialNumber\": \"SN00123\",\n \"unitOfMeasurement\": \"pcs\",\n \"category\": \"Power\",\n \"borrowable\": false\n}" 92 | }, 93 | "url": { 94 | "raw": "http://localhost:8080/api/items/1002", 95 | "protocol": "http", 96 | "host": [ 97 | "localhost" 98 | ], 99 | "port": "8080", 100 | "path": [ 101 | "api", 102 | "items", 103 | "1002" 104 | ] 105 | } 106 | }, 107 | "response": [] 108 | }, 109 | { 110 | "name": "Patch Item", 111 | "request": { 112 | "method": "PATCH", 113 | "header": [ 114 | { 115 | "key": "Content-Type", 116 | "value": "application/json" 117 | } 118 | ], 119 | "body": { 120 | "mode": "raw", 121 | "raw": "{\n \"quantity\": 10,\n \"borrowable\": true\n}" 122 | }, 123 | "url": { 124 | "raw": "http://localhost:8080/api/items/1002", 125 | "protocol": "http", 126 | "host": [ 127 | "localhost" 128 | ], 129 | "port": "8080", 130 | "path": [ 131 | "api", 132 | "items", 133 | "1002" 134 | ] 135 | } 136 | }, 137 | "response": [] 138 | }, 139 | { 140 | "name": "Delete Item", 141 | "request": { 142 | "method": "DELETE", 143 | "header": [], 144 | "url": { 145 | "raw": "http://localhost:8080/api/items/1002", 146 | "protocol": "http", 147 | "host": [ 148 | "localhost" 149 | ], 150 | "port": "8080", 151 | "path": [ 152 | "api", 153 | "items", 154 | "1002" 155 | ] 156 | } 157 | }, 158 | "response": [] 159 | } 160 | ] 161 | } -------------------------------------------------------------------------------- /week07/tasks.md: -------------------------------------------------------------------------------- 1 | ## Task 00 2 | Find a way to change the default port of your Spring Boot application 3 | 4 |
5 | Don't click here 6 | https://www.baeldung.com/spring-boot-change-port 7 |
8 | 9 | ## Task 01 10 | Expose CRUD (Create/Read/Update/Delete) functionalities related to the Inventory Item Domain 11 | 12 | Exampled Postman Collection can be found -> [collection](https://github.com/dreamix-fmi-course-2025/web-development-with-java/blob/main/week07/Inventory%20Item%20API.postman_collection.json) 13 | 14 | ## Task 02 15 | Create Exception Handler for the Rest Controller (@ControllerAdvice) 16 | 17 |
18 | Don't click here 19 | https://www.baeldung.com/exception-handling-for-rest-with-spring 20 |
21 | -------------------------------------------------------------------------------- /week09/tasks.md: -------------------------------------------------------------------------------- 1 | # Spring Boot + PostgreSQL DB Integration & Refactoring Tasks 2 | 3 | This guide walks you through integrating your Spring Boot application with a **PostgreSQL** database, defining a robust schema for an **Inventory Management System**, and refactoring your codebase for best practices and maintainability. 4 | 5 | --- 6 | 7 | ## Task 0: Setup Your Local Database 8 | 9 | - Install and run **PostgreSQL** on your machine. 10 | - You may use Docker or native installation. 11 | - 📚 [PostgreSQL setup guide](https://github.com/dreamix-fmi-course-2024/web-development-with-java-lab/blob/main/lab08/postgresql.md) 12 | 13 | --- 14 | 15 | ## Task 1: Create the Database and Schema 16 | 17 | - Create the database and user. 18 | - Default PostgreSQL port is 5432 (your setup may use 5433). 19 | - Use database name: `inventory-system`. 20 | 21 | **Schema Includes:** 22 | - `inventory_item` 23 | - `club_member` 24 | - `transaction` 25 | 26 | --- 27 | 28 | ## Task 2: Visualize Entity Relationships 29 | 30 | ### 🗃 Entity Relationship Diagram (ERD) 31 | 32 | ```mermaid 33 | erDiagram 34 | CLUB_MEMBER { 35 | INTEGER id PK 36 | VARCHAR first_name 37 | VARCHAR last_name 38 | VARCHAR email 39 | VARCHAR phone_number 40 | } 41 | INVENTORY_ITEM { 42 | INTEGER id PK 43 | VARCHAR name 44 | TEXT description 45 | INTEGER quantity 46 | VARCHAR serial_number 47 | VARCHAR unit_of_measurement 48 | VARCHAR category 49 | BOOLEAN borrowable 50 | TIMESTAMP added_date 51 | } 52 | TRANSACTION { 53 | INTEGER id PK 54 | INTEGER member_id FK 55 | INTEGER item_id FK 56 | TIMESTAMP transaction_date 57 | TIMESTAMP due_date 58 | INTEGER quantity_used 59 | TIMESTAMP returned_date 60 | } 61 | 62 | CLUB_MEMBER ||--o{ TRANSACTION : "has" 63 | INVENTORY_ITEM ||--o{ TRANSACTION : "used in" 64 | ``` 65 | 66 | --- 67 | 68 | ## Task 3: Integrate Spring Boot with PostgreSQL 69 | 70 | 1. **Add dependencies** for PostgreSQL and Spring JPA in your `pom.xml`. 71 | 2. **Configure** `application.properties`: 72 | ```properties 73 | spring.datasource.url=jdbc:postgresql://localhost:5433/inventory-system 74 | spring.datasource.username=postgres 75 | spring.datasource.password=pgadmin 76 | spring.datasource.driver-class-name=org.postgresql.Driver 77 | 78 | spring.jpa.hibernate.ddl-auto=validate 79 | spring.jpa.show-sql=true 80 | spring.jpa.properties.hibernate.format_sql=true 81 | 82 | # Load initial schema and data 83 | spring.sql.init.mode=always 84 | # never - don't search for data initialization 85 | # always - always execute data initialization 86 | spring.sql.init.schema-locations=classpath:db/init.sql 87 | spring.sql.init.data-locations=classpath:db/data.sql 88 | ``` 89 | 90 | --- 91 | 92 | ## Task 4: Define JPA Entities and DTOs 93 | 94 | - Create entity classes using `@Entity`, `@Id`, `@Column`. 95 | - Add DTOs for API layer data transfer. 96 | - Use Lombok (`@Data`, `@NoArgsConstructor`, `@AllArgsConstructor`) and Java `record` for DTOs where suitable. 97 | 98 | --- 99 | 100 | ## Task 5: Implement Repositories and Refactor Services 101 | 102 | - Replace in-memory storage with `JpaRepository` for all entities. 103 | - Refactor service logic to use repositories. 104 | 105 | --- 106 | 107 | ## Task 6: Refactor Transaction Model for Return Date 108 | 109 | - Update the `Transaction` entity to use a nullable `returnedDate` (`TIMESTAMP`) instead of a boolean. 110 | - Ensure service logic and API reflect this change. 111 | 112 | --- 113 | 114 | ## Task 7: Add Phone Number to ClubMember 115 | 116 | - Add a `phoneNumber` column to the `club_member` table and update the entity, DTO, and demo data accordingly. 117 | 118 | --- 119 | 120 | ## Task 8: Logging and Exception Handling 121 | 122 | - Use your `Logger` to trace calls to create/update items and transactions. 123 | - Handle exceptions via `@ControllerAdvice` or `@ExceptionHandler`. 124 | 125 | --- 126 | 127 | ## Task 9: Test and Seed Data 128 | 129 | - Use provided SQL scripts to seed demo data. 130 | - Test all endpoints for CRUD and transaction flows. 131 | 132 | --- 133 | 134 | 135 | ## Extra: Task 10: Add Borrowing Type to TransactionDto 136 | 137 | - Update your `TransactionDto` (the object returned by your API for transaction operations) to include an `itemType` field. 138 | - This field should be set to `"BORROWABLE"` if the item is borrowable, or `"CONSUMABLE"` if not. 139 | - Ensure that all endpoints returning a transaction (including when a transaction is returned) include this field in the response. 140 | - The mapping should be handled in the `fromEntity` static method of `TransactionDto`, e.g.: 141 | ```java 142 | public static TransactionDto fromEntity(Transaction transaction) { 143 | String itemType = transaction.getItem().isBorrowable() ? "BORROWABLE" : "CONSUMABLE"; 144 | return new TransactionDto( 145 | transaction.getId(), 146 | transaction.getItem().getId(), 147 | transaction.getMember().getId(), 148 | transaction.getQuantityUsed(), 149 | transaction.getTransactionDate(), 150 | transaction.getDueDate(), 151 | transaction.getReturnedDate(), 152 | itemType 153 | ); 154 | } 155 | ``` 156 | - This allows clients to easily distinguish between borrowable and consumable transactions in all API responses. 157 | 158 | --- 159 | 160 | ## 💡 Tips 161 | 162 | - Use your `InventoryItem` entity as a test subject before applying changes system-wide. 163 | - Build incrementally and test each step. 164 | 165 | --- 166 | 167 | ## 📦 SQL Scripts 168 | 169 |
170 | 📂 Click to expand SQL schema + seed scripts 171 | 172 | ```sql 173 | -- Insert initial ClubMembers 174 | INSERT INTO club_member (first_name, last_name, email, phone_number) VALUES 175 | ('John', 'Doe', 'john.doe@example.com', '+359888111222'), 176 | ('Jane', 'Smith', 'jane.smith@example.com', '+359888333444'); 177 | 178 | -- Insert initial InventoryItems 179 | INSERT INTO inventory_item (name, description, quantity, serial_number, unit_of_measurement, category, borrowable, added_date) VALUES 180 | ('RC Airplane', 'Remote controlled airplane', 10, 'SN12345', 'PIECE', 'AIRPLANE', true, NOW()), 181 | ('Drone Set', 'Quadcopter drone set', 5, 'SN67890', 'SET', 'DRONE', true, NOW()), 182 | ('Propeller', 'Spare propeller for airplane', 100, 'SN54321', 'PIECE', 'ACCESSORY', false, NOW()); 183 | 184 | -- Insert initial Transactions 185 | INSERT INTO transaction (member_id, item_id, transaction_date, due_date, quantity_used, returned_date) VALUES 186 | (1, 1, NOW(), NOW() + INTERVAL '7 days', 1, NULL), 187 | (2, 2, NOW(), NOW() + INTERVAL '7 days', 1, NULL), 188 | -- Consumable usage: member 1 uses 3 Propellers (item_id 3, which is not borrowable) 189 | (1, 3, NOW(), NOW(), 3, NULL); 190 | 191 | -- Create table for ClubMember 192 | DROP TABLE IF EXISTS transaction; 193 | DROP TABLE IF EXISTS inventory_item; 194 | DROP TABLE IF EXISTS club_member; 195 | 196 | CREATE TABLE IF NOT EXISTS club_member ( 197 | id SERIAL PRIMARY KEY, 198 | first_name VARCHAR(255) NOT NULL, 199 | last_name VARCHAR(255) NOT NULL, 200 | email VARCHAR(255) NOT NULL UNIQUE, 201 | phone_number VARCHAR(32) 202 | ); 203 | 204 | -- Create table for InventoryItem 205 | CREATE TABLE IF NOT EXISTS inventory_item ( 206 | id SERIAL PRIMARY KEY, 207 | name VARCHAR(255) NOT NULL, 208 | description TEXT, 209 | quantity INTEGER NOT NULL, 210 | serial_number VARCHAR(255), 211 | unit_of_measurement VARCHAR(50) NOT NULL, 212 | category VARCHAR(50) NOT NULL, 213 | borrowable BOOLEAN NOT NULL, 214 | added_date TIMESTAMP NOT NULL 215 | ); 216 | 217 | -- Create table for Transaction 218 | CREATE TABLE IF NOT EXISTS transaction ( 219 | id SERIAL PRIMARY KEY, 220 | member_id INTEGER NOT NULL REFERENCES club_member(id), 221 | item_id INTEGER NOT NULL REFERENCES inventory_item(id), 222 | transaction_date TIMESTAMP NOT NULL, 223 | due_date TIMESTAMP NOT NULL, 224 | quantity_used INTEGER NOT NULL, 225 | returned_date TIMESTAMP 226 | ); 227 | 228 | -- Add indexes for performance 229 | CREATE INDEX IF NOT EXISTS idx_transaction_member_id ON transaction(member_id); 230 | CREATE INDEX IF NOT EXISTS idx_transaction_item_id ON transaction(item_id); 231 | ``` 232 | 233 |
234 | 235 | --- -------------------------------------------------------------------------------- /week10/tasks.md: -------------------------------------------------------------------------------- 1 | # Tasks 2 | 3 | ## Task 1 4 | Finish the tasks from week09 (Hibernate/JPA/DB integration) 5 | 6 | ## Task 2 7 | Integrate flyway migrations in your project --------------------------------------------------------------------------------