diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..45afd4b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "maven" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + - package-ecosystem: github-actions + directory: / + schedule: + interval: daily diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 0000000..5d27e88 --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,26 @@ +name: Java CI with Maven + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + - name: Set up JDK 21 + uses: actions/setup-java@v5 + with: + java-version: '21' + distribution: 'temurin' + cache: maven + - name: Build with Maven + run: mvn -B compile diff --git a/README.md b/README.md index f831728..60f99a2 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,14 @@ ## Goal - - Demonstrate how to generate arbitrary documentation from your application - - Additionally, show how to use [Hibernate Data Repositories](https://docs.hibernate.org/orm/7.1/repositories/html_single/) +- Demonstrate how to generate arbitrary documentation from your application +- Additionally, show how to + use [Hibernate Data Repositories](https://docs.hibernate.org/orm/7.1/repositories/html_single/) ## Requirements - - Java 21 - - Docker for booting mySQL +- Java 21 +- Docker for booting mySQL ## Run @@ -16,15 +17,15 @@ ## Output: - - http://localhost:8080/docs - - http://localhost:8080/docs/full.html - - http://localhost:8080/docs/tryIt.html +- http://localhost:8080/docs +- http://localhost:8080/docs/full.html +- http://localhost:8080/docs/tryIt.html - - http://localhost:8080/redoc - - http://localhost:8080/swagger +- http://localhost:8080/redoc +- http://localhost:8080/swagger ## Documentation and usage - - https://jooby.io/modules/openapi - - https://jooby.io/modules/openapi/#openapi-outputdisplay +- https://jooby.io/modules/openapi +- https://jooby.io/modules/openapi/#openapi-outputdisplay diff --git a/conf/logback.xml b/conf/logback.xml index bdb1e0b..2ccd976 100644 --- a/conf/logback.xml +++ b/conf/logback.xml @@ -1,12 +1,12 @@ - - - [%d{ISO8601}]-[%thread] %-5level %logger - %msg%n - - + + + [%d{ISO8601}]-[%thread] %-5level %logger - %msg%n + + - - - + + + diff --git a/pom.xml b/pom.xml index 64b50c4..6547d0a 100644 --- a/pom.xml +++ b/pom.xml @@ -1,188 +1,190 @@ - - 4.0.0 + 4.0.0 - library-demo - app - 1.0.0 + library-demo + app + 1.0.0 - library-demo + library-demo - - - app.App - ${project.basedir}${file.separator}src${file.separator}main${file.separator}resources${file.separator}docs${file.separator} + + + app.App + + ${project.basedir}${file.separator}src${file.separator}main${file.separator}resources${file.separator}docs${file.separator} + - 4.0.13 - java,adoc + 4.0.15 + java,adoc - 21 - 21 - true - UTF-8 - + 21 + 21 + true + UTF-8 + - - - io.jooby - jooby-logback - - - io.jooby - jooby-netty - - - io.jooby - jooby-guice - - - io.jooby - jooby-jackson - - - io.jooby - jooby-hikari - - - io.jooby - jooby-flyway - - - org.flywaydb - flyway-mysql - 11.14.0 - - - io.jooby - jooby-hibernate - - - jakarta.data - jakarta.data-api - 1.0.1 - - - com.mysql - mysql-connector-j - 9.4.0 - - - - io.jooby - jooby-redoc - - - io.jooby - jooby-swagger-ui - - - - org.junit.jupiter - junit-jupiter-api - 5.13.4 - test - - - org.junit.jupiter - junit-jupiter-engine - 5.13.4 - test - - - - - - - conf - - - src${file.separator}main${file.separator}resources - - - - - maven-compiler-plugin - 3.14.0 - - - - org.hibernate.orm - hibernate-processor - 7.0.4.Final - - - io.jooby - jooby-apt - ${jooby.version} - - - - - - maven-surefire-plugin - 3.5.3 - - - - io.jooby - jooby-maven-plugin - ${jooby.version} - - - - openapi - - - 3.1 - - ${doc.dir}index.adoc - ${doc.dir}full.adoc - ${doc.dir}tryIt.adoc - - - - - - - - maven-shade-plugin - 3.6.0 - - - uber-jar - package - - shade - - - false - - - - ${application.class} - - - - - - - - - - - - io.jooby - jooby-bom - ${jooby.version} - pom - import - + + io.jooby + jooby-logback + + + io.jooby + jooby-netty + + + io.jooby + jooby-guice + + + io.jooby + jooby-jackson + + + io.jooby + jooby-hikari + + + io.jooby + jooby-flyway + + + org.flywaydb + flyway-mysql + 12.0.0 + + + io.jooby + jooby-hibernate + + + jakarta.data + jakarta.data-api + 1.0.1 + + + com.mysql + mysql-connector-j + 9.6.0 + + + + io.jooby + jooby-redoc + + + io.jooby + jooby-swagger-ui + + + + org.junit.jupiter + junit-jupiter-api + 6.0.2 + test + + + org.junit.jupiter + junit-jupiter-engine + 6.0.2 + test + - + + + + + conf + + + src${file.separator}main${file.separator}resources + + + + + maven-compiler-plugin + 3.15.0 + + + + org.hibernate.orm + hibernate-processor + 7.2.3.Final + + + io.jooby + jooby-apt + ${jooby.version} + + + + + + maven-surefire-plugin + 3.5.4 + + + + io.jooby + jooby-maven-plugin + ${jooby.version} + + + + openapi + + + 3.1 + + ${doc.dir}index.adoc + ${doc.dir}full.adoc + ${doc.dir}tryIt.adoc + + + + + + + + maven-shade-plugin + 3.6.1 + + + uber-jar + package + + shade + + + false + + + + ${application.class} + + + + + + + + + + + + + io.jooby + jooby-bom + ${jooby.version} + pom + import + + + diff --git a/src/main/java/app/App.java b/src/main/java/app/App.java index 6dbcd95..17dd5b5 100644 --- a/src/main/java/app/App.java +++ b/src/main/java/app/App.java @@ -5,7 +5,10 @@ import app.model.Author; import app.model.Book; import app.model.Publisher; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; import io.jooby.Jooby; +import io.jooby.OpenAPIModule; import io.jooby.flyway.FlywayModule; import io.jooby.guice.GuiceModule; import io.jooby.hibernate.HibernateModule; @@ -13,16 +16,17 @@ import io.jooby.hikari.HikariModule; import io.jooby.jackson.JacksonModule; import io.jooby.netty.NettyServer; -import io.jooby.OpenAPIModule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.concurrent.Executors; + /** * Library API. *

