diff --git a/README.md b/README.md
new file mode 100644
index 0000000..34a746f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,32 @@
+
+
+# api-first-development i.e. Contract First Development
+
+## Contract first development using Swagger API
+
+## Dependencies
+
+- Java Development Kit - JDK 17
+
+## Backend
+
+- Using: http://localhost:8080 for the project!
+
+
+### General setup (Guide)
+
+
+- In service see as follows:
+
+ - Database in case of dev and prod: MyQL Server (environment variables set for project, pls. user your own!)
+
+ - In case of test: H2 in memory database used (see application.properties for setup under test folder)
+
+- Environment Variables as follows:
+
+| Name | Value | Remark | Extra remark |
+|--------------------|--------------|-------------------|----------------------------------------------------------|
+| DB_PORT | 3306 | default | |
+| DB_URL | no such info | pls use your own! | |
+| DB_USER | root | default | pls use your own if you have set any other |
+| DB_PASSWORD | no such info | pls use your own! | [MYSQL Installation Guide](https://dev.mysql.com/doc/mysql-installation-excerpt/5.7/en/) |
diff --git a/api-first-development-service-api-contract/pom.xml b/api-first-development-service-api-contract/pom.xml
index 4285d89..889c6be 100644
--- a/api-first-development-service-api-contract/pom.xml
+++ b/api-first-development-service-api-contract/pom.xml
@@ -23,13 +23,11 @@
org.springframework.boot
spring-boot-starter-web
-
io.swagger
swagger-annotations
1.6.2
-
io.swagger
swagger-models
@@ -39,12 +37,26 @@
com.fasterxml.jackson.core
jackson-annotations
-
javax.validation
validation-api
2.0.1.Final
+
+ io.swagger.core.v3
+ swagger-annotations
+ 2.2.6
+
+
+ org.apache.tomcat
+ tomcat-annotations-api
+ 9.0.16
+
+
+ org.assertj
+ assertj-core
+ 3.23.1
+
@@ -52,7 +64,7 @@
io.swagger.codegen.v3
swagger-codegen-maven-plugin
- 3.0.37
+ 3.0.36
diff --git a/api-first-development-service-api-contract/src/main/java/com/csaba79coder/App.java b/api-first-development-service-api-contract/src/main/java/com/csaba79coder/App.java
deleted file mode 100644
index 8888bf1..0000000
--- a/api-first-development-service-api-contract/src/main/java/com/csaba79coder/App.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.csaba79coder;
-
-/**
- * Hello world!
- *
- */
-public class App
-{
- public static void main( String[] args )
- {
- System.out.println( "Hello World!" );
- }
-}
diff --git a/api-first-development-service-api-contract/src/main/resources/service-contract-api.yaml b/api-first-development-service-api-contract/src/main/resources/service-contract-api.yaml
index 09716d3..9a936e6 100644
--- a/api-first-development-service-api-contract/src/main/resources/service-contract-api.yaml
+++ b/api-first-development-service-api-contract/src/main/resources/service-contract-api.yaml
@@ -7,5 +7,364 @@ info:
email: csabavadasz79@gmail.com
servers:
- url: 'http://localhost:8080'
+tags:
+ - name: book
+ - name: log
+
paths:
-
+ /books:
+ get:
+ tags:
+ - book
+ summary: Render all books
+ description: Show all books from database
+ operationId: renderAllBooks
+ responses:
+ '200':
+ description: Successful operation
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/BookModel'
+ '400':
+ description: Bad request
+ '401':
+ description: Unauthorized
+ '403':
+ description: Forbidden
+ '404':
+ description: Book not found
+ '405':
+ description: Validation exception
+ post:
+ tags:
+ - book
+ summary: Add a new book to the store
+ description: Add a new book to the store
+ operationId: addBook
+ requestBody:
+ description: Create a new book in the store
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/NewBookModel'
+ required: true
+ responses:
+ '201':
+ description: Successful operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/BookModel'
+ '405':
+ description: Invalid input
+ /books/{bookId}:
+ get:
+ tags:
+ - book
+ summary: Find book by ID
+ description: Returns a single book
+ operationId: getBookById
+ parameters:
+ - name: bookId
+ in: path
+ description: ID of book to return
+ required: true
+ schema:
+ format: uuid
+ type: string
+ responses:
+ '200':
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/BookModel'
+ '400':
+ description: Invalid ID supplied
+ '404':
+ description: Book not found
+ put:
+ tags:
+ - book
+ summary: Update an existing book
+ description: Update an existing book by Id
+ operationId: updateBook
+ parameters:
+ - name: bookId
+ in: path
+ description: ID of book to return
+ required: true
+ schema:
+ format: uuid
+ type: string
+ requestBody:
+ description: Create a new book in the store
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ModifiedBookModel'
+ required: true
+ responses:
+ '200':
+ description: Successful operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/BookModel'
+ '400':
+ description: Invalid ID supplied
+ '404':
+ description: Book not found
+ '405':
+ description: Validation exception
+ delete:
+ tags:
+ - book
+ summary: Deletes a book
+ description: delete a book
+ operationId: deleteBook
+ parameters:
+ - name: bookId
+ in: path
+ description: ID of book to return
+ required: true
+ schema:
+ format: uuid
+ type: string
+ responses:
+ '204':
+ description: successful operation
+ /logs:
+ get:
+ tags:
+ - log
+ summary: Render all logs
+ description: Show all logs from database
+ operationId: renderAllLogs
+ responses:
+ '200':
+ description: Successful operation
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/LogModel'
+ '400':
+ description: Bad request
+ '401':
+ description: Unauthorized
+ '403':
+ description: Forbidden
+ '404':
+ description: Book not found
+ '405':
+ description: Validation exception
+ post:
+ tags:
+ - log
+ summary: Add a new log to the database
+ description: Add a new log to the database
+ operationId: addLog
+ requestBody:
+ description: Create a new log in the database
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/NewLogModel'
+ required: true
+ responses:
+ '201':
+ description: Successful operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/LogModel'
+ '405':
+ description: Invalid input
+
+components:
+ schemas:
+ NewBookModel:
+ type: object
+ properties:
+ title:
+ description: Title of book entity
+ type: string
+ example: 'Cat Among the Pigeons'
+ isbn:
+ description: ISBN number of the book (13 characters)
+ type: number
+ example: 9780671557003
+ genre:
+ description: Genre of the book entity
+ type: string
+ example: DETECTIVE_FICTION
+ enum:
+ - DETECTIVE_FICTION
+ - NOVEL
+ - MYSTERY
+ - THRILLER
+ - HORROR
+ - HISTORICAL
+ - ROMANCE
+ - WESTERN
+ - BILDUNGSROMAN
+ - SCIENCE_FICTION
+ - FICTION
+ - FANTASY
+ - MAGICAL_REALISM
+ - REALIST_LITERATURE
+ - OTHER
+ required:
+ - title
+ - isbn
+ - genre
+ BookModel:
+ type: object
+ properties:
+ id:
+ description: Id of book entity
+ type: string
+ format: uuid
+ example: '3a8ea9f1-1a95-4caf-932f-2f988052933b'
+ createdBy:
+ description: User id who created book entity
+ type: string
+ format: uuid
+ example: '3a8ea9f1-1a95-4caf-932f-2f988052933b'
+ updatedBy:
+ description: User id who updated book entity
+ type: string
+ format: uuid
+ example: '3a8ea9f1-1a95-4caf-932f-2f988052933b'
+ createdAt:
+ description: Creation time of book entity
+ type: string
+ format: timestamp
+ example: '2019-01-21T05:47:08.644'
+ updatedAt:
+ description: Updated time of book entity
+ type: string
+ format: timestamp
+ example: '2019-01-21T05:47:08.644'
+ title:
+ description: Title of book entity
+ type: string
+ example: 'Cat Among the Pigeons'
+ isbn:
+ description: ISBN number of the book (13 characters)
+ type: number
+ example: 9780671557003
+ genre:
+ description: Genre of the book entity
+ type: string
+ example: DETECTIVE_FICTION
+ enum:
+ - DETECTIVE_FICTION
+ - NOVEL
+ - MYSTERY
+ - THRILLER
+ - HORROR
+ - HISTORICAL
+ - ROMANCE
+ - WESTERN
+ - BILDUNGSROMAN
+ - SCIENCE_FICTION
+ - FICTION
+ - FANTASY
+ - MAGICAL_REALISM
+ - REALIST_LITERATURE
+ - OTHER
+ status:
+ description: Status of the book
+ type: string
+ example: SOLD
+ enum:
+ - AVAILABLE
+ - PENDING
+ - SOLD
+ availability:
+ description: Status of book availability
+ type: string
+ example: AVAILABLE
+ enum:
+ - AVAILABLE
+ - ARCHIVE
+ - DELETED
+ ModifiedBookModel:
+ type: object
+ properties:
+ title:
+ description: Title of book entity
+ type: string
+ example: 'Cat Among the Pigeons'
+ isbn:
+ description: ISBN number of the book (13 characters)
+ type: number
+ example: 9780671557003
+ genre:
+ description: Genre of the book entity
+ type: string
+ example: DETECTIVE_FICTION
+ enum:
+ - DETECTIVE_FICTION
+ - NOVEL
+ - MYSTERY
+ - THRILLER
+ - HORROR
+ - HISTORICAL
+ - ROMANCE
+ - WESTERN
+ - BILDUNGSROMAN
+ - SCIENCE_FICTION
+ - FICTION
+ - FANTASY
+ - MAGICAL_REALISM
+ - REALIST_LITERATURE
+ - OTHER
+ status:
+ description: Status of the book
+ type: string
+ example: SOLD
+ enum:
+ - AVAILABLE
+ - PENDING
+ - SOLD
+ availability:
+ description: Status of book availability
+ type: string
+ example: AVAILABLE
+ enum:
+ - AVAILABLE
+ - ARCHIVE
+ - DELETED
+ NewLogModel:
+ type: object
+ properties:
+ logMessage:
+ description: Log message
+ type: string
+ example: "{ERROR_CODE_001=Book with id: 4bc34bbf-6278-4586-9e62-429bc41edcf5 was not found}"
+ required:
+ - logMessage
+ LogModel:
+ type: object
+ properties:
+ id:
+ description: Id of log entity
+ type: string
+ format: uuid
+ example: '3a8ea9f1-1a95-4caf-932f-2f988052933b'
+ logMessage:
+ description: Log message
+ type: string
+ example: "{ERROR_CODE_001=Book with id: 4bc34bbf-6278-4586-9e62-429bc41edcf5 was not found}"
+ loggedAt:
+ description: Creation time of log
+ type: string
+ format: timestamp
+ example: '2019-01-21T05:47:08.644'
\ No newline at end of file
diff --git a/api-first-development-service/pom.xml b/api-first-development-service/pom.xml
index 4aecd01..82cd69c 100644
--- a/api-first-development-service/pom.xml
+++ b/api-first-development-service/pom.xml
@@ -80,6 +80,18 @@
3.8.1
test
+
+ com.csaba79coder
+ api-first-development-service-api-contract
+ 0.0.1-SNAPSHOT
+ compile
+
+
+ org.modelmapper
+ modelmapper
+ 3.1.1
+
+
diff --git a/api-first-development-service/src/main/java/com/csaba79coder/apifirstdevelopment/controller/BookController.java b/api-first-development-service/src/main/java/com/csaba79coder/apifirstdevelopment/controller/BookController.java
new file mode 100644
index 0000000..ca4628c
--- /dev/null
+++ b/api-first-development-service/src/main/java/com/csaba79coder/apifirstdevelopment/controller/BookController.java
@@ -0,0 +1,48 @@
+package com.csaba79coder.apifirstdevelopment.controller;
+
+import com.csaba79coder.api.BooksApi;
+import com.csaba79coder.apifirstdevelopment.service.BookService;
+import com.csaba79coder.models.BookModel;
+import com.csaba79coder.models.ModifiedBookModel;
+import com.csaba79coder.models.NewBookModel;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.UUID;
+
+@RestController
+@CrossOrigin(value = "http://localhost:8080")
+@RequiredArgsConstructor
+public class BookController implements BooksApi {
+
+ private final BookService bookService;
+
+ @Override
+ public ResponseEntity addBook(NewBookModel body) {
+ return ResponseEntity.status(201).body(bookService.addNewBook(body));
+ }
+
+ @Override
+ public ResponseEntity deleteBook(UUID bookId) {
+ bookService.deleteAnExistingBookById(bookId);
+ return ResponseEntity.status(204).build();
+ }
+
+ @Override
+ public ResponseEntity getBookById(UUID bookId) {
+ return ResponseEntity.status(200).body(bookService.getBookById(bookId));
+ }
+
+ @Override
+ public ResponseEntity> renderAllBooks() {
+ return ResponseEntity.status(200).body(bookService.renderAllBooks());
+ }
+
+ @Override
+ public ResponseEntity updateBook(UUID bookId, ModifiedBookModel body) {
+ return null;
+ }
+}
diff --git a/api-first-development-service/src/main/java/com/csaba79coder/apifirstdevelopment/controller/LogController.java b/api-first-development-service/src/main/java/com/csaba79coder/apifirstdevelopment/controller/LogController.java
new file mode 100644
index 0000000..8833dfe
--- /dev/null
+++ b/api-first-development-service/src/main/java/com/csaba79coder/apifirstdevelopment/controller/LogController.java
@@ -0,0 +1,28 @@
+package com.csaba79coder.apifirstdevelopment.controller;
+
+import com.csaba79coder.api.LogsApi;
+import com.csaba79coder.apifirstdevelopment.service.LogService;
+import com.csaba79coder.models.LogModel;
+import com.csaba79coder.models.NewLogModel;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@RequiredArgsConstructor
+public class LogController implements LogsApi {
+
+ private final LogService logService;
+
+ @Override
+ public ResponseEntity addLog(NewLogModel body) {
+ return null;
+ }
+
+ @Override
+ public ResponseEntity> renderAllLogs() {
+ return null;
+ }
+}
diff --git a/api-first-development-service/src/main/java/com/csaba79coder/apifirstdevelopment/entity/BaseEntity.java b/api-first-development-service/src/main/java/com/csaba79coder/apifirstdevelopment/entity/BaseEntity.java
new file mode 100644
index 0000000..4da31f9
--- /dev/null
+++ b/api-first-development-service/src/main/java/com/csaba79coder/apifirstdevelopment/entity/BaseEntity.java
@@ -0,0 +1,39 @@
+package com.csaba79coder.apifirstdevelopment.entity;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Id;
+import jakarta.persistence.MappedSuperclass;
+import lombok.Data;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import org.hibernate.Hibernate;
+
+import java.util.Objects;
+import java.util.UUID;
+
+@MappedSuperclass
+@Getter
+@Setter
+@ToString
+@RequiredArgsConstructor
+public class BaseEntity {
+
+ @Id
+ @Column(name = "id", nullable = false)
+ private UUID id = UUID.randomUUID();
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
+ BaseEntity that = (BaseEntity) o;
+ return id != null && Objects.equals(id, that.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return getClass().hashCode();
+ }
+}
diff --git a/api-first-development-service/src/main/java/com/csaba79coder/apifirstdevelopment/entity/Book.java b/api-first-development-service/src/main/java/com/csaba79coder/apifirstdevelopment/entity/Book.java
new file mode 100644
index 0000000..28356a6
--- /dev/null
+++ b/api-first-development-service/src/main/java/com/csaba79coder/apifirstdevelopment/entity/Book.java
@@ -0,0 +1,59 @@
+package com.csaba79coder.apifirstdevelopment.entity;
+
+import com.csaba79coder.apifirstdevelopment.value.Availability;
+import com.csaba79coder.apifirstdevelopment.value.Genre;
+import com.csaba79coder.apifirstdevelopment.value.Status;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
+import jdk.jfr.Timestamp;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import org.hibernate.annotations.Where;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+@Entity
+@Getter
+@Setter
+@ToString
+@NoArgsConstructor
+@Where(clause = "availability != 'DELETED'")
+public class Book extends BaseEntity {
+
+ @Column(name = "created_by", nullable = false)
+ private UUID createdBy = UUID.fromString("3a8ea9f1-1a95-4caf-932f-2f988052933b");
+
+ @Column(name = "updated_by", nullable = false)
+ private UUID updatedBy = UUID.fromString("9e91103b-ef57-4d61-983c-28dfdd7e332a");
+
+ @Timestamp
+ @Column(name = "created_at", nullable = false)
+ private LocalDateTime createdAt = LocalDateTime.now();
+
+ @Timestamp
+ @Column(name = "updated_at", nullable = false)
+ private LocalDateTime updatedAt = LocalDateTime.now();
+ @Column(name = "title", nullable = false)
+ private String title;
+
+ @Column(name = "isbn", nullable = false, unique = true, length = 13)
+ private BigDecimal isbn;
+
+ @Column(name = "genre", nullable = false)
+ @Enumerated(EnumType.STRING)
+ private Genre genre = Genre.OTHER;
+
+ @Column(name = "status", nullable = false)
+ @Enumerated(EnumType.STRING)
+ private Status status = Status.AVAILABLE;
+
+ @Column(name = "availability", nullable = false)
+ @Enumerated(EnumType.STRING)
+ private Availability availability = Availability.AVAILABLE;
+}
diff --git a/api-first-development-service/src/main/java/com/csaba79coder/apifirstdevelopment/entity/Log.java b/api-first-development-service/src/main/java/com/csaba79coder/apifirstdevelopment/entity/Log.java
new file mode 100644
index 0000000..cd5906a
--- /dev/null
+++ b/api-first-development-service/src/main/java/com/csaba79coder/apifirstdevelopment/entity/Log.java
@@ -0,0 +1,33 @@
+package com.csaba79coder.apifirstdevelopment.entity;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Table;
+import jdk.jfr.Timestamp;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.NonNull;
+import lombok.Setter;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@Entity
+@Getter
+@Setter
+@ToString
+@NoArgsConstructor
+@Table(name = "log")
+public class Log extends BaseEntity {
+
+ @Timestamp
+ @Column(name = "logged_at")
+ private LocalDateTime loggedAt = LocalDateTime.now();
+
+ @Column(name = "log_message")
+ private String logMessage;
+
+ public Log(@NonNull String message) {
+ logMessage = message;
+ }
+}
diff --git a/api-first-development-service/src/main/java/com/csaba79coder/apifirstdevelopment/exception/ControllerExceptionHandler.java b/api-first-development-service/src/main/java/com/csaba79coder/apifirstdevelopment/exception/ControllerExceptionHandler.java
new file mode 100644
index 0000000..bc1117b
--- /dev/null
+++ b/api-first-development-service/src/main/java/com/csaba79coder/apifirstdevelopment/exception/ControllerExceptionHandler.java
@@ -0,0 +1,32 @@
+package com.csaba79coder.apifirstdevelopment.exception;
+
+import com.csaba79coder.apifirstdevelopment.value.ErrorCode;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+import java.util.InputMismatchException;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import static com.csaba79coder.apifirstdevelopment.value.ErrorCode.ERROR_CODE_001;
+import static com.csaba79coder.apifirstdevelopment.value.ErrorCode.ERROR_CODE_002;
+
+@ControllerAdvice
+public class ControllerExceptionHandler {
+
+ @ExceptionHandler(value = {NoSuchElementException.class})
+ public ResponseEntity