From 1151b0e45e3174adb5b0245a8eba4dfbaa4a9085 Mon Sep 17 00:00:00 2001 From: Thomas Peetz Date: Sun, 1 Jun 2025 18:32:15 +0200 Subject: [PATCH] Add field published_on and title to Issue /refs #16 Add field published_on and title to Issue and display both fields. --- kontor-api/src/db/models/comic.py | 2 + .../src/templates/comic/issue_detail.html | 12 ++ kontor-scripts/db/models/comic.py | 129 ++++++++++-------- kontor-spring/build.gradle | 3 +- kontor-spring/gradle/libs.versions.toml | 2 + .../de/thpeetz/kontor/comics/data/Issue.java | 17 ++- .../kontor/comics/views/IssueForm.java | 28 +++- .../kontor/comics/views/IssueView.java | 6 + .../kontor/tysc/services/SportService.java | 7 +- 9 files changed, 138 insertions(+), 68 deletions(-) diff --git a/kontor-api/src/db/models/comic.py b/kontor-api/src/db/models/comic.py index e5e3851..f498571 100644 --- a/kontor-api/src/db/models/comic.py +++ b/kontor-api/src/db/models/comic.py @@ -95,6 +95,8 @@ class StoryArc(Base, BaseMixin): class Issue(Base, BaseMixin): __tablename__ = "issue" issue_number = Column(String) + title = Column(String, nullable=True) + published_on: Mapped[datetime] = mapped_column(nullable=True) in_stock = Column(Boolean) is_read = Column(Boolean) comic_id = Column(String, ForeignKey("comic.id"), nullable=False) diff --git a/kontor-api/src/templates/comic/issue_detail.html b/kontor-api/src/templates/comic/issue_detail.html index bbaa42d..75514fe 100644 --- a/kontor-api/src/templates/comic/issue_detail.html +++ b/kontor-api/src/templates/comic/issue_detail.html @@ -20,6 +20,14 @@ Issue Number {{issue.issue_number}} + + Full Title + {{issue.title}} + + + Published + {{issue.published_on}} + Auf Lager @@ -56,6 +64,10 @@ Data Modified {{issue.last_modified_date}} + + Data Version + {{issue.version}} + diff --git a/kontor-scripts/db/models/comic.py b/kontor-scripts/db/models/comic.py index 35e1efa..07da295 100644 --- a/kontor-scripts/db/models/comic.py +++ b/kontor-scripts/db/models/comic.py @@ -35,14 +35,15 @@ class Publisher(Base): self.parent_publisher_id = import_data['parent_publisher_id'] def export_dict(self) -> Dict[AnyStr, Any]: - item: Dict[AnyStr, Any] = {} - item['id'] = self.id - item['created_date'] = str(self.created_date) - item['last_modified_date'] = str(self.last_modified_date) - item['version'] = self.version - item['name'] = self.name - item['weblink'] = self.weblink - item['parent_publisher_id'] = self.parent_publisher_id + item: Dict[AnyStr, Any] = { + 'id': self.id, + 'created_date': str(self.created_date), + 'last_modified_date': str(self.last_modified_date), + 'version': self.version, + 'name': self.name, + 'weblink': self.weblink, + 'parent_publisher_id': self.parent_publisher_id + } return item @@ -79,16 +80,17 @@ class Comic(Base, BaseMixin): self.weblink = import_data['weblink'] def export_dict(self) -> Dict[AnyStr, Any]: - item: Dict[AnyStr, Any] = {} - item['id'] = self.id - item['created_date'] = str(self.created_date) - item['last_modified_date'] = str(self.last_modified_date) - item['version'] = self.version - item['title'] = self.title - item['publisher_id'] = self.publisher_id - item['current_order'] = self.current_order - item['completed'] = self.completed - item['weblink'] = self.weblink + item: Dict[AnyStr, Any] = { + 'id': self.id, + 'created_date': str(self.created_date), + 'last_modified_date': str(self.last_modified_date), + 'version': self.version, + 'title': self.title, + 'publisher_id': self.publisher_id, + 'current_order': self.current_order, + 'completed': self.completed, + 'weblink': self.weblink + } return item @@ -168,20 +170,23 @@ class StoryArc(Base, BaseMixin): self.volume_id = import_data['volume_id'] def export_dict(self) -> Dict[AnyStr, Any]: - item: Dict[AnyStr, Any] = {} - item['id'] = self.id - item['created_date'] = str(self.created_date) - item['last_modified_date'] = str(self.last_modified_date) - item['version'] = self.version - item['name'] = self.name - item['comic_id'] = self.comic_id - item['volume_id'] = self.volume_id + item: Dict[AnyStr, Any] = { + 'id': self.id, + 'created_date': str(self.created_date), + 'last_modified_date': str(self.last_modified_date), + 'version': self.version, + 'name': self.name, + 'comic_id': self.comic_id, + 'volume_id': self.volume_id + } return item class Issue(Base, BaseMixin): __tablename__ = "issue" issue_number = Column(String) + title = Column(String, nullable=True) + published_on: Mapped[datetime] = mapped_column(nullable=True) in_stock = Column(Boolean) is_read = Column(Boolean) comic_id = Column(String, ForeignKey("comic.id"), nullable=False) @@ -197,6 +202,8 @@ class Issue(Base, BaseMixin): self.last_modified_date = import_data['last_modified_date'] self.version = import_data['version'] self.issue_number = import_data['issue_number'] + self.title = import_data['title'] + self.published_on = import_data['published_on'] self.in_stock = import_data['in_stock'] self.is_read = import_data['is_read'] self.comic_id = import_data['comic_id'] @@ -204,17 +211,20 @@ class Issue(Base, BaseMixin): self.story_arc_id = import_data['story_arc_id'] def export_dict(self) -> Dict[AnyStr, Any]: - item: Dict[AnyStr, Any] = {} - item['id'] = self.id - item['created_date'] = str(self.created_date) - item['last_modified_date'] = str(self.last_modified_date) - item['version'] = self.version - item['issue_number'] = self.issue_number - item['in_stock'] = self.in_stock - item['is_read'] = self.is_read - item['comic_id'] = self.comic_id - item['volume_id'] = self.volume_id - item['story_arc_id'] = self.story_arc_id + item: Dict[AnyStr, Any] = { + 'id': self.id, + 'created_date': str(self.created_date), + 'last_modified_date': str(self.last_modified_date), + 'version': self.version, + 'issue_number': self.issue_number, + 'title': self.title, + 'published_on': str(self.published_on), + 'in_stock': self.in_stock, + 'is_read': self.is_read, + 'comic_id': self.comic_id, + 'volume_id': self.volume_id, + 'story_arc_id': self.story_arc_id + } return item @@ -234,13 +244,14 @@ class Artist(Base, BaseMixin): self.weblink = import_data['weblink'] def export_dict(self) -> Dict[AnyStr, Any]: - item: Dict[AnyStr, Any] = {} - item['id'] = self.id - item['created_date'] = str(self.created_date) - item['last_modified_date'] = str(self.last_modified_date) - item['version'] = self.version - item['name'] = self.name - item['weblink'] = self.weblink + item: Dict[AnyStr, Any] = { + 'id': self.id, + 'created_date': str(self.created_date), + 'last_modified_date': str(self.last_modified_date), + 'version': self.version, + 'name': self.name, + 'weblink': self.weblink + } return item @@ -263,12 +274,13 @@ class WorkType(Base, BaseMixin): self.name = import_data['name'] def export_dict(self) -> Dict[AnyStr, Any]: - item: Dict[AnyStr, Any] = {} - item['id'] = self.id - item['created_date'] = str(self.created_date) - item['last_modified_date'] = str(self.last_modified_date) - item['version'] = self.version - item['name'] = self.name + item: Dict[AnyStr, Any] = { + 'id': self.id, + 'created_date': str(self.created_date), + 'last_modified_date': str(self.last_modified_date), + 'version': self.version, + 'name': self.name + } return item @@ -291,12 +303,13 @@ class ComicWork(Base, BaseMixin): self.work_type_id = import_data['work_type_id'] def export_dict(self) -> Dict[AnyStr, Any]: - item: Dict[AnyStr, Any] = {} - item['id'] = self.id - item['created_date'] = str(self.created_date) - item['last_modified_date'] = str(self.last_modified_date) - item['version'] = self.version - item['comic_id'] = self.comic_id - item['artist_id'] = self.artist_id - item['work_type_id'] = self.work_type_id + item: Dict[AnyStr, Any] = { + 'id': self.id, + 'created_date': str(self.created_date), + 'last_modified_date': str(self.last_modified_date), + 'version': self.version, + 'comic_id': self.comic_id, + 'artist_id': self.artist_id, + 'work_type_id': self.work_type_id + } return item diff --git a/kontor-spring/build.gradle b/kontor-spring/build.gradle index 844fdc1..585e603 100644 --- a/kontor-spring/build.gradle +++ b/kontor-spring/build.gradle @@ -64,7 +64,8 @@ dependencies { implementation 'com.h2database:h2' implementation libs.hsqldb implementation 'org.postgresql:postgresql' - runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' + //runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' + implementation libs.hypersistence implementation libs.mail implementation libs.jackson implementation libs.gson diff --git a/kontor-spring/gradle/libs.versions.toml b/kontor-spring/gradle/libs.versions.toml index 8f74e77..39ad0b9 100644 --- a/kontor-spring/gradle/libs.versions.toml +++ b/kontor-spring/gradle/libs.versions.toml @@ -24,6 +24,7 @@ gson = "2.9.0" jackson = "2.16.1" json_simple = "1.1.1" mail = "1.6.2" +hypersistence = "3.9.10" [libraries] args4j = { module = "args4j:args4j", version.ref = "args4j" } @@ -40,6 +41,7 @@ jackson = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref json = { module = "com.googlecode.json-simple:json-simple", version.ref ="json_simple" } mail = { module = "com.sun.mail:javax.mail", version.ref ="mail" } sqlite-jdbc = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite" } +hypersistence = { module = "io.hypersistence:hypersistence-utils-hibernate-63", version.ref = "hypersistence" } vaadin-bom = { module = "com.vaadin:vaadin-bom", version.ref = "vaadin" } asciidoctorGradleJvmGems = { module = "org.asciidoctor:asciidoctor-gradle-jvm-gems", version.ref= "asciidoctor" } asciidoctorGradleJvm = { module = "org.asciidoctor:asciidoctor-gradle-jvm", version.ref= "asciidoctor" } diff --git a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/data/Issue.java b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/data/Issue.java index 02392e2..cea9663 100644 --- a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/data/Issue.java +++ b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/data/Issue.java @@ -2,8 +2,10 @@ package de.thpeetz.kontor.comics.data; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import de.thpeetz.kontor.common.data.AbstractEntity; -import io.micrometer.common.lang.Nullable; +import de.thpeetz.kontor.common.data.*; +import io.hypersistence.utils.hibernate.type.basic.YearMonthDateType; +import jakarta.annotation.Nullable; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; @@ -13,6 +15,9 @@ import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import lombok.ToString; +import org.hibernate.annotations.Type; + +import java.time.YearMonth; @Getter @Setter @@ -42,6 +47,14 @@ public class Issue extends AbstractEntity { @NotEmpty private String issueNumber; + @Nullable + private String title; + + @Nullable + @Type(YearMonthDateType.class) + @Column(name = "published_on", columnDefinition = "date") + private YearMonth publishedOn; + private Boolean isRead; private Boolean inStock; diff --git a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/IssueForm.java b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/IssueForm.java index 5bb117f..a54d9ec 100644 --- a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/IssueForm.java +++ b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/IssueForm.java @@ -1,5 +1,7 @@ package de.thpeetz.kontor.comics.views; +import java.time.*; +import java.time.format.*; import java.util.List; import com.vaadin.flow.component.ComponentEvent; @@ -9,12 +11,13 @@ import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.checkbox.Checkbox; import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.datepicker.*; import com.vaadin.flow.component.formlayout.FormLayout; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.textfield.TextField; -import com.vaadin.flow.data.binder.BeanValidationBinder; -import com.vaadin.flow.data.binder.Binder; +import com.vaadin.flow.data.binder.*; +import com.vaadin.flow.data.converter.*; import de.thpeetz.kontor.comics.data.*; import lombok.extern.slf4j.Slf4j; @@ -24,6 +27,8 @@ public class IssueForm extends FormLayout { ComboBox comic = new ComboBox<>("Comic"); ComboBox volume = new ComboBox<>("Volume"); TextField issueNumber = new TextField("Issue number"); + TextField title = new TextField("Full Title"); + TextField publishedOn = new TextField("Published"); Checkbox isRead = new Checkbox("Read"); Checkbox inStock = new Checkbox("In stock"); @@ -35,11 +40,28 @@ public class IssueForm extends FormLayout { public IssueForm(List comics) { addClassName("issue-form"); + binder.forField(publishedOn).withConverter(new Converter() { + @Override + public Result convertToModel(String value, ValueContext context) { + try { + YearMonth result = YearMonth.parse(value); + return Result.ok(result); + } catch (DateTimeParseException e) { + return Result.error("invalid year-month format"); + } + } + + @Override + public String convertToPresentation(YearMonth value, ValueContext context) { + if (value == null) return ""; + return value.format(DateTimeFormatter.ofPattern("yyyy-MM")); + } + }).bind(Issue::getPublishedOn, Issue::setPublishedOn); binder.bindInstanceFields(this); comic.setItems(comics); comic.setItemLabelGenerator(Comic::getTitle); - add(comic, volume, issueNumber, isRead, inStock, createButtonsLayout()); + add(comic, volume, issueNumber, title, publishedOn, isRead, inStock, createButtonsLayout()); } private HorizontalLayout createButtonsLayout() { diff --git a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/IssueView.java b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/IssueView.java index 64d7634..1978c9d 100644 --- a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/IssueView.java +++ b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/IssueView.java @@ -53,6 +53,10 @@ public class IssueView extends VerticalLayout { .setHeader("Volume").setResizable(true).setSortable(true); Grid.Column issueNumberColumn = grid.addColumn(Issue::getIssueNumber) .setHeader("Heft Nummer").setResizable(true).setSortable(true); + Grid.Column issueTitleColumn = grid.addColumn(Issue::getTitle) + .setHeader("Full Title").setResizable(true).setSortable(true); + Grid.Column publishedOncolumn = grid.addColumn(Issue::getPublishedOn) + .setHeader("Published").setResizable(true).setSortable(true); Grid.Column isReadColumn = grid.addComponentColumn(issueColumn -> StatusIcon.create(issueColumn.getIsRead())) .setHeader("Gelesen?").setWidth("6rem").setSortable(true); Grid.Column inStockColumn = grid.addComponentColumn(issueColumn -> StatusIcon.create(issueColumn.getInStock())) @@ -126,6 +130,8 @@ public class IssueView extends VerticalLayout { columnToggleContextMenu.addColumnToggleItem(titleColumn); columnToggleContextMenu.addColumnToggleItem(volumeColumn); columnToggleContextMenu.addColumnToggleItem(issueNumberColumn); + columnToggleContextMenu.addColumnToggleItem(issueTitleColumn); + columnToggleContextMenu.addColumnToggleItem(publishedOncolumn); columnToggleContextMenu.addColumnToggleItem(isReadColumn); columnToggleContextMenu.addColumnToggleItem(inStockColumn); HorizontalLayout toolbar = new HorizontalLayout(comicFilter, addIssueButton, menuButton); diff --git a/kontor-spring/src/main/java/de/thpeetz/kontor/tysc/services/SportService.java b/kontor-spring/src/main/java/de/thpeetz/kontor/tysc/services/SportService.java index 9536ec5..1fc7ff4 100644 --- a/kontor-spring/src/main/java/de/thpeetz/kontor/tysc/services/SportService.java +++ b/kontor-spring/src/main/java/de/thpeetz/kontor/tysc/services/SportService.java @@ -5,7 +5,6 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; -import de.thpeetz.kontor.admin.data.MetaDataTable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -177,7 +176,7 @@ public class SportService { } private String updateSportFields(Sport sport, Map fields) { - String status = ""; + StringBuilder status = new StringBuilder(); for (Map.Entry entry : fields.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); @@ -185,11 +184,11 @@ public class SportService { case "id", "created_date", "last_modified_date", "version": break; case "table_name": - status += sport.updateName(value); + status.append(sport.updateName(value)); default: log.info("field {} is unknown for table {}", key, sport.getClass().getName()); } } - return status; + return status.toString(); } } -- 2.18.0