* An imaginary, but delightful Library API for interacting with library services and information. * Built with love by https://jooby.io. - *

+ *

* * @version 1.0.0 * @license.name Apache 2.0 @@ -44,36 +48,43 @@ */ public class App extends Jooby { - private static final Logger log = LoggerFactory.getLogger(App.class); + private static final Logger log = LoggerFactory.getLogger(App.class); + + { + // Enable Virtual Threads + setDefaultWorker(Executors.newVirtualThreadPerTaskExecutor()); + + // Dependency Injection + install(new GuiceModule()); + + // JSON + ObjectMapper objectMapper = JacksonModule.create(). + setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); - { - // Dependency Injection - install(new GuiceModule()); - // JSON - install(new JacksonModule()); + install(new JacksonModule(objectMapper)); - /* Documentation */ - install(new OpenAPIModule()); - // Publish generated files - assets("/docs/?*", "/app"); + /* Documentation */ + install(new OpenAPIModule()); + // Publish generated files + assets("/docs/?*", "/app"); - /* Database */ - install(new HikariModule()); - install(new FlywayModule()); - install(new HibernateModule(Book.class, Author.class, Publisher.class, Address.class)); + /* Database */ + install(new HikariModule()); + install(new FlywayModule()); + install(new HibernateModule(Book.class, Author.class, Publisher.class, Address.class)); - /* Middleware */ - use(new TransactionalRequest().useStatelessSession()); + /* Middleware */ + use(new TransactionalRequest().useStatelessSession()); - /* Controller */ - mvc(new LibraryApi_()); + /* Controller */ + mvc(new LibraryApi_()); - onStarted(() -> { - log.info("\n\nTry live docs at: \n http://localhost:8080/docs\n http://localhost:8080/docs/full.html\n http://localhost:8080/docs/tryIt.html\n\n http://localhost:8080/redoc\n http://localhost:8080/swagger\n"); - }); - } + onStarted(() -> { + log.info("\n\nTry live docs at: \n http://localhost:8080/docs\n http://localhost:8080/docs/full.html\n http://localhost:8080/docs/tryIt.html\n\n http://localhost:8080/redoc\n http://localhost:8080/swagger\n"); + }); + } - public static void main(final String[] args) { - runApp(args, new NettyServer(), App::new); - } + public static void main(final String[] args) { + runApp(args, new NettyServer(), App::new); + } } diff --git a/src/main/java/app/api/LibraryApi.java b/src/main/java/app/api/LibraryApi.java index 71cc447..4537faa 100644 --- a/src/main/java/app/api/LibraryApi.java +++ b/src/main/java/app/api/LibraryApi.java @@ -17,90 +17,87 @@ @Path("/library") public class LibraryApi { - private final Library library; + private final Library library; - @Inject - public LibraryApi(Library library) { - this.library = library; - } + @Inject + public LibraryApi(Library library) { + this.library = library; + } - /** - * Get Specific Book Details - *

- * View the full information for a single specific book using its unique ISBN. - *

- * - * @param isbn The unique ID from the URL (e.g., /books/978-3-16-148410-0) - * @return The book data - * @throws NotFoundException 404 error if it doesn't exist. - * @tag Library - * @securityRequirement librarySecurity read:books - */ - @GET - @Path("/books/{isbn}") - public Book getBook(@PathParam String isbn) { - return library.findBook(isbn) - .orElseThrow(() -> new NotFoundException(isbn)); - } + /** + * Get Specific Book Details + *

+ * View the full information for a single specific book using its unique ISBN. + *

+ * + * @param isbn The unique ID from the URL (e.g., /books/978-3-16-148410-0) + * @return The book data + * @throws NotFoundException 404 error if it doesn't exist. + * @tag Library + * @securityRequirement librarySecurity read:books + */ + @GET + @Path("/books/{isbn}") + public Book getBook(@PathParam String isbn) { + return library.findBook(isbn).orElseThrow(() -> new NotFoundException(isbn)); + } - /** - * Quick Search - *

Find books by a partial title (e.g., searching "Harry" finds "Harry Potter"). - * - * @param q The word or phrase to search for. - * @return A list of books matching that term. - * @x-badges [{name:Beta, position:before, color:purple}] - * @tag Library - * @securityRequirement librarySecurity read:books - */ - @GET - @Path("/search") - public List searchBooks(@QueryParam String q) { - var pattern = "%" + (q != null ? q : "") + "%"; + /** + * Quick Search + *

Find books by a partial title (e.g., searching "Harry" finds "Harry Potter"). + * + * @param q The word or phrase to search for. + * @return A list of books matching that term. + * @x-badges [{name:Beta, position:before, color:purple}] + * @tag Library + * @securityRequirement librarySecurity read:books + */ + @GET + @Path("/search") + public List searchBooks(@QueryParam String q) { + var pattern = "%" + (q != null ? q : "") + "%"; - return library.searchBooks(pattern); - } + return library.searchBooks(pattern); + } - /** - * Browse Books (Paginated) - *

Look up a specific book title where there might be many editions or copies, splitting the results into - * manageable pages. - * - * @param title The exact book title to filter by. - * @param page Which page number to load (defaults to 1). - * @param size How many books to show per page (defaults to 20). - * @return A "Page" object containing the books and info like "Total Pages: 5". - * @tag Library - * @securityRequirement librarySecurity read:books - */ - @GET - @Path("/books") - public Page getBooksByTitle(@QueryParam String title, - @QueryParam int page, - @QueryParam int size) { - // Ensure we have sensible defaults if the user sends nothing - int pageNum = page > 0 ? page : 1; - int pageSize = size > 0 ? size : 20; + /** + * Browse Books (Paginated) + *

Look up a specific book title where there might be many editions or copies, splitting the results into + * manageable pages. + * + * @param title The exact book title to filter by. + * @param page Which page number to load (defaults to 1). + * @param size How many books to show per page (defaults to 20). + * @return A "Page" object containing the books and info like "Total Pages: 5". + * @tag Library + * @securityRequirement librarySecurity read:books + */ + @GET + @Path("/books") + public Page getBooksByTitle(@QueryParam String title, @QueryParam int page, @QueryParam int size) { + // Ensure we have sensible defaults if the user sends nothing + int pageNum = page > 0 ? page : 1; + int pageSize = size > 0 ? size : 20; - // Ask the database for just this specific slice of data - return library.findBooksByTitle(title, PageRequest.ofPage(pageNum).size(pageSize)); - } + // Ask the database for just this specific slice of data + return library.findBooksByTitle(title, PageRequest.ofPage(pageNum).size(pageSize)); + } - /** - * Add New Book - * - *

Register a new book in the system. - * - * @param book New book to add. - * @return A text message confirming success. - * @throws io.jooby.exception.BadRequestException 400 On bad book data. - * @tag Inventory - * @securityRequirement librarySecurity write:books - */ - @POST - @Path("/books") - public Book addBook(Book book) { - // Save it - return library.add(book); - } + /** + * Add New Book + * + *

Register a new book in the system. + * + * @param book New book to add. + * @return A text message confirming success. + * @throws io.jooby.exception.BadRequestException 400 On bad book data. + * @tag Inventory + * @securityRequirement librarySecurity write:books + */ + @POST + @Path("/books") + public Book addBook(Book book) { + // Save it + return library.add(book); + } } diff --git a/src/main/java/app/model/Address.java b/src/main/java/app/model/Address.java index 4c3b2d3..57db7f8 100644 --- a/src/main/java/app/model/Address.java +++ b/src/main/java/app/model/Address.java @@ -8,29 +8,29 @@ */ @Embeddable public class Address { - /** - * The specific street address. - *

- * Includes the house number, street name, and apartment number if applicable. - * Example: "123 Maple Avenue, Apt 4B". - *

- */ - public String street; + /** + * The specific street address. + *

+ * Includes the house number, street name, and apartment number if applicable. + * Example: "123 Maple Avenue, Apt 4B". + *

+ */ + public String street; - /** - * The town, city, or municipality. - *

- * Used for grouping authors by location or calculating shipping regions. - *

- */ - public String city; + /** + * The town, city, or municipality. + *

+ * Used for grouping authors by location or calculating shipping regions. + *

+ */ + public String city; - /** - * The postal or zip code. - *

- * Stored as text (String) rather than a number to support codes - * that start with zero (e.g., "02138") or contain letters (e.g., "K1A 0B1"). - *

- */ - public String zip; + /** + * The postal or zip code. + *

+ * Stored as text (String) rather than a number to support codes + * that start with zero (e.g., "02138") or contain letters (e.g., "K1A 0B1"). + *

+ */ + public String zip; } diff --git a/src/main/java/app/model/Author.java b/src/main/java/app/model/Author.java index 386ce0e..f5958db 100644 --- a/src/main/java/app/model/Author.java +++ b/src/main/java/app/model/Author.java @@ -15,33 +15,34 @@ @Entity public class Author { - /** - * The author's unique government ID (SSN). - */ - @Id - public String ssn; - - /** - * The full name of the author. - */ - public String name; - - /** - * Where the author lives. - * This information is stored inside the Author table, not a separate one. - */ - @Embedded - public Address address; - - @ManyToMany - @JsonIgnore - public Set books = new HashSet<>(); - - public Author() {} - - public Author(String ssn, String name) { - this.ssn = ssn; - this.name = name; - } + /** + * The author's unique government ID (SSN). + */ + @Id + public String ssn; + + /** + * The full name of the author. + */ + public String name; + + /** + * Where the author lives. + * This information is stored inside the Author table, not a separate one. + */ + @Embedded + public Address address; + + @ManyToMany + @JsonIgnore + public Set books = new HashSet<>(); + + public Author() { + } + + public Author(String ssn, String name) { + this.ssn = ssn; + this.name = name; + } } diff --git a/src/main/java/app/model/Book.java b/src/main/java/app/model/Book.java index 09dd579..430e6e2 100644 --- a/src/main/java/app/model/Book.java +++ b/src/main/java/app/model/Book.java @@ -16,121 +16,121 @@ @Entity public class Book { - /** - * The unique "barcode" for this book (ISBN). - * We use this to identify exactly which book edition we are talking about. - */ - @Id - private String isbn; - - /** - * The name printed on the cover. - */ - @Basic(optional = false) - private String title; - - /** - * When this book was released to the public. - */ - private LocalDate publicationDate; - - /** - * The full story or content of the book. - *

- * Since this can be very long, we store it in a special way (Large Object) - * to keep the database fast. - *

- */ - @Lob - @Basic(optional = false) - private String text; - - /** - * Categorizes the item (e.g., is it a regular Book or a Magazine?). - */ - @Enumerated(EnumType.STRING) - @Basic(optional = false) - private BookType type; - - /** - * The company that published this book. - *

- * Performance Note: We only load this information if you specifically - * ask for it ("Lazy"), which saves memory. - *

- */ - @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST) - private Publisher publisher; - - /** - * The list of people who wrote this book. - */ - @ManyToMany(fetch = FetchType.EAGER, mappedBy = "books", cascade = CascadeType.PERSIST) - private Set authors = new HashSet<>(); - - public Book() { - } - - public Book(String isbn, String title, BookType type) { - this.isbn = isbn; - this.title = title; - this.type = type; - this.text = "Content placeholder"; - } - - public String getIsbn() { - return isbn; - } - - public void setIsbn(String isbn) { - this.isbn = isbn; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public LocalDate getPublicationDate() { - return publicationDate; - } - - public void setPublicationDate(LocalDate publicationDate) { - this.publicationDate = publicationDate; - } - - public String getText() { - return text; - } - - public void setText(String text) { - this.text = text; - } - - public BookType getType() { - return type; - } - - public void setType(BookType type) { - this.type = type; - } - - public Publisher getPublisher() { - return publisher; - } - - public void setPublisher(Publisher publisher) { - this.publisher = publisher; - } - - public Set getAuthors() { - return authors; - } - - public void setAuthors(Set authors) { - this.authors = authors; - } + /** + * The unique "barcode" for this book (ISBN). + * We use this to identify exactly which book edition we are talking about. + */ + @Id + private String isbn; + + /** + * The name printed on the cover. + */ + @Basic(optional = false) + private String title; + + /** + * When this book was released to the public. + */ + private LocalDate publicationDate; + + /** + * The full story or content of the book. + *

+ * Since this can be very long, we store it in a special way (Large Object) + * to keep the database fast. + *

+ */ + @Lob + @Basic(optional = false) + private String text; + + /** + * Categorizes the item (e.g., is it a regular Book or a Magazine?). + */ + @Enumerated(EnumType.STRING) + @Basic(optional = false) + private BookType type; + + /** + * The company that published this book. + *

+ * Performance Note: We only load this information if you specifically + * ask for it ("Lazy"), which saves memory. + *

+ */ + @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST) + private Publisher publisher; + + /** + * The list of people who wrote this book. + */ + @ManyToMany(fetch = FetchType.EAGER, mappedBy = "books", cascade = CascadeType.PERSIST) + private Set authors = new HashSet<>(); + + public Book() { + } + + public Book(String isbn, String title, BookType type) { + this.isbn = isbn; + this.title = title; + this.type = type; + this.text = "Content placeholder"; + } + + public String getIsbn() { + return isbn; + } + + public void setIsbn(String isbn) { + this.isbn = isbn; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public LocalDate getPublicationDate() { + return publicationDate; + } + + public void setPublicationDate(LocalDate publicationDate) { + this.publicationDate = publicationDate; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public BookType getType() { + return type; + } + + public void setType(BookType type) { + this.type = type; + } + + public Publisher getPublisher() { + return publisher; + } + + public void setPublisher(Publisher publisher) { + this.publisher = publisher; + } + + public Set getAuthors() { + return authors; + } + + public void setAuthors(Set authors) { + this.authors = authors; + } } diff --git a/src/main/java/app/model/BookType.java b/src/main/java/app/model/BookType.java index 5199508..89e20ca 100644 --- a/src/main/java/app/model/BookType.java +++ b/src/main/java/app/model/BookType.java @@ -4,51 +4,51 @@ * Defines the format and release schedule of the item. */ public enum BookType { - /** - * A fictional narrative story. - *

- * Examples: "Pride and Prejudice", "Harry Potter", "Dune". - * These are creative works meant for entertainment or artistic expression. - *

- */ - NOVEL, + /** + * A fictional narrative story. + *

+ * Examples: "Pride and Prejudice", "Harry Potter", "Dune". + * These are creative works meant for entertainment or artistic expression. + *

+ */ + NOVEL, - /** - * A written account of a real person's life. - *

- * Examples: "Steve Jobs" by Walter Isaacson, "The Diary of a Young Girl". - * These are non-fiction historical records of an individual. - *

- */ - BIOGRAPHY, + /** + * A written account of a real person's life. + *

+ * Examples: "Steve Jobs" by Walter Isaacson, "The Diary of a Young Girl". + * These are non-fiction historical records of an individual. + *

+ */ + BIOGRAPHY, - /** - * An educational book used for study. - *

- * Examples: "Calculus: Early Transcendentals", "Introduction to Java Programming". - * These are designed for students and are often used as reference material - * in academic courses. - *

- */ - TEXTBOOK, + /** + * An educational book used for study. + *

+ * Examples: "Calculus: Early Transcendentals", "Introduction to Java Programming". + * These are designed for students and are often used as reference material + * in academic courses. + *

+ */ + TEXTBOOK, - /** - * A periodical publication intended for general readers. - *

- * Examples: Time, National Geographic, Vogue. - * These contain various articles, are published frequently (weekly/monthly), - * and often have a glossy format. - *

- */ - MAGAZINE, + /** + * A periodical publication intended for general readers. + *

+ * Examples: Time, National Geographic, Vogue. + * These contain various articles, are published frequently (weekly/monthly), + * and often have a glossy format. + *

+ */ + MAGAZINE, - /** - * A scholarly or professional publication. - *

- * Examples: The New England Journal of Medicine, Harvard Law Review. - * These focus on academic research or trade news and are written by experts - * for other experts. - *

- */ - JOURNAL + /** + * A scholarly or professional publication. + *

+ * Examples: The New England Journal of Medicine, Harvard Law Review. + * These focus on academic research or trade news and are written by experts + * for other experts. + *

+ */ + JOURNAL } diff --git a/src/main/java/app/model/Publisher.java b/src/main/java/app/model/Publisher.java index 2cc8e01..b08ba43 100644 --- a/src/main/java/app/model/Publisher.java +++ b/src/main/java/app/model/Publisher.java @@ -9,26 +9,30 @@ */ @Entity public class Publisher { - /** - * The unique internal ID for this publisher. - *

- * This is a number generated automatically by the system. - * Users usually don't need to memorize this, but it's used by the database - * to link books to their publishers. - *

- */ - @Id - @GeneratedValue - public Long id; + /** + * The unique internal ID for this publisher. + *

+ * This is a number generated automatically by the system. + * Users usually don't need to memorize this, but it's used by the database + * to link books to their publishers. + *

+ */ + @Id + @GeneratedValue + public Long id; - /** - * The official business name of the publishing house. - *

- * Example: "Penguin Random House" or "O'Reilly Media". - *

- */ - public String name; + /** + * The official business name of the publishing house. + *

+ * Example: "Penguin Random House" or "O'Reilly Media". + *

+ */ + public String name; - public Publisher() {} - public Publisher(String name) { this.name = name; } + public Publisher() { + } + + public Publisher(String name) { + this.name = name; + } } diff --git a/src/main/java/app/repo/Library.java b/src/main/java/app/repo/Library.java index c08cd93..99321f6 100644 --- a/src/main/java/app/repo/Library.java +++ b/src/main/java/app/repo/Library.java @@ -23,83 +23,83 @@ @ImplementedBy(Library_.class) public interface Library { - /** - * A helper to access the database connection directly. - * Useful if we need to do something very specific that the automatic methods can't handle. - */ - StatelessSession session(); + /** + * A helper to access the database connection directly. + * Useful if we need to do something very specific that the automatic methods can't handle. + */ + StatelessSession session(); - // --- Finding Items --- + // --- Finding Items --- - /** - * Looks up a single book using its ISBN code. - * - * @param isbn The unique code to look for. - * @return An "Optional" box that contains the book if we found it, or is empty if we didn't. - */ - @Find - Optional findBook(String isbn); + /** + * Looks up a single book using its ISBN code. + * + * @param isbn The unique code to look for. + * @return An "Optional" box that contains the book if we found it, or is empty if we didn't. + */ + @Find + Optional findBook(String isbn); - /** - * Looks up an author using their ID. - */ - @Find - Optional findAuthor(String ssn); + /** + * Looks up an author using their ID. + */ + @Find + Optional findAuthor(String ssn); - /** - * Finds books that match a specific title. - *

- * Because there might be thousands of results, this method splits them into "pages". - * You ask for "Page 1" or "Page 5", and it gives you just that chunk. - *

- * - * @param title The exact title to look for. - * @param pageRequest Which page of results do you want? - * @return A page containing a list of books and total count info. - */ - @Find - @OrderBy("title") - Page findBooksByTitle(String title, PageRequest pageRequest); + /** + * Finds books that match a specific title. + *

+ * Because there might be thousands of results, this method splits them into "pages". + * You ask for "Page 1" or "Page 5", and it gives you just that chunk. + *

+ * + * @param title The exact title to look for. + * @param pageRequest Which page of results do you want? + * @return A page containing a list of books and total count info. + */ + @Find + @OrderBy("title") + Page findBooksByTitle(String title, PageRequest pageRequest); - // --- Custom Searches --- + // --- Custom Searches --- - /** - * Search for books that have a specific word in the title. - *

- * Example: If you search for "%Harry%", it finds "Harry Potter" and "Dirty Harry". - * It also sorts the results alphabetically by title. - *

- */ - @Query("where title like :pattern order by title") - List searchBooks(String pattern); + /** + * Search for books that have a specific word in the title. + *

+ * Example: If you search for "%Harry%", it finds "Harry Potter" and "Dirty Harry". + * It also sorts the results alphabetically by title. + *

+ */ + @Query("where title like :pattern order by title") + List searchBooks(String pattern); - /** - * A custom report that just lists the titles of new books. - * Useful for creating quick lists without loading all the book details. - * - * @param minYear The oldest year we care about (e.g., 2023). - * @return Just the names of the books. - */ - @Query("select title from Book where extract(year from publicationDate) >= :minYear") - List findRecentBookTitles(int minYear); + /** + * A custom report that just lists the titles of new books. + * Useful for creating quick lists without loading all the book details. + * + * @param minYear The oldest year we care about (e.g., 2023). + * @return Just the names of the books. + */ + @Query("select title from Book where extract(year from publicationDate) >= :minYear") + List findRecentBookTitles(int minYear); - // --- Saving & Deleting --- + // --- Saving & Deleting --- - /** - * Registers a new book in the system. - */ - @Insert - Book add(Book book); + /** + * Registers a new book in the system. + */ + @Insert + Book add(Book book); - /** - * Saves changes made to an author's details. - */ - @Update - void update(Author author); + /** + * Saves changes made to an author's details. + */ + @Update + void update(Author author); - /** - * Permanently removes a book from the library. - */ - @Delete - void remove(Book book); + /** + * Permanently removes a book from the library. + */ + @Delete + void remove(Book book); } diff --git a/src/main/resources/db/migration/V0.0.0__Schema.sql b/src/main/resources/db/migration/V0.0.0__Schema.sql index 8fffa28..5540a9d 100644 --- a/src/main/resources/db/migration/V0.0.0__Schema.sql +++ b/src/main/resources/db/migration/V0.0.0__Schema.sql @@ -3,68 +3,76 @@ -- -------------------------------------------------------- -- Stores the publishing companies. -- Created first because Books depend on Publishers. -CREATE TABLE IF NOT EXISTS Publisher ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE IF NOT EXISTS Publisher +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4; -- -------------------------------------------------------- -- 2. Book Table -- -------------------------------------------------------- -- Stores the physical items (Books, Magazines, etc). -- 'publisher_id' links to the Publisher table. -CREATE TABLE IF NOT EXISTS Book ( - isbn VARCHAR(255) NOT NULL PRIMARY KEY, - title VARCHAR(255) NOT NULL, +CREATE TABLE IF NOT EXISTS Book +( + isbn VARCHAR(255) NOT NULL PRIMARY KEY, + title VARCHAR(255) NOT NULL, publicationDate DATE, -- @Lob maps to TEXT or LONGTEXT in MySQL for large content - text LONGTEXT NOT NULL, + text LONGTEXT NOT NULL, -- Enum values stored as strings for readability - type ENUM('NOVEL', 'BIOGRAPHY', 'TEXTBOOK', 'MAGAZINE', 'JOURNAL') NOT NULL, + type ENUM ('NOVEL', 'BIOGRAPHY', 'TEXTBOOK', 'MAGAZINE', 'JOURNAL') NOT NULL, -- Foreign Key column - publisher_id BIGINT, + publisher_id BIGINT, CONSTRAINT fk_book_publisher - FOREIGN KEY (publisher_id) - REFERENCES Publisher(id) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + FOREIGN KEY (publisher_id) + REFERENCES Publisher (id) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4; -- -------------------------------------------------------- -- 3. Author Table -- -------------------------------------------------------- -- Stores author details. -- The Address fields (street, city, zip) are embedded directly here. -CREATE TABLE IF NOT EXISTS Author ( - ssn VARCHAR(255) NOT NULL PRIMARY KEY, - name VARCHAR(255) NOT NULL, +CREATE TABLE IF NOT EXISTS Author +( + ssn VARCHAR(255) NOT NULL PRIMARY KEY, + name VARCHAR(255) NOT NULL, -- Embedded Address fields street VARCHAR(255), - city VARCHAR(255), - zip VARCHAR(255) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + city VARCHAR(255), + zip VARCHAR(255) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4; -- -------------------------------------------------------- -- 4. Author_Book (Join Table) -- -------------------------------------------------------- -- Handles the Many-to-Many relationship between Authors and Books. -- One author can write many books, and one book can have multiple authors. -CREATE TABLE IF NOT EXISTS Author_Book ( - authors_ssn VARCHAR(255) NOT NULL, - books_isbn VARCHAR(255) NOT NULL, +CREATE TABLE IF NOT EXISTS Author_Book +( + authors_ssn VARCHAR(255) NOT NULL, + books_isbn VARCHAR(255) NOT NULL, PRIMARY KEY (authors_ssn, books_isbn), CONSTRAINT fk_ab_author - FOREIGN KEY (authors_ssn) - REFERENCES Author(ssn) - ON DELETE CASCADE, + FOREIGN KEY (authors_ssn) + REFERENCES Author (ssn) + ON DELETE CASCADE, CONSTRAINT fk_ab_book - FOREIGN KEY (books_isbn) - REFERENCES Book(isbn) - ON DELETE CASCADE - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + FOREIGN KEY (books_isbn) + REFERENCES Book (isbn) + ON DELETE CASCADE +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4; diff --git a/src/main/resources/db/migration/V0.0.1__Sample_Data.sql b/src/main/resources/db/migration/V0.0.1__Sample_Data.sql index fc8cb65..5ea66fa 100644 --- a/src/main/resources/db/migration/V0.0.1__Sample_Data.sql +++ b/src/main/resources/db/migration/V0.0.1__Sample_Data.sql @@ -3,11 +3,16 @@ -- -------------------------------------------------------- -- 1. Insert Publishers -INSERT INTO Publisher (id, name) VALUES (1, 'Penguin Classics'); -INSERT INTO Publisher (id, name) VALUES (2, 'Bloomsbury Publishing'); -INSERT INTO Publisher (id, name) VALUES (3, 'Allen & Unwin'); -INSERT INTO Publisher (id, name) VALUES (4, 'Time USA, LLC'); -INSERT INTO Publisher (id, name) VALUES (5, 'Massachusetts Medical Society'); +INSERT INTO Publisher (id, name) +VALUES (1, 'Penguin Classics'); +INSERT INTO Publisher (id, name) +VALUES (2, 'Bloomsbury Publishing'); +INSERT INTO Publisher (id, name) +VALUES (3, 'Allen & Unwin'); +INSERT INTO Publisher (id, name) +VALUES (4, 'Time USA, LLC'); +INSERT INTO Publisher (id, name) +VALUES (5, 'Massachusetts Medical Society'); -- 2. Insert Authors -- Note: Address fields are embedded directly in the Author table @@ -36,7 +41,8 @@ VALUES ('978-0441172719', 'Dune', '1965-08-01', 'In the week before their depart -- Novel (J.K. Rowling) INSERT INTO Book (isbn, title, publicationDate, text, type, publisher_id) -VALUES ('978-0747532743', 'Harry Potter and the Philosopher\'s Stone', '1997-06-26', 'Mr. and Mrs. Dursley, of number four, Privet Drive...', 'NOVEL', 2); +VALUES ('978-0747532743', 'Harry Potter and the Philosopher\'s Stone', '1997-06-26', 'Mr. and Mrs. Dursley, of number + four, Privet Drive...', 'NOVEL', 2); -- Novel (J.R.R. Tolkien) INSERT INTO Book (isbn, title, publicationDate, text, type, publisher_id) diff --git a/src/main/resources/docs/full.adoc b/src/main/resources/docs/full.adoc index 360bd3f..9e7aad4 100644 --- a/src/main/resources/docs/full.adoc +++ b/src/main/resources/docs/full.adoc @@ -16,13 +16,11 @@ All requests start with: `{{ server(0).url }}/library`. {{ statusCode({ 200: "Default/Success", 201: "Created", 404: "Not found", 400: "Invalid Request"}) | list }} -{% for tag in tags %} -== {{ tag.name }} +{% for tag in tags %} == {{ tag.name }} {{ tag.description }} -{% for route in tag.routes %} -=== {{ loop.index + 1 }}. {{ route.summary }} +{% for route in tag.routes %} === {{ loop.index + 1 }}. {{ route.summary }} {{ route.description }} @@ -46,17 +44,18 @@ Returns a {{ route | response | link }} object. .Response {{ route | response | example | json }} -{% if route.security is not empty %} -.Required Permissions +{% if route.security is not empty %} .Required Permissions + [cols="1,3"] |=== |Type | Scopes {% for scheme in route.security %} - {% for req in scheme %} +{% for req in scheme %} | *{{ req.key }}* | {{ req.value | join(", ") }} - {% endfor %} +{% endfor %} {% endfor %} |=== + {% endif -%} {% endfor -%} {% endfor %} @@ -64,13 +63,18 @@ Returns a {{ route | response | link }} object. == 📖 Reference === Book Types -When viewing book details, you will see a `type` field. Here is what those categories mean: + +When viewing book details, you will see a `type` field. +Here is what those categories mean: {{schema("Book.type") | table }} === Schemas + {% for schema in schemas %} + [id="{{ schema.name }}"] ==== {{ schema.name }} + {{ schema | table }} {% endfor %} diff --git a/src/main/resources/docs/index.adoc b/src/main/resources/docs/index.adoc index 35b5b72..6e8be98 100644 --- a/src/main/resources/docs/index.adoc +++ b/src/main/resources/docs/index.adoc @@ -9,12 +9,12 @@ All requests start with: `{{ server(0).url }}/library` - == 🔍 Finding & Browsing Books + {%- set quickSearch= GET("/library/search") -%} -{%- set paginated = GET("/library/books") -%} -{%- set book = GET("/library/books/{isbn}") -%} -{%- set addBook = POST("/library/books") %} +{%- set paginated = GET("/library/books") -%} +{%- set book = GET("/library/books/{isbn}") -%} +{%- set addBook = POST("/library/books") %} === 1. {{ quickSearch.summary }} @@ -67,7 +67,9 @@ All requests start with: `{{ server(0).url }}/library` {{ addBook | response(400) | json }} == 📖 Reference: Book Types -When viewing book details, you will see a `type` field. Here is what those categories mean: + +When viewing book details, you will see a `type` field. +Here is what those categories mean: {{schema("Book.type") | table }} diff --git a/src/main/resources/docs/tryIt.adoc b/src/main/resources/docs/tryIt.adoc index d2c13e1..f0b246b 100644 --- a/src/main/resources/docs/tryIt.adoc +++ b/src/main/resources/docs/tryIt.adoc @@ -33,29 +33,24 @@ All requests start with: `{{ server(0).url }}/library` *Endpoint:* `{{ route.method }} {{ route.path }}` {# 1. Path Parameters Section (filtered) #} -{% if route | parameters("path") is not empty %} -.Path Parameters +{% if route | parameters("path") is not empty %} .Path Parameters {{ route | parameters("path") | table }} {% endif %} {# 2. Query Parameters Section (filtered) #} -{% if route | parameters("query") is not empty %} -.Query Parameters +{% if route | parameters("query") is not empty %} .Query Parameters {{ route | parameters("query") | table }} {% endif %} {# 3. Request Body (only for POST/PUT) #} -{% if route.body is not null %} -.Request Body +{% if route.body is not null %} .Request Body {{ route | request | body | example | json }} {% endif %} -{# 4. Response Example #} -.Response (200 OK) +{# 4. Response Example #} .Response (200 OK) {{ route | response(200) | body | example | json }} -{# 5. Usage Example (cURL) #} -.Try it +{# 5. Usage Example (cURL) #} .Try it {{ route | curl(language="bash") }} {% endfor %}