diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2330d74 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.idea/ +__pycache__/ +bonus/ +icons/ +icons-shadowless/ +.vscode/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..7231f34 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,36 @@ +image: gradle:8.6-jdk21-alpine + +stages: + - build + - test + - publish + +# Disable the Gradle daemon for Continuous Integration servers as correctness +# is usually a priority over speed in CI environments. Using a fresh +# runtime for each build is more reliable since the runtime is completely +# isolated from any previous builds. +variables: + GRADLE_OPTS: "-Dorg.gradle.daemon=false" + +before_script: + - GRADLE_USER_HOME="$(pwd)/.gradle" + - export GRADLE_USER_HOME + +build: + stage: build + script: + - cd springboot + - gradle assemble --no-daemon + +test: + stage: test + script: + - cd springboot + - gradle check --no-daemon + +publish: + stage: publish + script: + - cd springboot + - gradle --no-daemon publish -PgitlabPackageRegistryUsername=gitlab-ci-token -PgitlabPackageRegistryPassword="$CI_JOB_TOKEN" + diff --git a/flask/.gitignore b/flask/.gitignore new file mode 100644 index 0000000..259ed2a --- /dev/null +++ b/flask/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +.idea diff --git a/flask/README.md b/flask/README.md new file mode 100644 index 0000000..6aad8a5 --- /dev/null +++ b/flask/README.md @@ -0,0 +1,2 @@ +# Kontor Flask + diff --git a/flask/app.py b/flask/app.py new file mode 100644 index 0000000..dc827b3 --- /dev/null +++ b/flask/app.py @@ -0,0 +1,5 @@ +from kontor import create_app + +if __name__ == '__main__': + app = create_app() + app.run(host="0.0.0.0", port=8000, debug=True) diff --git a/flask/kontor/__init__.py b/flask/kontor/__init__.py new file mode 100644 index 0000000..f8cb55c --- /dev/null +++ b/flask/kontor/__init__.py @@ -0,0 +1,61 @@ +from flask import Flask, render_template +from flask_jwt_extended import JWTManager + +from kontor import config +from kontor.extensions import db, ma +from logging.config import dictConfig + +dictConfig({ + 'version': 1, + 'formatters': {'default': { + 'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s', + }}, + 'handlers': {'wsgi': { + 'class': 'logging.StreamHandler', + 'stream': 'ext://flask.logging.wsgi_errors_stream', + 'formatter': 'default' + }}, + 'root': { + 'level': 'INFO', + 'handlers': ['wsgi'] + } +}) + +app = Flask(__name__) + + +def create_app(config_class=config.Config): + app.config.from_object(config_class) + + db.init_app(app) + ma.init_app(app) + # Initialize Flask extensions here + app.config["JWT_SECRET_KEY"] = "super-secret" # Change this! + jwt = JWTManager(app) + + with app.app_context(): + # db.create_all() + db.reflect() + + # Register blueprints here + from kontor.main import bp as main_bp + app.register_blueprint(main_bp) + from kontor.comics import comics_bp + app.register_blueprint(comics_bp, url_prefix='/comics') + from kontor.api import api_bp + app.register_blueprint(api_bp, url_prefix='/api/v1') + from kontor.comics import comics_api + app.register_blueprint(comics_api, url_prefix='/api/v1/comics') + + from kontor.media import media_bp + app.register_blueprint(media_bp, url_prefix='/media') + from kontor.media import media_api + app.register_blueprint(media_api, url_prefix='/api/v1/media') + # from kontor.auth.auth import auth_bp + # from kontor.cart.cart import cart_bp + # from kontor.general.general import general_bp + # app.register_blueprint(auth_bp) + # app.register_blueprint(cart_bp, url_prefix='/cart') + # app.register_blueprint(general_bp) + + return app diff --git a/flask/kontor/api/__init__.py b/flask/kontor/api/__init__.py new file mode 100644 index 0000000..b26fcb6 --- /dev/null +++ b/flask/kontor/api/__init__.py @@ -0,0 +1,5 @@ +from flask import Blueprint + +api_bp = Blueprint('api_bp', __name__) + +from kontor.api import routes diff --git a/flask/kontor/api/routes.py b/flask/kontor/api/routes.py new file mode 100644 index 0000000..4e9b489 --- /dev/null +++ b/flask/kontor/api/routes.py @@ -0,0 +1,30 @@ +from flask import jsonify, request +from flask_jwt_extended import jwt_required, get_jwt_identity, create_access_token + +from kontor.api import api_bp + + +@api_bp.route('/') +def index(): + modules = ['comics'] + return jsonify(modules) + + +# Create a route to authenticate your users and return JWTs. The +# create_access_token() function is used to actually generate the JWT. +@api_bp.route("/login", methods=["POST"]) +def login(): + username = request.json.get("username", None) + password = request.json.get("password", None) + if username != "test" or password != "test": + return jsonify({"msg": "Bad username or password"}), 401 + + access_token = create_access_token(identity=username) + return jsonify(access_token=access_token) + + +@api_bp.route('/protected', methods=['GET']) +@jwt_required() +def protected(): + current_user = get_jwt_identity() + return {'message': f'Hello, {current_user}!'} diff --git a/flask/kontor/auth/__init__.py b/flask/kontor/auth/__init__.py new file mode 100644 index 0000000..9da7988 --- /dev/null +++ b/flask/kontor/auth/__init__.py @@ -0,0 +1,22 @@ +import bcrypt +from flask import session +from flask_httpauth import HTTPBasicAuth + +from kontor import app +from kontor.auth.models import User + +auth = HTTPBasicAuth() + + +@auth.verify_password +def verify_password(username, password): + if username is None: + return False + # Add your authentication logic here + app.logger.info("login user %s", username) + user = User.query.filter_by(user_name=username).first() + app.logger.info("User: %s", user) + app.logger.info("Stored Password: '%s' Hashed Password: '%s'", user.password, bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())) + if 'user_name' in session and session['user_name'] == username: + return True + return False \ No newline at end of file diff --git a/flask/kontor/auth/models.py b/flask/kontor/auth/models.py new file mode 100644 index 0000000..fc3b8d4 --- /dev/null +++ b/flask/kontor/auth/models.py @@ -0,0 +1,45 @@ +from kontor.extensions import db, ma +from sqlalchemy.sql import func + + +class User(db.Model): + # __table__ = db.metadata.tables["publisher"] + __tablename__ = "user" + __table_args__ = {'extend_existing': True} + id = db.Column(db.String, primary_key=True) + created_date = db.Column(db.DateTime(timezone=True), server_default=func.now()) + last_modified_date = db.Column(db.DateTime(timezone=True), server_default=func.now()) + version = db.Column(db.Integer) + enabled = db.Column(db.SmallInteger) + email = db.Column(db.String) + first_name = db.Column(db.String) + last_name = db.Column(db.String) + user_name = db.Column(db.String) + password = db.Column(db.String) + token = db.Column(db.String) + token_expired = db.Column(db.SmallInteger) + + def is_token_valid(self): + return self.review == 'b\x01' + + def is_user_enabled(self): + return self.should_download == 'b\x01' + +class Role(db.Model): + __tablename__ = "role" + __table_args__ = {'extend_existing': True} + id = db.Column(db.String, primary_key=True) + created_date = db.Column(db.DateTime(timezone=True), server_default=func.now()) + last_modified_date = db.Column(db.DateTime(timezone=True), server_default=func.now()) + version = db.Column(db.Integer) + name = db.Column(db.String) + +class AuthorizationMatrix(db.Model): + __tablename__ = "authorization_matrix" + __table_args__ = {'extend_existing': True} + id = db.Column(db.String, primary_key=True) + created_date = db.Column(db.DateTime(timezone=True), server_default=func.now()) + last_modified_date = db.Column(db.DateTime(timezone=True), server_default=func.now()) + version = db.Column(db.Integer) + role_id = db.Column(db.String, db.ForeignKey("role.id")) + user_id = db.Column(db.String, db.ForeignKey("user.id")) diff --git a/flask/kontor/comics/__init__.py b/flask/kontor/comics/__init__.py new file mode 100644 index 0000000..12bb371 --- /dev/null +++ b/flask/kontor/comics/__init__.py @@ -0,0 +1,9 @@ +from flask import Blueprint + +comics_bp = Blueprint('comics_bp', __name__, + template_folder='templates', + static_folder='static', static_url_path='assets') +comics_api = Blueprint('comics_api', __name__) + +from kontor.comics import routes +from kontor.comics import api diff --git a/flask/kontor/comics/api.py b/flask/kontor/comics/api.py new file mode 100644 index 0000000..22bca28 --- /dev/null +++ b/flask/kontor/comics/api.py @@ -0,0 +1,28 @@ +from flask import Blueprint, render_template, jsonify + +from kontor.comics import comics_api +from kontor.models import Comic, comics_schema, Publisher, comic_schema, publisher_schema, publishers_schema + + +@comics_api.route('/') +def index(): + comics = Comic.query.all() + return comics_schema.dump(comics) + + +@comics_api.route('/comic/') +def view(comic_id): + comic = Comic.query.get(comic_id) + return comic_schema.dump(comic) + + +@comics_api.route('/publisher/') +def publisher_all(): + publishers = Publisher.query.all() + return publishers_schema.dump(publishers) + + +@comics_api.route('/publisher/') +def publisher_detail(publisher_id): + publisher = Publisher.query.get(publisher_id) + return publisher_schema.dump(publisher) \ No newline at end of file diff --git a/flask/kontor/comics/routes.py b/flask/kontor/comics/routes.py new file mode 100644 index 0000000..0b0f53e --- /dev/null +++ b/flask/kontor/comics/routes.py @@ -0,0 +1,16 @@ +from flask import Blueprint, render_template + +from kontor.comics import comics_bp +from kontor.models import Comic + + +@comics_bp.route('/') +def index(): + comics = Comic.query.all() + return render_template('comics/list.html', comics=comics) + + +@comics_bp.route('/comic/') +def view(comic_id): + comic = Comic.query.get(comic_id) + return render_template('comics/view.html', comic=comic) diff --git a/flask/kontor/comics/templates/comics/list.html b/flask/kontor/comics/templates/comics/list.html new file mode 100644 index 0000000..c0f4922 --- /dev/null +++ b/flask/kontor/comics/templates/comics/list.html @@ -0,0 +1,28 @@ +{% extends 'base.html' %} + +{% block content %} + +

{% block title %} Comics {% endblock %}

+
+
+

Comics Blueprint

+ {% for comic in comics %} +
+ +

+ {{ comic.title }} +

+
+

{{ comic.publisher.name }}

+
+

Created

+

{{ comic.created_date }}

+

Modified

+

{{ comic.last_modified_date }}

+
+

Version: {{ comic.version }}

+

Review: {{ comic.review }}

+
+ {% endfor %} +
+{% endblock %} diff --git a/flask/kontor/comics/templates/comics/view.html b/flask/kontor/comics/templates/comics/view.html new file mode 100644 index 0000000..b70da14 --- /dev/null +++ b/flask/kontor/comics/templates/comics/view.html @@ -0,0 +1,27 @@ +{% extends 'base.html' %} + +{% block content %} + +

{% block title %} Comics {% endblock %}

+
+
+

Comics Blueprint

+
+ +

+ {{ comic.title }} +

+
+ +

{{ comic.id }}

+
+
+

Created

+

{{ comic.created_date }}

+

Modified

+

{{ comic.last_modified_date }}

+
+

Version: {{ comic.version }}

+
+
+{% endblock %} diff --git a/flask/kontor/config.py b/flask/kontor/config.py new file mode 100644 index 0000000..8382f66 --- /dev/null +++ b/flask/kontor/config.py @@ -0,0 +1,10 @@ +import os + +basedir = os.path.abspath(os.path.dirname(__file__)) + + +class Config: + SECRET_KEY = os.environ.get('SECRET_KEY') + # SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URI') or 'sqlite:///' + os.path.join(basedir, 'kontor.db') + SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URI') or 'mariadb+mariadbconnector://kontor:kontor@localhost/kontor' + SQLALCHEMY_TRACK_MODIFICATIONS = False diff --git a/flask/kontor/extensions.py b/flask/kontor/extensions.py new file mode 100644 index 0000000..461bd5d --- /dev/null +++ b/flask/kontor/extensions.py @@ -0,0 +1,5 @@ +import flask_sqlalchemy +from flask_marshmallow import Marshmallow + +db = flask_sqlalchemy.SQLAlchemy() +ma = Marshmallow() diff --git a/flask/kontor/main/__init__.py b/flask/kontor/main/__init__.py new file mode 100644 index 0000000..ebdaf6e --- /dev/null +++ b/flask/kontor/main/__init__.py @@ -0,0 +1,5 @@ +from flask import Blueprint + +bp = Blueprint('main', __name__) + +from kontor.main import routes diff --git a/flask/kontor/main/routes.py b/flask/kontor/main/routes.py new file mode 100644 index 0000000..0c2e5dd --- /dev/null +++ b/flask/kontor/main/routes.py @@ -0,0 +1,18 @@ +from flask import render_template, session + +from kontor import app +from kontor.main import bp +from kontor.auth import auth + + +@bp.route('/') +@auth.login_required +def index(): + return render_template('index.html') + +@bp.route('/logout') +def logout(): + app.logger.info("logout") + auth.current_user() + session['user_name'] = None + return render_template('index.html') diff --git a/flask/kontor/media/__init__.py b/flask/kontor/media/__init__.py new file mode 100644 index 0000000..581af5f --- /dev/null +++ b/flask/kontor/media/__init__.py @@ -0,0 +1,9 @@ +from flask import Blueprint + +media_bp = Blueprint('media_bp', __name__, + template_folder='templates', + static_folder='static', static_url_path='assets') +media_api = Blueprint('media_api', __name__) + +from kontor.media import routes +from kontor.media import api diff --git a/flask/kontor/media/api.py b/flask/kontor/media/api.py new file mode 100644 index 0000000..be9b875 --- /dev/null +++ b/flask/kontor/media/api.py @@ -0,0 +1,18 @@ +from flask import Blueprint, render_template, jsonify + +from kontor import app +from kontor.media import media_api +from kontor.media.models import MediaFile, mediafile_schema, mediafiles_schema + + +@media_api.route('/') +def mediafile_list(): + app.logger.info("get all media files") + files = MediaFile.query.all() + return mediafiles_schema.dump(files) + + +@media_api.route('/mediafile/') +def mediafile_detail(file_id): + file = MediaFile.query.get(file_id) + return mediafile_schema.dump(file) diff --git a/flask/kontor/media/models.py b/flask/kontor/media/models.py new file mode 100644 index 0000000..4e54978 --- /dev/null +++ b/flask/kontor/media/models.py @@ -0,0 +1,43 @@ +from kontor.extensions import db, ma +from sqlalchemy.sql import func + + +class MediaFile(db.Model): + # __table__ = db.metadata.tables["publisher"] + __tablename__ = "media_file" + __table_args__ = {'extend_existing': True} + id = db.Column(db.String, primary_key=True) + created_date = db.Column(db.DateTime(timezone=True), server_default=func.now()) + last_modified_date = db.Column(db.DateTime(timezone=True), server_default=func.now()) + version = db.Column(db.Integer) + title = db.Column(db.String) + file_name = db.Column(db.String) + url = db.Column(db.String) + path = db.Column(db.String) + cloud_link = db.Column(db.String) + review = db.Column(db.SmallInteger) + should_download = db.Column(db.Boolean) + + def is_review(self): + return self.review == 'b\x00' + + def is_download(self): + return self.should_download == 'b\x00' + + +class MediaFileSchema(ma.SQLAlchemySchema): + class Meta: + model = MediaFile + fields = ("id", "created_date", "last_modified_date", "version", "title", "file_name", "url", "path", "cloud_link", "review", "should_download", "_links") + + # Smart hyperlinking + _links = ma.Hyperlinks( + { + "self": ma.URLFor("media_api.mediafile_detail", values=dict(mediafile_id="")), + "collection": ma.URLFor("media_api.mediafile_list"), + } + ) + + +mediafile_schema = MediaFileSchema() +mediafiles_schema = MediaFileSchema(many=True) diff --git a/flask/kontor/media/routes.py b/flask/kontor/media/routes.py new file mode 100644 index 0000000..a32f5e3 --- /dev/null +++ b/flask/kontor/media/routes.py @@ -0,0 +1,16 @@ +from flask import Blueprint, render_template + +from kontor.media import media_bp +from kontor.media.models import MediaFile + + +@media_bp.route('/') +def mediafile_list(): + files = MediaFile.query.all() + return render_template('media/mediafile_list.html', mediafiles=files) + + +@media_bp.route('/mediafile/') +def mediafile_detail(mediafile_id): + mediafile = MediaFile.query.get(mediafile_id) + return render_template('media/mediafile_detail.html', mediafile=mediafile) diff --git a/flask/kontor/media/templates/media/mediafile_detail.html b/flask/kontor/media/templates/media/mediafile_detail.html new file mode 100644 index 0000000..e3474ae --- /dev/null +++ b/flask/kontor/media/templates/media/mediafile_detail.html @@ -0,0 +1,29 @@ +{% extends 'base.html' %} + +{% block content %} + +

{% block title %} MediaFile {% endblock %}

+
+
+

MediaFile Blueprint

+
+ +

+ {{ mediafile.title }} +

+
+ +

{{ mediafile.id }}

+
+
+

Created

+

{{ mediafile.created_date }}

+

Modified

+

{{ mediafile.last_modified_date }}

+
+

Version: {{ mediafile.version }}

+

Review: {{ mediafile.is_review() }}

+

Should Download: {{mediafile.is_download() }}

+
+
+{% endblock %} diff --git a/flask/kontor/media/templates/media/mediafile_list.html b/flask/kontor/media/templates/media/mediafile_list.html new file mode 100644 index 0000000..2d7d29f --- /dev/null +++ b/flask/kontor/media/templates/media/mediafile_list.html @@ -0,0 +1,30 @@ +{% extends 'base.html' %} + +{% block content %} + +

{% block title %} MediaFile {% endblock %}

+
+
+

MediaFile Blueprint

+ {% for mediafile in mediafiles %} +
+ +

+ {{ mediafile.title }} +

+
+

{{ mediafile.id }}

+
+

Created

+

{{ mediafile.created_date }}

+

Modified

+

{{ mediafile.last_modified_date }}

+
+

Version: {{ mediafile.version }}

+

Review: {{ mediafile.is_review() }}

+

Should Download: {{ mediafile.is_download() }}

+

Links: {{ mediafile._links }}

+
+ {% endfor %} +
+{% endblock %} diff --git a/flask/kontor/models.py b/flask/kontor/models.py new file mode 100644 index 0000000..eba153c --- /dev/null +++ b/flask/kontor/models.py @@ -0,0 +1,49 @@ +from kontor.extensions import db, ma +from sqlalchemy.sql import func + + +class Publisher(db.Model): + # __table__ = db.metadata.tables["publisher"] + __tablename__ = "publisher" + __table_args__ = {'extend_existing': True} + id = db.Column(db.String, primary_key=True) + created_date = db.Column(db.DateTime(timezone=True), server_default=func.now()) + last_modified_date = db.Column(db.DateTime(timezone=True), server_default=func.now()) + version = db.Column(db.Integer) + name = db.Column(db.String) + comics = db.relationship('Comic', back_populates='publisher', lazy='dynamic') + + +class PublisherSchema(ma.SQLAlchemySchema): + class Meta: + model = Publisher + + comics = ma.List(ma.HyperlinkRelated("comics_api.index")) + + +publisher_schema = PublisherSchema() +publishers_schema = PublisherSchema(many=True) + + +class Comic(db.Model): + # __table__ = db.metadata.tables["comic"] + __tablename__ = "comic" + __table_args__ = {'extend_existing': True} + id = db.Column(db.String, primary_key=True) + created_date = db.Column(db.DateTime(timezone=True), server_default=func.now()) + last_modified_date = db.Column(db.DateTime(timezone=True), server_default=func.now()) + version = db.Column(db.Integer) + title = db.Column(db.String) + publisher_id = db.Column(db.String, db.ForeignKey("publisher.id")) + publisher = db.relationship("Publisher", back_populates="comics") + + +class ComicSchema(ma.SQLAlchemyAutoSchema): + class Meta: + model = Comic + include_fk = True + publisher = ma.HyperlinkRelated("comics_api.publisher_detail") + + +comic_schema = ComicSchema() +comics_schema = ComicSchema(many=True) diff --git a/flask/kontor/templates/base.html b/flask/kontor/templates/base.html new file mode 100644 index 0000000..0d7af2d --- /dev/null +++ b/flask/kontor/templates/base.html @@ -0,0 +1,68 @@ + + + + + {% block title %} {% endblock %} - FlaskApp + + + + +
+
+ {% block content %} {% endblock %} +
+ + diff --git a/flask/kontor/templates/index.html b/flask/kontor/templates/index.html new file mode 100644 index 0000000..e9210ee --- /dev/null +++ b/flask/kontor/templates/index.html @@ -0,0 +1,8 @@ +{% extends 'base.html' %} + +{% block content %} +

{% block title %} The Home Page of Kontor {% endblock %}

+
+

This is the main Flask blueprint

+
+{% endblock %} diff --git a/gui/comic_model.py b/gui/comic_model.py new file mode 100644 index 0000000..2472670 --- /dev/null +++ b/gui/comic_model.py @@ -0,0 +1,89 @@ +from datetime import datetime + +import mariadb +from PySide6.QtCore import QAbstractTableModel, QModelIndex, Qt +from PySide6.QtGui import QColor + + +class ComicTableModel(QAbstractTableModel): + + def __init__(self, db_config, main_window): + super().__init__() + self.main_window = main_window + self._data = [] + self.status_bar = main_window.statusBar + self.mariadb_conn = mariadb.connect( + host=db_config['mariadb']['host'], + port=db_config['mariadb']['port'], + user=db_config['mariadb']['user'], + password=db_config['mariadb']['password'], + database=db_config['mariadb']['database'] + ) + self.refresh() + + def refresh(self): + data = [] + cursor = self.mariadb_conn.cursor() + cursor.execute("SELECT id, created_date, last_modified_date, title, publisher_id FROM comic") + rows = cursor.fetchall() + for row in rows: + data.append(list(row)) + self.status_bar.showMessage(f"{len(rows)} Einträge geladen", 3000) + self._data = data + + def rowCount(self, parent=QModelIndex()): + # The length of the outer list. + return len(self._data) + + def headerData(self, col, orientation, role=Qt.ItemDataRole.DisplayRole): + if orientation == Qt.Orientation.Horizontal and role == Qt.ItemDataRole.DisplayRole: + match col: + case 0: + return "ID" + case 1: + return "Created" + case 2: + return "Updated" + case 3: + return "Title" + case 4: + return "Verlag" + if orientation == Qt.Orientation.Vertical and role == Qt.ItemDataRole.DisplayRole: + return str(col + 1) + + def data(self, index, role=Qt.ItemDataRole.DisplayRole): + value = self._data[index.row()][index.column()] + if role == Qt.ItemDataRole.DisplayRole: + if isinstance(value, datetime): + return value.strftime("%Y-%m-%d %M:%M:%S") + if isinstance(value, str): + return value + if isinstance(value, bytes): + if value == b'\x01': + return "True" + return "False" + return value + if role == Qt.ItemDataRole.DecorationRole: + if isinstance(value, bytes): + # print('{}: {}'.format(value, type(value))) + if value == b'\x01': + return self.main_window.tick + else: + return self.main_window.cross + + + def columnCount(self, index=QModelIndex()): + # The following takes the first sub-list, and returns + # the length (only works if all rows are an equal length) + return len(self._data[0]) + + def setData(self, index, value, role=Qt.ItemDataRole.EditRole): + if role == Qt.ItemDataRole.EditRole: + self._data[index.row()][index.column()] = value + if role == Qt.ItemDataRole.CheckStateRole: + checked = value == Qt.CheckState.Checked + self._data[index.row()][index.column()] = checked + return True + + def flags(self, index): + return Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsEditable | Qt.ItemFlag.ItemIsUserTristate diff --git a/gui/data.py b/gui/data.py new file mode 100644 index 0000000..bb4436c --- /dev/null +++ b/gui/data.py @@ -0,0 +1,79 @@ +import mariadb + + +class KontorDB: + + def __init__(self, db_config): + self.db_conn = mariadb.connect( + host=db_config['mariadb']['host'], + port=db_config['mariadb']['port'], + user=db_config['mariadb']['user'], + password=db_config['mariadb']['password'], + database=db_config['mariadb']['database'] + ) + + def get_table_id(self, table_name): + cursor = self.db_conn.cursor() + cursor.execute("SELECT id, created_date, last_modified_date FROM meta_data_table WHERE table_name=?", (table_name, )) + row = cursor.fetchone() + cursor.close() + return row[0] + + def get_table_names(self) -> list: + tables_names = [] + cursor = self.db_conn.cursor() + cursor.execute("SELECT id, table_name from meta_data_table") + rows = cursor.fetchall() + for (_, table_name) in rows: + tables_names.append(table_name) + cursor.close() + return tables_names + + def get_column_meta_data(self, table_id): + cursor = self.db_conn.cursor() + meta_data = {} + cursor.execute("SELECT column_name, column_order, column_label FROM meta_data_column WHERE table_id=? AND is_shown is true ORDER bY column_order", (table_id, )) + rows = cursor.fetchall() + order = 0 + for (column_name, column_order, column_label) in rows: + meta_data[order] = { 'column': column_name, 'label': column_label, 'order': column_order} + order += 1 + cursor.close() + # print(f"retrieved {len(rows)} columns, set {len(meta_data)} headers") + return meta_data + + def get_filters(self, table_id): + cursor = self.db_conn.cursor() + filters = {} + cursor.execute("SELECT column_name, filter_label from meta_data_column WHERE table_id=? AND show_filter is true", (table_id, )) + rows = cursor.fetchall() + for row in rows: + filters[row[0]] = {'label': row[1], 'widget': None} + cursor.close() + # print(f"retrieved {len(rows)} filters: {filters}") + return filters + + def get_data(self, table_name: str, columns: dict, where_clause: str) -> list: + data = [] + cursor = self.db_conn.cursor() + cursor.execute(self.get_statement(table_name, columns, where_clause)) + rows = cursor.fetchall() + print(len(rows)) + for row in rows: + # print(f"KontorDB.get_data: {row}") + data.append(list(row)) + cursor.close() + print(f"KontorDB.getData: return {len(data)}") + return data + + def get_statement(self, table: str, header: dict, where_clause): + columns = "" + for index, column in header.items(): + if index > 0: + columns += ", " + columns += column['column'] + if len(columns) == 0: + columns = "*" + statement = f"SELECT {columns} FROM {table} {where_clause}" + print(f"{statement=}") + return statement diff --git a/gui/dialogs.py b/gui/dialogs.py new file mode 100644 index 0000000..703baeb --- /dev/null +++ b/gui/dialogs.py @@ -0,0 +1,106 @@ +from pathlib import Path + +from PySide6.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QHBoxLayout, QPushButton, QFileDialog, \ + QGroupBox, QCheckBox, QComboBox + + +class ExportKontorDialog(QDialog): + def __init__(self, parent=None, kontor_db=None): + super().__init__(parent) + + self.parent = parent + self.kontor_db = kontor_db + self.file_name = None + self.tables = [] + self._table_options = {} + + self.export_options = {"JSON": {"ext": ".json"}, "YAML": {"ext": ".yaml"}, "SQLite": {"ext": ".db"}} + self.current_export_type = "JSON" + + buttons = (QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + + self.buttonBox = QDialogButtonBox(buttons) + self.buttonBox.accepted.connect(self.accept) + self.buttonBox.rejected.connect(self.reject) + + layout = QVBoxLayout() + + self.label = QLabel() + self.label.setText("Export DB to data.json") + + self.combo_box = QComboBox() + self.combo_box.addItems(["JSON", "YAML", "SQLite"]) + self.combo_box.currentTextChanged.connect(self.change_export_type) + file_layout = QHBoxLayout() + file_layout.addWidget(self.label) + file_layout.addWidget(self.combo_box) + file_button = QPushButton("Select file") + file_button.clicked.connect(self.select_file) + file_layout.addWidget(file_button) + layout.addLayout(file_layout) + + for table_name in self.kontor_db.get_table_names(): + check_box = QCheckBox(table_name) + check_box.setChecked(True) + self.tables.append(table_name) + self._table_options[table_name] = check_box + check_box.stateChanged.connect(self.change_selection) + layout.addWidget(check_box) + layout.addWidget(self.buttonBox) + self.setLayout(layout) + + def change_selection(self): + self.tables.clear() + for (name, box) in self._table_options.items(): + if box.isChecked(): + self.tables.append(name) + + def change_export_type(self, text): + self.current_export_type = text + self.label.setText(f'Export DB to data.{self.export_options[text]["ext"]}') + + def select_file(self): + file_dialog = QFileDialog() + file_dialog.setFileMode(QFileDialog.FileMode.AnyFile) + file_dialog.setDefaultSuffix(self.export_options[self.current_export_type]["ext"]) + file_dialog.setNameFilter(f'*{self.export_options[self.current_export_type]["ext"]}') + if file_dialog.exec(): + self.file_name = file_dialog.selectedFiles()[0] + export_file = Path(self.file_name) + self.file_name = export_file.with_suffix(self.export_options[self.current_export_type]["ext"]) + self.label.setText(f"Export DB to {self.file_name}") + + def get_tables_to_export(self) -> list: + return self.tables + + +class ImportKontorDialog(QDialog): + def __init__(self, parent=None): + super().__init__(parent) + + self.file_name = None + + QBtn = (QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + + self.buttonBox = QDialogButtonBox(QBtn) + self.buttonBox.accepted.connect(self.accept) + self.buttonBox.rejected.connect(self.reject) + + self.label = QLabel() + self.label.setText("Import DB from data.json") + layout = QVBoxLayout() + file_layout = QHBoxLayout() + file_layout.addWidget(self.label) + file_button = QPushButton("Select file") + file_button.clicked.connect(self.select_file) + file_layout.addWidget(file_button) + layout.addLayout(file_layout) + layout.addWidget(self.buttonBox) + self.setLayout(layout) + + def select_file(self): + file_dialog = QFileDialog() + file_dialog.setFileMode(QFileDialog.FileMode.ExistingFile) + if file_dialog.exec(): + self.file_name = file_dialog.selectedFiles()[0] + self.label.setText(f"Import DB from {self.file_name}") diff --git a/gui/kontor.py b/gui/kontor.py new file mode 100644 index 0000000..30bb3bd --- /dev/null +++ b/gui/kontor.py @@ -0,0 +1,151 @@ +""" +PyQT6 GUI for Kontor +""" +import sys +from pathlib import Path + +import yaml +from PySide6.QtGui import QAction, QIcon +from PySide6.QtWidgets import QWidget, QVBoxLayout, QMenu, QMessageBox, QTabWidget, QTableView +from PySide6.QtWidgets import QApplication, QLabel, QMainWindow +from platformdirs import PlatformDirs + +from comic_model import ComicTableModel +from dialogs import ExportKontorDialog, ImportKontorDialog +from data import KontorDB +from model_config import KontorModelConfig +from table_model import KontorTableModel + + +class MainWindow(QMainWindow): + + def __init__(self, config): + super().__init__() + + self.tick = QIcon('res/tick.png') + self.cross = QIcon('res/cross.png') + self.import_icon = QIcon("res/application-import.png") + self.export_icon = QIcon("res/application-export.png") + self.circle_icon = QIcon("res/arrow-circle-double.png") + + self.setWindowTitle("Kontor") + self.setMinimumSize(800, 500) + self._create_actions() + self._create_menubar() + self._create_toolbars() + self._create_statusbar() + + self.data = [] + self.filter = {} + self.kontor_db = KontorDB(config) + self.central_widget = QWidget() + parent_layout = QVBoxLayout() + self.central_widget.setLayout(parent_layout) + self.tabs = QTabWidget() + self.tabs.addTab(self.generate_data_tab("comic"), "Comics") + self.tabs.addTab(self.generate_data_tab("media_file"), "MediaFile") + self.tabs.currentChanged.connect(self._tab_changed) + #label.setAlignment(Qt.AlignmentFlag.AlignCenter) + parent_layout.addWidget(self.tabs) + + self.setCentralWidget(self.central_widget) + + def _create_actions(self): + self.newAction = QAction("&New", self) + self.aboutAction = QAction("&Über...", self) + self.aboutAction.triggered.connect(self.about) + self.importAction = QAction(self.import_icon, "&Import", self) + self.importAction.triggered.connect(self.import_from_file) + self.exportAction = QAction(self.export_icon, "&Export", self) + self.exportAction.triggered.connect(self.export_to_file) + self.refreshAction = QAction(self.circle_icon, "&Refresh", self) + self.refreshAction.triggered.connect(self.refresh) + self.updateTitleAction = QAction("&Update Titles", self) + self.downloadAction = QAction("&Download Videos", self) + self.exitAction = QAction("&Beenden", self) + self.exitAction.setShortcut("Alt+F4") + self.exitAction.triggered.connect(self.close) + + def _create_menubar(self): + menu_bar = self.menuBar() + # File menu + file_menu = QMenu("&Datei") + menu_bar.addMenu(file_menu) + file_menu.addAction(self.exitAction) + # Kontor menu + kontor_menu = QMenu("&Kontor") + menu_bar.addMenu(kontor_menu) + kontor_menu.addAction(self.importAction) + kontor_menu.addAction(self.exportAction) + media_file_menu = QMenu("&MediaFile") + media_file_menu.addAction(self.updateTitleAction) + media_file_menu.addAction(self.downloadAction) + kontor_menu.addMenu(media_file_menu) + # Help menu + help_menu = QMenu("&Hilfe") + menu_bar.addMenu(help_menu) + help_menu.addAction(self.aboutAction) + + def _create_toolbars(self): + # Kontor toolbar + kontor_tool_bar = self.addToolBar("Kontor") + kontor_tool_bar.addAction(self.importAction) + kontor_tool_bar.addAction(self.exportAction) + kontor_tool_bar.addAction(self.refreshAction) + + def _create_statusbar(self): + self.statusBar = self.statusBar() + self.statusBar.showMessage("Kontor ready", 6000) + self.status_label = QLabel("") + self.statusBar.addPermanentWidget(self.status_label) + + def about(self): + QMessageBox.about(self.central_widget, "Über Kontor", f"Python: 3.11\nKontor: 0.1.0") + + def import_from_file(self): + import_dlg = ImportKontorDialog(self) + if import_dlg.exec(): + print(f"import DB from file {import_dlg.file_name}") + else: + print("no nothing for import") + pass + + def export_to_file(self): + export_dlg = ExportKontorDialog(self, self.kontor_db) + if export_dlg.exec(): + print(export_dlg.get_tables_to_export()) + print(f"export DB to {export_dlg.file_name}") + self.statusBar.showMessage(f"export DB to {export_dlg.file_name}", 3000) + else: + self.statusBar.showMessage("Export cancelled", 3000) + + def refresh(self): + self.data[self.tabs.currentIndex()].refresh() + + def _tab_changed(self, tab_index): + self.data[tab_index].refresh() + + def generate_data_tab(self, table_name): + data_tab = QWidget() + table_config = KontorModelConfig(self.kontor_db, self, table_name) + model = KontorTableModel(table_config) + layout = QVBoxLayout() + self.data.append(model) + data_tab.setLayout(layout) + table_view = QTableView() + table_view.setModel(model) + layout.addLayout(table_config.get_filter_layout()) + layout.addWidget(table_view) + model.refresh() + return data_tab + + +if __name__ == '__main__': + app = QApplication(sys.argv) + dirs = PlatformDirs("kontor") + database_config = Path(dirs.user_config_dir, 'database-config.yaml') + with open(database_config, 'rt') as f: + db_config = yaml.safe_load(f.read()) + window = MainWindow(db_config) + window.show() + app.exec() diff --git a/gui/model_config.py b/gui/model_config.py new file mode 100644 index 0000000..ba3e510 --- /dev/null +++ b/gui/model_config.py @@ -0,0 +1,58 @@ +import mariadb +from PySide6.QtWidgets import QHBoxLayout, QCheckBox + +from data import KontorDB + + +class KontorModelConfig: + + def __init__(self, kontor_db: KontorDB, main_window, table_name: str): + self.header = {} + self.filter = {} + self.main_window = main_window + self._table = table_name + self._table_id = None + self.kontor_db = kontor_db + self.get_table_config() + + def get_table_id(self): + if self._table_id is not None: + return + self._table_id = self.kontor_db.get_table_id(self._table) + + def get_table_config(self): + if self._table_id is None: + self.get_table_id() + self.header = self.kontor_db.get_column_meta_data(self._table_id) + self.filter = self.kontor_db.get_filters(self._table_id) + + def get_filter(self) -> str: + filter_rule = "" + # print(self.filter["download"].isChecked()) + for column, filter_info in self.filter.items(): + # print(column, filter_info) + if filter_info['widget'].isChecked(): + # print(column, filter_info, filter_rule, len(filter_rule)) + if len(filter_rule) < 1: + filter_rule += "WHERE " + if len(filter_rule) > 8: + filter_rule += " AND " + filter_rule += f"{column} is true" + # print(f"{filter_rule=}") + return filter_rule + + def get_data(self) -> list: + data = self.kontor_db.get_data(self._table, self.header, self.get_filter()) + print(f"KontorModelConfig.get_data: {len(data)}") + return data + + def get_filter_layout(self) -> QHBoxLayout: + filter_layout = QHBoxLayout() + for column, filter_info in self.filter.items(): + filter_checkbox = QCheckBox() + filter_checkbox.setText(filter_info['label']) + filter_checkbox.checkStateChanged.connect(self.main_window.refresh) + self.filter[column]['widget'] = filter_checkbox + filter_layout.addWidget(filter_checkbox) + filter_layout.addStretch() + return filter_layout diff --git a/gui/res/application-export.png b/gui/res/application-export.png new file mode 100644 index 0000000..555887a Binary files /dev/null and b/gui/res/application-export.png differ diff --git a/gui/res/application-import.png b/gui/res/application-import.png new file mode 100644 index 0000000..922cb07 Binary files /dev/null and b/gui/res/application-import.png differ diff --git a/gui/res/arrow-circle-double.png b/gui/res/arrow-circle-double.png new file mode 100644 index 0000000..ba5ebd1 Binary files /dev/null and b/gui/res/arrow-circle-double.png differ diff --git a/gui/res/cross.png b/gui/res/cross.png new file mode 100644 index 0000000..6b9fa6d Binary files /dev/null and b/gui/res/cross.png differ diff --git a/gui/res/tick.png b/gui/res/tick.png new file mode 100644 index 0000000..2414885 Binary files /dev/null and b/gui/res/tick.png differ diff --git a/gui/resources.py b/gui/resources.py new file mode 100644 index 0000000..c135d07 --- /dev/null +++ b/gui/resources.py @@ -0,0 +1,7 @@ +from PySide6.QtGui import QIcon + +tick = QIcon('tick.png') +cross = QIcon('cross.png') +import_icon = QIcon("application-import.png") +export_icon = QIcon("application-export.png") +circle_icon = QIcon("arrow-circle-double.png") diff --git a/gui/table_model.py b/gui/table_model.py new file mode 100644 index 0000000..a28e1cc --- /dev/null +++ b/gui/table_model.py @@ -0,0 +1,83 @@ +from datetime import datetime + +from PySide6.QtCore import QAbstractTableModel, QModelIndex +from PySide6.QtGui import Qt + +from model_config import KontorModelConfig + + +class KontorTableModel(QAbstractTableModel): + + def __init__(self, model_config: KontorModelConfig): + super().__init__() + self._main_window = model_config.main_window + self._config = model_config + self._data = [] + + def refresh(self): + data = self._config.get_data() + count = 0 + # print(data) + if data is not None: + self.beginResetModel() + self._data.clear() + self._data = data + self.endResetModel() + count = len(data) + # print(data) + # print(self._data) + self.layoutChanged.emit() + self._main_window.statusBar.showMessage(f"{count} Einträge geladen", 3000) + + def rowCount(self, parent=QModelIndex()): + # The length of the outer list. + if self._data is None: + return 0 + return len(self._data) + + def headerData(self, col, orientation, role=Qt.ItemDataRole.DisplayRole): + if orientation == Qt.Orientation.Horizontal and role == Qt.ItemDataRole.DisplayRole: + return self._config.header[col]['label'] + if orientation == Qt.Orientation.Vertical and role == Qt.ItemDataRole.DisplayRole: + return str(col+1) + + def data(self, index, role=Qt.ItemDataRole.DisplayRole): + if self._data is None: + return None + value = self._data[index.row()][index.column()] + if role == Qt.ItemDataRole.DisplayRole: + # print('{}: {}'.format(value, type(value))) + if isinstance(value, datetime): + return value.strftime("%Y-%m-%d %M:%M:%S") + if isinstance(value, str): + return value + if isinstance(value, bytes): + if value == b'\x01': + return self._main_window.tick + else: + return self._main_window.cross + return str(value) + if role == Qt.ItemDataRole.DecorationRole: + if isinstance(value, bytes): + # print('{}: {}'.format(value, type(value))) + if value == b'\x01': + return self._main_window.tick + else: + return self._main_window.cross + + def columnCount(self, index=QModelIndex()): + # The following takes the first sub-list, and returns + # the length (only works if all rows are an equal length) + # print(f"Header count: {len(self._config.get_header())}") + return len(self._config.header) + + def setData(self, index, value, role=Qt.ItemDataRole.EditRole): + if role == Qt.ItemDataRole.EditRole: + self._data[index.row()][index.column()] = value + if role == Qt.ItemDataRole.CheckStateRole: + checked = value == Qt.CheckState.Checked + self._data[index.row()][index.column()] = checked + return True + + def flags(self, index): + return Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsEditable | Qt.ItemFlag.ItemIsUserTristate diff --git a/kontor.tjp b/kontor.tjp new file mode 100644 index 0000000..a5d46c6 --- /dev/null +++ b/kontor.tjp @@ -0,0 +1,30 @@ +project kontor "Kontor" "0.1.0" 2024-12-05 +5m { + timezone "Europe/Berlin" + timeformat "%d.%m.%Y" + numberformat "-" "" "" "," 1 + currencyformat "-" "" "" "," 0 + currency "EUR" + + scenario plan "Plan" { + scenario real "Realität" + } +} + +resource gcpce "Google Cloud Compute Engine" { + efficiency 0.0 + rate 0.25 +} + +task flask "Kontor-Flask" { + task import "Import repository kontor-flask into directory flask" +} +task springboot "Springboot Vaadin" { + task import "Import repository kontor-spring into directory springboot" +} + +taskreport "Arbeitsliste" { + formats html + hidetask ~isleaf() + sorttasks plan.end.up +} + diff --git a/springboot/.gitattributes b/springboot/.gitattributes new file mode 100644 index 0000000..097f9f9 --- /dev/null +++ b/springboot/.gitattributes @@ -0,0 +1,9 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + diff --git a/springboot/.gitignore b/springboot/.gitignore new file mode 100644 index 0000000..0294d4c --- /dev/null +++ b/springboot/.gitignore @@ -0,0 +1,32 @@ +.gradle/ +.settings/ +build/ +bin/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +.project +.classpath +.vscode/ +.idea/ +*.lock +logs/ +frontend/generated +frontend/index.html +package*.json +tsconfig.json +types.d.ts +node_modules/ +vite.* +kontor*Db +tags* +kontorHSQLDB* +.vs/ +.winget +src/main/resources/application-local.properties +src/main/resources/application-prod.properties +src/main/resources/application-*.yml diff --git a/springboot/README.md b/springboot/README.md new file mode 100644 index 0000000..f24bbcd --- /dev/null +++ b/springboot/README.md @@ -0,0 +1,3 @@ +# kontor-spring + +Kontor Anwendung mit Spring Boot und Vaadin \ No newline at end of file diff --git a/springboot/build.gradle b/springboot/build.gradle new file mode 100644 index 0000000..0624568 --- /dev/null +++ b/springboot/build.gradle @@ -0,0 +1,240 @@ +buildscript { + configurations.classpath { + resolutionStrategy.eachDependency { DependencyResolveDetails details -> + if (details.requested.group == 'com.burgstaller' && details.requested.name == 'okhttp-digest' && details.requested.version == '1.10') { + details.useTarget "io.github.rburgst:${details.requested.name}:1.21" + details.because 'Dependency has moved' + } + } + } + repositories { + mavenCentral() + maven { setUrl("https://maven.vaadin.com/vaadin-prereleases") } + maven { setUrl("https://repo.spring.io/milestone") } + } +} + +plugins { + id 'java' + id 'application' + id 'maven-publish' + id "com.google.cloud.artifactregistry.gradle-plugin" version "2.2.0" + id 'jvm-test-suite' + id 'jacoco' + id 'test-report-aggregation' + id 'jacoco-report-aggregation' + alias(libs.plugins.spring.boot) + alias(libs.plugins.spring.dependencies) + alias(libs.plugins.vaadin) + alias(libs.plugins.lombok) + alias(libs.plugins.asciidoctorPdf) + alias(libs.plugins.asciidoctorConvert) + alias(libs.plugins.asciidoctorGems) +} + +repositories { + mavenCentral() + ruby.gems() + maven { setUrl("https://maven.vaadin.com/vaadin-prereleases") } + maven { setUrl("https://repo.spring.io/milestone") } + maven { setUrl("https://maven.vaadin.com/vaadin-addons") } +} + +sourceCompatibility = '17' + +configurations { + developmentOnly + runtimeClasspath { + extendsFrom developmentOnly + } +} + +dependencies { + implementation 'com.vaadin:vaadin-core' + implementation 'com.vaadin:vaadin-spring-boot-starter' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-actuator' + developmentOnly 'org.springframework.boot:spring-boot-devtools' + implementation 'org.springframework.security:spring-security-oauth2-jose' + implementation 'org.springframework.security:spring-security-oauth2-resource-server' + implementation 'com.h2database:h2' + implementation libs.hsqldb + runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' + implementation 'com.sun.mail:javax.mail:1.6.2' + implementation 'org.hibernate.orm:hibernate-community-dialects' + testImplementation('org.springframework.boot:spring-boot-starter-test') { + exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' + } + testImplementation 'org.springframework.security:spring-security-test' + testImplementation 'com.vaadin:vaadin-testbench-junit5' + testImplementation 'io.projectreactor:reactor-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + asciidoctorGems libs.rouge + //asciidoctorGems libs.diagram +} + +def pdfFile = layout.buildDirectory.file("docs/asciidocPdf/kontor-spring.pdf") +def pdfArtifact = artifacts.add('archives', pdfFile.get().asFile) { + type 'pdf' + builtBy asciidoctorPdf +} + +publishing { + publications { + maven(MavenPublication) { + groupId = group + '.docs' + artifactId = project.name + artifact pdfArtifact + } + bootJava(MavenPublication) { + artifact tasks.named("bootDistTar") + } + } + repositories { + maven { + name = "gitlabPackageRegistry" + url = uri("https://gitlab.com/api/v4/projects/64726715/packages/maven") + credentials(PasswordCredentials) + } + } +} + +final BUILD_DATE = new Date().format('dd.MM.yyyy').toString() + +asciidoctorPdf { + dependsOn asciidoctorGemsPrepare + + baseDirFollowsSourceFile() + + asciidoctorj { + modules { + diagram.use() + } + requires 'rouge' + attributes 'build-gradle': file('build.gradle'), + 'endpoint-url': 'https://www.thpeetz.de', + 'source-highlighter': 'rouge', + 'imagesdir': './images', + 'toc': 'left', + 'toc-title': 'Inhaltsverzeichnis', + 'revdate': BUILD_DATE, + 'revnumber': { project.version.toString() }, + 'revremark': 'Entwurf', + 'chapter-label': '', + 'icons': 'font', + 'idprefix': 'id_', + 'idseparator': '-', + 'docinfo1': '' + } +} + +build.dependsOn asciidoctorPdf + +dependencyManagement { + imports { + mavenBom libs.vaadin.bom.get().toString() + } +} + +application { + mainClass = 'de.thpeetz.kontor.Application' +} + +bootRun { + args = ["--spring.profiles.active=${project.properties['profile'] ?: 'prod'}"] +} + +vaadin { + productionMode = true +} + +testing { + suites { + configureEach { + useJUnitJupiter() + dependencies { + implementation project() + implementation 'com.vaadin:vaadin-core' + implementation 'com.vaadin:vaadin-spring-boot-starter' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'com.h2database:h2' + implementation libs.hsqldb + implementation libs.sqlite.jdbc + //runtimeOnly 'com.mysql:mysql-connector-j' + runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' + implementation('org.springframework.boot:spring-boot-starter-test') { + exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' + } + implementation 'org.springframework.security:spring-security-test' + implementation 'com.vaadin:vaadin-testbench-junit5' + implementation 'io.projectreactor:reactor-test' + runtimeOnly 'org.junit.platform:junit-platform-launcher' + } + } + test(JvmTestSuite) { + testType = TestSuiteType.UNIT_TEST + targets { + all { + testTask.configure { + reports { + junitXml { + outputPerTestCase = true // defaults to false + mergeReruns = true // defaults to false + } + } + finalizedBy(jacocoTestReport) + } + } + } + } + integrationTest(JvmTestSuite) { + testType = "view-test" + targets { + all { + testTask.configure { + shouldRunAfter(test) + finalizedBy(jacocoTestReport) + } + } + } + } + } +} + +tasks.named('check') { + dependsOn(testing.suites.integrationTest) + dependsOn(testing.suites.test) + dependsOn tasks.named('testAggregateTestReport', TestReport) + dependsOn tasks.named('integrationTestAggregateTestReport', TestReport) +} + + +jacocoTestReport { + dependsOn test, integrationTest + reports { + xml.required = true + csv.required = false + } +} + +reporting { + reports { + testAggregateTestReport(AggregateTestReport) { + testType = TestSuiteType.UNIT_TEST + } + integrationTestAggregateTestReport(AggregateTestReport) { + testType = "view-test" + } + integrationTestCodeCoverageReport(JacocoCoverageReport) { + testType = "view-test" + } + } +} + +wrapper { + gradleVersion = "8.6" +} diff --git a/springboot/frontend/themes/kontor/styles.css b/springboot/frontend/themes/kontor/styles.css new file mode 100644 index 0000000..843559f --- /dev/null +++ b/springboot/frontend/themes/kontor/styles.css @@ -0,0 +1,10 @@ +@media all and (max-width: 1100px) { + .list-view.editing .toolbar, + .list-view.editing .contact-grid { + display: none; + } +} +a[highlight] { + font-weight: bold; + text-decoration: underline; +} diff --git a/springboot/frontend/themes/kontor/theme.json b/springboot/frontend/themes/kontor/theme.json new file mode 100644 index 0000000..0f7a81f --- /dev/null +++ b/springboot/frontend/themes/kontor/theme.json @@ -0,0 +1,3 @@ +{ + "lumoImports" : [ "typography", "color", "spacing", "badge", "utility" ] +} \ No newline at end of file diff --git a/springboot/gradle.properties b/springboot/gradle.properties new file mode 100644 index 0000000..f305204 --- /dev/null +++ b/springboot/gradle.properties @@ -0,0 +1,3 @@ +description='Kontor with Spring Boot' +version=0.1.0-SNAPSHOT +group=de.thpeetz diff --git a/springboot/gradle/libs.versions.toml b/springboot/gradle/libs.versions.toml new file mode 100644 index 0000000..6ec0bbe --- /dev/null +++ b/springboot/gradle/libs.versions.toml @@ -0,0 +1,58 @@ +[versions] +gradle = "8.6" +args4j = "2.33" +commonscli = "1.5.0" +junit = "5.8.2" +logback = "1.1.2" +mockito = "1.9.5" +picoli = "4.7.0" +slf4j = "1.7.22" +hsqldb = "2.7.1" +sqlite = "3.25.2" +spotbugs = "6.0.7" +asciidoctor = "4.0.2" +rouge = "3.15.0" +#diagram = "2.2.2" +diagram = "2.3.1" +sonarqube = "3.3" +cimtConventions = "1.0.0-SNAPSHOT" +springboot = "3.2.5" +springdependencies = "1.1.4" +vaadin = "24.3.8" +lombok = "8.6" + +[libraries] +args4j = { module = "args4j:args4j", version.ref = "args4j" } +commonscli = { module = "commons-cli:commons-cli", version.ref = "commonscli" } +junit = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } +logbackCore = { module = "ch.qos.logback:logback-core", version.ref = "logback" } +logbackClassic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } +mockito = { module = "org.mockito:mockito-all", version.ref = "mockito" } +picocli = { module = "info.picocli:picocli", version.ref = "picoli" } +slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } +hsqldb = { module = "org.hsqldb:hsqldb", version.ref = "hsqldb" } +sqlite-jdbc = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite" } +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" } +asciidoctorGradleJvmPdf = { module = "org.asciidoctor:asciidoctor-gradle-jvm-pdf", version.ref= "asciidoctor" } +rouge = { module = "rubygems:rouge", version.ref = "rouge" } +diagram = { module = "rubygems:asciidoctor-diagram", version.ref = "diagram" } + +[bundles] +logback = ["logbackCore", "logbackClassic"] + +[plugins] +spotbugs = { id = "com.github.spotbugs", version.ref = "spotbugs" } +sonarqube = { id = "org.sonarqube", version.ref = "sonarqube" } +asciidoctorPdf = { id = "org.asciidoctor.jvm.pdf", version.ref = "asciidoctor" } +asciidoctorConvert = { id = "org.asciidoctor.jvm.convert", version.ref = "asciidoctor" } +asciidoctorGems = { id = "org.asciidoctor.jvm.gems", version.ref = "asciidoctor" } +javaConvention = { id = "de.cimt.java-conventions", version.ref = "cimtConventions" } +applicationConvention = { id = "de.cimt.application-conventions", version.ref = "cimtConventions" } +libraryConvention = { id = "de.cimt.library-conventions", version.ref = "cimtConventions" } +asciidoctorConvention = { id = "de.cimt.asciidoctor-conventions", version.ref = "cimtConventions" } +spring-boot = { id = "org.springframework.boot", version.ref = "springboot"} +spring-dependencies = { id = "io.spring.dependency-management", version.ref = "springdependencies" } +vaadin = { id = "com.vaadin", version.ref = "vaadin" } +lombok = { id = "io.freefair.lombok", version.ref = "lombok" } diff --git a/springboot/gradle/wrapper/gradle-wrapper.jar b/springboot/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..d64cd49 Binary files /dev/null and b/springboot/gradle/wrapper/gradle-wrapper.jar differ diff --git a/springboot/gradle/wrapper/gradle-wrapper.properties b/springboot/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a80b22c --- /dev/null +++ b/springboot/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/springboot/gradlew b/springboot/gradlew new file mode 100755 index 0000000..1aa94a4 --- /dev/null +++ b/springboot/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/springboot/gradlew.bat b/springboot/gradlew.bat new file mode 100644 index 0000000..25da30d --- /dev/null +++ b/springboot/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/springboot/settings.gradle b/springboot/settings.gradle new file mode 100644 index 0000000..f559dac --- /dev/null +++ b/springboot/settings.gradle @@ -0,0 +1,24 @@ +pluginManagement { + resolutionStrategy { + eachPlugin { + if (requested.id.id == 'org.springframework.boot') { + useModule("org.springframework.boot:spring-boot-gradle-plugin:${requested.version}") + } + if (requested.id.id == 'org.gradle.toolchains.foojay-resolver') { + useModule("org.gradle.toolchains.foojay-resolver-convention:0.4.0") + } + } + } + repositories { + gradlePluginPortal() + mavenCentral() + maven { setUrl("https://maven.vaadin.com/vaadin-prereleases") } + maven { setUrl("https://repo.spring.io/milestone") } + maven { url 'https://plugins.gradle.org/m2/' } + } +// plugins { +// id 'com.vaadin' version "${vaadinVersion}" +// } +} + +rootProject.name = 'kontor-spring' diff --git a/springboot/src/docs/asciidoc/kontor-spring.adoc b/springboot/src/docs/asciidoc/kontor-spring.adoc new file mode 100644 index 0000000..bd4ee00 --- /dev/null +++ b/springboot/src/docs/asciidoc/kontor-spring.adoc @@ -0,0 +1,509 @@ += Projektbeschreibung kontor-spring: Entwicklungs- und Projekthandbuch +:author: Thomas Peetz +:email: +:doctype: book +:sectnums: +:sectnumlevels: 4 +:toc: +:toclevels: 4 +:table-caption!: +:counter: table-number: 0 + +[title="Dokumenthistorie", id="Table-{counter:table-number}", options="header"] +|=== +| Version | Datum | Autor | Änderungsgrund / Bemerkungen +| 1.0.0 | 16.05.2022 | Thomas Peetz | Ersterstellung +|=== + +== Allgemeines + +=== Zweck des Dokumentes + +Das Entwicklungshandbuch beschreibt die Werkzeuge und die Vorgehensweise bei der Entwicklung +im Projekt kontor-spring und der Erstellung der Dokumentation. + +=== Verwendete Tools + +==== Gitea + +Für die Verwaltung des Sourcecode kommt ((Gitea))<> zum Einsatz. +Mit Gitea werden auch die Projektaufgaben verwaltet. + +Das Projekt und das dazugehörige Git Repository sind unter der Adresse + +https://gitea.thpeetz.de/kontor/kontor-spring + +zu finden. + +== Erstellung der Dokumentation + +Die Dokumentation des Projektes wird mit ((Asciidoctor))<> geschrieben. +Die Dokumente erhalten ihre Namen nach dem jeweiligen Hauptdokument. + +=== Quellcode Verwaltung + +Die Asciidoctor-Dateien haben die Endung `.adoc`. + +=== Buildsystem + +Zur Erstellung der PDF-Dateien aus den Asciidoctor-Dateien wird das Buildsystem ((Gradle))<> verwendet. +Die Dateien für die Dokumente liegen im Verzeichnis `src/docs/asciidoc`. + +Der Gradle Build wird über die Datei `build.gradle` definiert. + + +== Einführung + +=== Zweck + +=== Stakeholder des Systems + +=== Systemumfang + +==== Zielsetzung des Systems + +=== Systemübersicht + +==== Systemkontext + +==== Systemarchitektur + +==== Systemschnittstellen + +===== Realisierte Schnittstellen + +===== Verwendete Schnittstellen + +==== Logisches Datenmodell + +===== Benutzer ER-Diagramm + +[mermaid, kontor-user-er, png] +.Benutzer ER-Diagramm +.... +erDiagram + user { + string id PK + datetime created_date + datetime last_modified_date + int version + string email + boolean enabled + string firstName + string lastName + string password + string token + boolean tokenExpired + string userName UNIQUE + } + role { + string id PK + datetime created_date + datetime last_modified_date + int version + string name + } + authorization_matrix { + string id PK + datetime created_date + datetime last_modified_date + int version + string user_id FK + string role_id FK + } + module_data { + string id PK + datetime created_date + datetime last_modified_date + int version + boolean import_data + string module_name UNIQUE + } + user ||--o{ authorization_matrix : "matrix" + role ||--o{ authorization_matrix : "matrix" +.... + + +===== Comics ER-Diagramm + +[mermaid, kontor-comics-er, png] +.Comics ER-Diagramm +.... +erDiagram + comic { + string id PK + datetime created_date + datetime last_modified_date + int version + boolean completed + boolean currentOrder + string title + string publisher_id FK + } + volume { + string id PK + datetime created_date + datetime last_modified_date + int version + string name + string comic_id FK + } + issue { + string id PK + datetime created_date + datetime last_modified_date + int version + boolean in_stock + boolean is_read + string issue_number + string comic_id FK + string volume_id FK + } + publisher { + string id PK + datetime created_date + datetime last_modified_date + int version + string name + } + artist { + string id PK + datetime created_date + datetime last_modified_date + int version + string name + } + story_arc { + string id PK + datetime created_date + datetime last_modified_date + int version + string name + string comic_id FK + } + trade_paperback { + string id PK + datetime created_date + datetime last_modified_date + int version + int issueStart + int issueEnd + string name + string comic_id FK + } + worktype { + string id PK + datetime created_date + datetime last_modified_date + int version + string name + } + comic_work { + string id PK + datetime created_date + datetime last_modified_date + int version + string artist_id FK + string comic_id FK + string worktype_id FK + } + comic ||--o{ comic_work : "1" + artist ||--o{ comic_work : "1" + worktype ||--o{ comic-work : "1" + publisher ||--o{ comic : "1" + comic ||--o{ issue : "1" + comic ||--o{ volume : "1" + comic ||--o{ story_arc : "1" + comic ||--o{ trade_paperback : "1" + volume ||--o{ issue : "1" +.... + +===== TYSC ER-Diagramm + +[mermaid, kontor-tysc-er, png] +.TYSC ER-Diagramm +.... +erDiagram + sport { + string id PK + datetime created_date + datetime last_modified_date + int version + string name + } + team { + string id PK + datetime created_date + datetime last_modified_date + int version + string name + string short_name + string sport_id FK + } + field_position { + string id PK + datetime created_date + datetime last_modified_date + int version + string name + string short_name + string sport_id FK + } + rooster { + string id PK + datetime created_date + datetime last_modified_date + int version + int year + string player_id FK + string position_id FK + string team_id FK + } + player { + string id PK + datetime created_date + datetime last_modified_date + int version + string first_name + string last_name + } + vendor { + string id PK + datetime created_date + datetime last_modified_date + int version + string name + } + card_set { + string id PK + datetime created_date + datetime last_modified_date + int version + boolean insert_set + string name + boolean parallel_set + string vendor_id FK + } + card { + string id PK + datetime created_date + datetime last_modified_date + int version + int cardNumber + int year + string card_set FK + string rooster_id FK + string vendor_id FK + } + sport ||--o{ team : "1" + sport ||--o{ field_position : "1" + field_position ||--o{ rooster : "1" + player ||--o{ rooster : "1" + team ||--o{ rooster : "1" + vendor ||--o{ card : "1" + card_set ||--o{ card : "1" + rooster ||--o{ card : "1" +.... + +===== Bookshelf ER-Diagramm + +[mermaid, kontor-bookshelf-er, png] +.Bookshelf ER-Diagramm +.... +erDiagram + article { + string id PK + datetime created_date + datetime last_modified_date + int version + string title + } + book { + string id PK + datetime created_date + datetime last_modified_date + int version + string isbn UNIQUE + string title + int year + string publisher_id FK + } + bookshelf_publisher { + string id PK + datetime created_date + datetime last_modified_date + int version + string name UNIQUE + } + author { + string id PK + datetime created_date + datetime last_modified_date + int version + string first_name + string last_name + } + article_author { + string id PK + datetime created_date + datetime last_modified_date + int version + string article_id FK + string author_id FK + } + book_author { + string id PK + datetime created_date + datetime last_modified_date + int version + string book_id FK + string author_id FK + } + publisher ||--o{ book : "1" + article ||--o{ article_author : "1" + author ||--o{ article_author : "1" + book ||--o{ book_author : "1" + author ||--o{ book_author : "1" +.... + +===== Mail ER-Diagramm + +[mermaid, kontor-mail-er, png] +.Mail ER-Diagramm +.... +erDiagram + mail { + string id PK + datetime created_date + datetime last_modified_date + int version + string subject + string content + datetime received_date + datetime sent_date + } + mail_account { + string id PK + datetime created_date + datetime last_modified_date + int version + string host + string password + int port + string protocol + boolean start_tls + string user_name + } + mail_address { + string id PK + datetime created_date + datetime last_modified_date + int version + string internet_address UNIQUE + string personal + string user_id FK + } + user ||--o{ mail_address : "1" +.... + +==== Einschränkungen + +== Anforderungen der Domäne + +=== Systemfunktionen + +==== Anwendungsfälle + +==== Akteure + +==== Zielgruppen + +=== Anforderungen + +==== Anforderungen an externe Schnittstellen + +==== Funktionale Anforderungen + +==== Qualitätsanforderungen + +==== Randbedingungen + +==== Weitere Anforderungen + +==== Wartungs- und Supportinformationen + +=== Verifikation + +== Projektbeschreibung + +=== Ausgangslage + +//==== Rechtliche Vorgaben und Rahmenbedingungen +//=== Rahmenbedingungen + +//==== Vorhandene Regelungen + +=== Projektziele + +=== Projektabgrenzung + +//=== Voraussichtliche Kosten + +//=== Projektrisiken + +//==== Produktivität + +//==== Finanzielle Risiken + +//==== Akzeptanz + +== Projektorganisation + +=== Projekt-Aufbauorganisation + +=== Rollendefinition + +//==== Projektauftraggeber + +//==== Projektausschuss + +//==== Beratung / Qualitätssicherung + +==== Projekteiter + +==== Projektteam + +==== Liste der Stakeholder + +=== Projektablauforganisation + +==== Projekt-Phasen + +===== Erstellung der Projektdokumentation + + +== Verschiedenes + +=== Erreichbarkeiten + +[bibliography] +== Referenzen + +- [[[asciidoctor]]] http://asciidoctor.org +- [[[gitea]]] http://www.gitea.org +- [[[gradle]]] http://www.gradle.org +- [[[jenkins]]] http://jenkins-ci.org + +[glossary] +== Glossar + +[index] +== Index + +== Verzeichnisse + +=== Abbildungsverzeichnis + +=== Tabellenverzeichnis + +<> <> diff --git a/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/ArtistViewTest.java b/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/ArtistViewTest.java new file mode 100644 index 0000000..e2eec07 --- /dev/null +++ b/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/ArtistViewTest.java @@ -0,0 +1,41 @@ +package de.thpeetz.kontor.comics.views; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import com.vaadin.flow.component.grid.Grid; + +import de.thpeetz.kontor.comics.data.Artist; + +@SpringBootTest +class ArtistViewTest { + + @Autowired + private ArtistView artistView; + + @Test + void formShownWhenArtistSelected() { + Grid grid = artistView.getGrid(); + Artist firstArtist = getFirstItem(grid); + + ArtistForm form = artistView.getForm(); + + assertFalse(form.isVisible()); + grid.asSingleSelect().setValue(firstArtist); + assertTrue(form.isVisible()); + assertEquals(firstArtist.getName(), form.name.getValue()); + } + + private Artist getFirstItem(Grid grid) { + int count = grid.getListDataView().getItemCount(); + List artists = grid.getListDataView().getItems().collect(Collectors.toList()); + assertEquals(5, count); + return artists.get(0); + } +} diff --git a/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/ArtistformTest.java b/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/ArtistformTest.java new file mode 100644 index 0000000..a6aea11 --- /dev/null +++ b/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/ArtistformTest.java @@ -0,0 +1,63 @@ +package de.thpeetz.kontor.comics.views; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import de.thpeetz.kontor.comics.data.Artist; + +@SpringBootTest +class ArtistformTest { + + private Artist artist1; + private static final String ARTISTNAME= "Lee, Stan"; + + @BeforeEach + void setupData() { + artist1 = new Artist(); + artist1.setName(ARTISTNAME); + } + + @Test + void formFieldsPopulated() { + ArtistForm form = new ArtistForm(); + form.setArtist(artist1); + assertEquals(ARTISTNAME, form.name.getValue()); + } + + @Test + void saveEventHasCorrectValues() { + ArtistForm form = new ArtistForm(); + Artist artist = new Artist(); + form.setArtist(artist); + form.name.setValue(ARTISTNAME); + + AtomicReference savedArtistReference = new AtomicReference<>(null); + form.addSaveListener(e -> { + savedArtistReference.set(e.getArtist()); + }); + form.save.click(); + Artist savedArtist = savedArtistReference.get(); + assertEquals(ARTISTNAME, savedArtist.getName()); + } + + @Test + void deleteEventHasCorrectValues() { + ArtistForm form = new ArtistForm(); + Artist artist = new Artist(); + form.setArtist(artist); + form.name.setValue(ARTISTNAME); + + AtomicReference deletedArtistReference = new AtomicReference<>(null); + form.addDeleteListener(e -> { + deletedArtistReference.set(e.getArtist()); + }); + form.delete.click(); + Artist deletedArtist = deletedArtistReference.get(); + assertEquals(ARTISTNAME, deletedArtist.getName()); + } +} diff --git a/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/ComicViewTest.java b/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/ComicViewTest.java new file mode 100644 index 0000000..084d64e --- /dev/null +++ b/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/ComicViewTest.java @@ -0,0 +1,43 @@ +package de.thpeetz.kontor.comics.views; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import com.vaadin.flow.component.grid.Grid; + +import de.thpeetz.kontor.comics.data.Comic; + +@SpringBootTest +public class ComicViewTest { + + @Autowired + private ComicView comicView; + + @Test + void formShownWhenComicSelected() { + Grid grid = comicView.getGrid(); + Comic firstComic = getFirstItem(grid); + + ComicForm form = comicView.getForm(); + + assertFalse(form.isVisible()); + grid.asSingleSelect().setValue(firstComic); + assertTrue(form.isVisible()); + assertEquals(firstComic.getTitle(), form.title.getValue()); + } + + private Comic getFirstItem(Grid grid) { + int count = grid.getListDataView().getItemCount(); + List comics = grid.getListDataView().getItems().collect(Collectors.toList()); + assertEquals(169, count); + return comics.get(0); + } +} diff --git a/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/ComicWorkViewTest.java b/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/ComicWorkViewTest.java new file mode 100644 index 0000000..0d3113e --- /dev/null +++ b/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/ComicWorkViewTest.java @@ -0,0 +1,43 @@ +package de.thpeetz.kontor.comics.views; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import com.vaadin.flow.component.grid.Grid; + +import de.thpeetz.kontor.comics.data.ComicWork; + +@SpringBootTest +class ComicWorkViewTest { + + @Autowired + private ComicWorkView comicWorkView; + + @Test + void formShownWhenComicSelected() { + Grid grid = comicWorkView.getGrid(); + ComicWork firstComicWork = getFirstItem(grid); + + ComicWorkForm form = comicWorkView.getForm(); + + assertFalse(form.isVisible()); + grid.asSingleSelect().setValue(firstComicWork); + assertTrue(form.isVisible()); + assertEquals(firstComicWork.getComic(), form.comic.getValue()); + } + + private ComicWork getFirstItem(Grid grid) { + int count = grid.getListDataView().getItemCount(); + List comicWorks = grid.getListDataView().getItems().collect(Collectors.toList()); + assertEquals(18, count); + return comicWorks.get(0); + } +} diff --git a/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/IssueViewTest.java b/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/IssueViewTest.java new file mode 100644 index 0000000..089f8f5 --- /dev/null +++ b/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/IssueViewTest.java @@ -0,0 +1,43 @@ +package de.thpeetz.kontor.comics.views; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import com.vaadin.flow.component.grid.Grid; + +import de.thpeetz.kontor.comics.data.Issue; + +@SpringBootTest +public class IssueViewTest { + + @Autowired + private IssueView issueView; + + @Test + void formShownWhenIssueSelected() { + Grid grid = issueView.getGrid(); + Issue firstIssue = getFirstItem(grid); + + IssueForm form = issueView.getForm(); + + assertFalse(form.isVisible()); + grid.asSingleSelect().setValue(firstIssue); + assertTrue(form.isVisible()); + assertEquals(firstIssue.getIssueNumber(), form.issueNumber.getValue()); + } + + private Issue getFirstItem(Grid grid) { + int count = grid.getListDataView().getItemCount(); + List issues = grid.getListDataView().getItems().collect(Collectors.toList()); + assertEquals(750, count); + return issues.get(0); + } +} diff --git a/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/PublisherViewTest.java b/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/PublisherViewTest.java new file mode 100644 index 0000000..b3bab80 --- /dev/null +++ b/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/PublisherViewTest.java @@ -0,0 +1,41 @@ +package de.thpeetz.kontor.comics.views; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import com.vaadin.flow.component.grid.Grid; + +import de.thpeetz.kontor.comics.data.Publisher; + +@SpringBootTest +class PublisherViewTest { + + @Autowired + private PublisherView publisherView; + + @Test + void formShownWhenPublisherSelected() { + Grid grid = publisherView.getGrid(); + Publisher firstPublisher = getFirstItem(grid); + + PublisherForm form = publisherView.getForm(); + + assertFalse(form.isVisible()); + grid.asSingleSelect().setValue(firstPublisher); + assertTrue(form.isVisible()); + assertEquals(firstPublisher.getName(), form.name.getValue()); + } + + private Publisher getFirstItem(Grid grid) { + int count = grid.getListDataView().getItemCount(); + List publishers = grid.getListDataView().getItems().collect(Collectors.toList()); + assertEquals(18, count); + return publishers.get(0); + } +} diff --git a/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/StoryArcViewTest.java b/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/StoryArcViewTest.java new file mode 100644 index 0000000..56f9c34 --- /dev/null +++ b/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/StoryArcViewTest.java @@ -0,0 +1,43 @@ +package de.thpeetz.kontor.comics.views; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import com.vaadin.flow.component.grid.Grid; + +import de.thpeetz.kontor.comics.data.StoryArc; + +@SpringBootTest +class StoryArcViewTest { + + @Autowired + private StoryArcView storyArcView; + + @Test + void formShownWhenStoryArcSelected() { + Grid grid = storyArcView.getGrid(); + StoryArc firstStoryArc = getFirstItem(grid); + + StoryArcForm form = storyArcView.getForm(); + + assertFalse(form.isVisible()); + grid.asSingleSelect().setValue(firstStoryArc); + assertTrue(form.isVisible()); + assertEquals(firstStoryArc.getName(), form.name.getValue()); + } + + private StoryArc getFirstItem(Grid grid) { + int count = grid.getListDataView().getItemCount(); + List storyArcs = grid.getListDataView().getItems().collect(Collectors.toList()); + assertEquals(3, count); + return storyArcs.get(0); + } +} diff --git a/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/TradePaperbackViewTest.java b/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/TradePaperbackViewTest.java new file mode 100644 index 0000000..a3b4119 --- /dev/null +++ b/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/TradePaperbackViewTest.java @@ -0,0 +1,47 @@ +package de.thpeetz.kontor.comics.views; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import com.vaadin.flow.component.grid.Grid; + +import de.thpeetz.kontor.comics.data.TradePaperback; +import de.thpeetz.kontor.comics.data.Volume; + +@SpringBootTest +class TradePaperbackViewTest { + + @Autowired + private TradePaperbackView tradePaperbackView; + + @Test + void formShownWhenVolumeSelected() { + Grid grid = tradePaperbackView.getGrid(); + + TradePaperback firstTradePaperback = getFirstItem(grid); + + TradePaperBackForm form = tradePaperbackView.getForm(); + assertFalse(form.isVisible()); + + if (firstTradePaperback != null) { + grid.asSingleSelect().setValue(firstTradePaperback); + assertTrue(form.isVisible()); + assertEquals(firstTradePaperback.getName(), form.name.getValue()); + } + } + + private TradePaperback getFirstItem(Grid grid) { + int count = grid.getListDataView().getItemCount(); + List tradePaperbacks = grid.getListDataView().getItems().collect(Collectors.toList()); + assertEquals(40, count); + return tradePaperbacks.get(0); + } +} diff --git a/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/VolumeViewTest.java b/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/VolumeViewTest.java new file mode 100644 index 0000000..15e8d15 --- /dev/null +++ b/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/VolumeViewTest.java @@ -0,0 +1,50 @@ +package de.thpeetz.kontor.comics.views; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import com.vaadin.flow.component.grid.Grid; + +import de.thpeetz.kontor.comics.data.Volume; + +@SpringBootTest +class VolumeViewTest { + + @Autowired + private VolumeView volumeView; + + @Test + void formShownWhenVolumeSelected() { + Grid grid = volumeView.getGrid(); + + Volume firstVolume = getFirstItem(grid); + + VolumeForm form = volumeView.getForm(); + assertFalse(form.isVisible()); + + if (firstVolume != null) { + grid.asSingleSelect().setValue(firstVolume); + assertTrue(form.isVisible()); + assertEquals(firstVolume.getName(), form.name.getValue()); + } + } + + private Volume getFirstItem(Grid grid) { + int count = grid.getListDataView().getItemCount(); + List volumes = grid.getListDataView().getItems().collect(Collectors.toList()); + assertEquals(0, count); + if (count > 0) { + return volumes.get(0); + } else { + return null; + } + } +} diff --git a/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/WorktypeViewTest.java b/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/WorktypeViewTest.java new file mode 100644 index 0000000..2f15672 --- /dev/null +++ b/springboot/src/integrationTest/java/de/thpeetz/kontor/comics/views/WorktypeViewTest.java @@ -0,0 +1,49 @@ +package de.thpeetz.kontor.comics.views; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import com.vaadin.flow.component.grid.Grid; + +import de.thpeetz.kontor.comics.data.Worktype; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@SpringBootTest +class WorktypeViewTest { + + @Autowired + private WorktypeView worktypeView; + + @Test + void formShownWhenWorktypeSelected() { + Grid grid = worktypeView.getGrid(); + + Worktype firstWorktype = getFirstItem(grid); + + WorktypeForm form = worktypeView.getForm(); + assertFalse(form.isVisible()); + + if (firstWorktype != null) { + grid.asSingleSelect().setValue(firstWorktype); + assertTrue(form.isVisible()); + assertEquals(firstWorktype.getName(), form.name.getValue()); + } + } + + private Worktype getFirstItem(Grid grid) { + int count = grid.getListDataView().getItemCount(); + List worktypes = grid.getListDataView().getItems().collect(Collectors.toList()); + log.info("found worktypes: {}", worktypes); + assertEquals(3, count); + return worktypes.get(0); + } +} diff --git a/springboot/src/integrationTest/java/de/thpeetz/kontor/tysc/views/CardSetViewTest.java b/springboot/src/integrationTest/java/de/thpeetz/kontor/tysc/views/CardSetViewTest.java new file mode 100644 index 0000000..40d3fbb --- /dev/null +++ b/springboot/src/integrationTest/java/de/thpeetz/kontor/tysc/views/CardSetViewTest.java @@ -0,0 +1,43 @@ +package de.thpeetz.kontor.tysc.views; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import com.vaadin.flow.component.grid.Grid; + +import de.thpeetz.kontor.tysc.data.CardSet; + +@SpringBootTest +class CardSetViewTest { + + @Autowired + private CardSetView cardSetView; + + @Test + void formShownWhenCardSetSelected() { + Grid grid = cardSetView.getGrid(); + CardSet firstCardSet = getFirstItem(grid); + + CardSetForm form = cardSetView.getForm(); + + assertFalse(form.isVisible()); + grid.asSingleSelect().setValue(firstCardSet); + assertTrue(form.isVisible()); + assertEquals(firstCardSet.getName(), form.name.getValue()); + } + + private CardSet getFirstItem(Grid grid) { + int count = grid.getListDataView().getItemCount(); + List cardSets = grid.getListDataView().getItems().collect(Collectors.toList()); + assertEquals(15, count); + return cardSets.get(0); + } +} diff --git a/springboot/src/integrationTest/java/de/thpeetz/kontor/tysc/views/CardViewTest.java b/springboot/src/integrationTest/java/de/thpeetz/kontor/tysc/views/CardViewTest.java new file mode 100644 index 0000000..0a5a3ed --- /dev/null +++ b/springboot/src/integrationTest/java/de/thpeetz/kontor/tysc/views/CardViewTest.java @@ -0,0 +1,43 @@ +package de.thpeetz.kontor.tysc.views; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import com.vaadin.flow.component.grid.Grid; + +import de.thpeetz.kontor.tysc.data.Card; + +@SpringBootTest +class CardViewTest { + + @Autowired + private CardView cardView; + + @Test + void formShownWhenCardSelected() { + Grid grid = cardView.getGrid(); + Card firstCard = getFirstItem(grid); + + CardForm form = cardView.getForm(); + + assertFalse(form.isVisible()); + grid.asSingleSelect().setValue(firstCard); + assertTrue(form.isVisible()); + assertEquals(String.valueOf(firstCard.getCardNumber()), form.cardNumber.getValue()); + } + + private Card getFirstItem(Grid grid) { + int count = grid.getListDataView().getItemCount(); + List cards = grid.getListDataView().getItems().collect(Collectors.toList()); + assertEquals(10, count); + return cards.get(0); + } +} diff --git a/springboot/src/integrationTest/java/de/thpeetz/kontor/tysc/views/FieldPositionViewTest.java b/springboot/src/integrationTest/java/de/thpeetz/kontor/tysc/views/FieldPositionViewTest.java new file mode 100644 index 0000000..737b74d --- /dev/null +++ b/springboot/src/integrationTest/java/de/thpeetz/kontor/tysc/views/FieldPositionViewTest.java @@ -0,0 +1,43 @@ +package de.thpeetz.kontor.tysc.views; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import com.vaadin.flow.component.grid.Grid; + +import de.thpeetz.kontor.tysc.data.FieldPosition; + +@SpringBootTest +class FieldPositionViewTest { + + @Autowired + private PositionView positionView; + + @Test + void formShownWhenPositionSelected() { + Grid grid = positionView.getGrid(); + FieldPosition firstFieldPosition = getFirstItem(grid); + + PositionForm form = positionView.getForm(); + + assertFalse(form.isVisible()); + grid.asSingleSelect().setValue(firstFieldPosition); + assertTrue(form.isVisible()); + assertEquals(firstFieldPosition.getName(), form.name.getValue()); + } + + private FieldPosition getFirstItem(Grid grid) { + int count = grid.getListDataView().getItemCount(); + List positions = grid.getListDataView().getItems().collect(Collectors.toList()); + assertEquals(44, count); + return positions.get(0); + } +} diff --git a/springboot/src/integrationTest/java/de/thpeetz/kontor/tysc/views/PlayerViewTest.java b/springboot/src/integrationTest/java/de/thpeetz/kontor/tysc/views/PlayerViewTest.java new file mode 100644 index 0000000..5e7d640 --- /dev/null +++ b/springboot/src/integrationTest/java/de/thpeetz/kontor/tysc/views/PlayerViewTest.java @@ -0,0 +1,44 @@ +package de.thpeetz.kontor.tysc.views; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import com.vaadin.flow.component.grid.Grid; + +import de.thpeetz.kontor.tysc.data.Player; + +@SpringBootTest +class PlayerViewTest { + + @Autowired + private PlayerView playerView; + + @Test + void formShownWhenPlayerSelected() { + Grid grid = playerView.getGrid(); + Player firstPlayer = getFirstItem(grid); + + PlayerForm form = playerView.getForm(); + + assertFalse(form.isVisible()); + grid.asSingleSelect().setValue(firstPlayer); + assertTrue(form.isVisible()); + assertEquals(firstPlayer.getLastName(), form.lastName.getValue()); + assertEquals(firstPlayer.getFirstName(), form.firstName.getValue()); + } + + private Player getFirstItem(Grid grid) { + int count = grid.getListDataView().getItemCount(); + List players = grid.getListDataView().getItems().collect(Collectors.toList()); + assertEquals(38, count); + return players.get(0); + } +} diff --git a/springboot/src/integrationTest/java/de/thpeetz/kontor/tysc/views/RoosterViewTest.java b/springboot/src/integrationTest/java/de/thpeetz/kontor/tysc/views/RoosterViewTest.java new file mode 100644 index 0000000..d04f306 --- /dev/null +++ b/springboot/src/integrationTest/java/de/thpeetz/kontor/tysc/views/RoosterViewTest.java @@ -0,0 +1,43 @@ +package de.thpeetz.kontor.tysc.views; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import com.vaadin.flow.component.grid.Grid; + +import de.thpeetz.kontor.tysc.data.Rooster; + +@SpringBootTest +class RoosterViewTest { + + @Autowired + private RoosterView roosterView; + + @Test + void formShownWhenRoosterSelected() { + Grid grid = roosterView.getGrid(); + Rooster firstRooster = getFirstItem(grid); + + RoosterForm form = roosterView.getForm(); + + assertFalse(form.isVisible()); + grid.asSingleSelect().setValue(firstRooster); + assertTrue(form.isVisible()); + assertEquals(firstRooster.getYear(), form.year.getValue()); + } + + private Rooster getFirstItem(Grid grid) { + int count = grid.getListDataView().getItemCount(); + List roosters = grid.getListDataView().getItems().collect(Collectors.toList()); + assertEquals(11, count); + return roosters.get(0); + } +} diff --git a/springboot/src/integrationTest/java/de/thpeetz/kontor/tysc/views/SportViewTest.java b/springboot/src/integrationTest/java/de/thpeetz/kontor/tysc/views/SportViewTest.java new file mode 100644 index 0000000..ca86d0a --- /dev/null +++ b/springboot/src/integrationTest/java/de/thpeetz/kontor/tysc/views/SportViewTest.java @@ -0,0 +1,43 @@ +package de.thpeetz.kontor.tysc.views; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import com.vaadin.flow.component.grid.Grid; + +import de.thpeetz.kontor.tysc.data.Sport; + +@SpringBootTest +class SportViewTest { + + @Autowired + private SportView sportView; + + @Test + void formShownWhenSportSelected() { + Grid grid = sportView.getGrid(); + Sport firstSport = getFirstItem(grid); + + SportForm form = sportView.getForm(); + + assertFalse(form.isVisible()); + grid.asSingleSelect().setValue(firstSport); + assertTrue(form.isVisible()); + assertEquals(firstSport.getName(), form.name.getValue()); + } + + private Sport getFirstItem(Grid grid) { + int count = grid.getListDataView().getItemCount(); + List sports = grid.getListDataView().getItems().collect(Collectors.toList()); + assertEquals(4, count); + return sports.get(0); + } +} diff --git a/springboot/src/integrationTest/java/de/thpeetz/kontor/tysc/views/TeamViewTest.java b/springboot/src/integrationTest/java/de/thpeetz/kontor/tysc/views/TeamViewTest.java new file mode 100644 index 0000000..da0ea19 --- /dev/null +++ b/springboot/src/integrationTest/java/de/thpeetz/kontor/tysc/views/TeamViewTest.java @@ -0,0 +1,43 @@ +package de.thpeetz.kontor.tysc.views; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import com.vaadin.flow.component.grid.Grid; + +import de.thpeetz.kontor.tysc.data.Team; + +@SpringBootTest +class TeamViewTest { + + @Autowired + private TeamView teamView; + + @Test + void formShownWhenTeamSelected() { + Grid grid = teamView.getGrid(); + Team firstTeam = getFirstItem(grid); + + TeamForm form = teamView.getForm(); + + assertFalse(form.isVisible()); + grid.asSingleSelect().setValue(firstTeam); + assertTrue(form.isVisible()); + assertEquals(firstTeam.getName(), form.name.getValue()); + } + + private Team getFirstItem(Grid grid) { + int count = grid.getListDataView().getItemCount(); + List teams = grid.getListDataView().getItems().collect(Collectors.toList()); + assertEquals(122, count); + return teams.get(0); + } +} diff --git a/springboot/src/integrationTest/java/de/thpeetz/kontor/tysc/views/VendorViewTest.java b/springboot/src/integrationTest/java/de/thpeetz/kontor/tysc/views/VendorViewTest.java new file mode 100644 index 0000000..ef80d22 --- /dev/null +++ b/springboot/src/integrationTest/java/de/thpeetz/kontor/tysc/views/VendorViewTest.java @@ -0,0 +1,43 @@ +package de.thpeetz.kontor.tysc.views; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import com.vaadin.flow.component.grid.Grid; + +import de.thpeetz.kontor.tysc.data.Vendor; + +@SpringBootTest +class VendorViewTest { + + @Autowired + private VendorView vendorView; + + @Test + void formShownWhenVendorSelected() { + Grid grid = vendorView.getGrid(); + Vendor firstVendor = getFirstItem(grid); + + VendorForm form = vendorView.getForm(); + + assertFalse(form.isVisible()); + grid.asSingleSelect().setValue(firstVendor); + assertTrue(form.isVisible()); + assertEquals(firstVendor.getName(), form.name.getValue()); + } + + private Vendor getFirstItem(Grid grid) { + int count = grid.getListDataView().getItemCount(); + List vendors = grid.getListDataView().getItems().collect(Collectors.toList()); + assertEquals(9, count); + return vendors.get(0); + } +} diff --git a/springboot/src/integrationTest/resources/application.properties b/springboot/src/integrationTest/resources/application.properties new file mode 100644 index 0000000..27a1935 --- /dev/null +++ b/springboot/src/integrationTest/resources/application.properties @@ -0,0 +1,30 @@ +server.port=8085 + +spring.hibernate.dialect=org.hibernate.dialect.HSQLDialect +spring.jpa.database-platform=org.hibernate.dialect.HSQLDialect +spring.datasource.driverClassName=org.hsqldb.jdbc.JDBCDriver +spring.datasource.url=jdbc:hsqldb:mem:itDb +spring.datasource.username=sa +spring.datasource.password=sa + +#spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect +#spring.datasource.driverClassName=org.sqlite.JDBC +#spring.datasource.url=jdbc:sqlite:file:./kontorITDb?cache=shared +#spring.datasource.username=sa +#spring.datasource.password=sa + +spring.jpa.defer-datasource-initialization = true +#spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=false +spring.sql.init.mode=always + +spring.mustache.check-template-location = false + +logging.level.org.atmosphere=INFO +logging.level.org.springframework.web=INFO +logging.level.guru.springframework.controllers=DEBUG +logging.level.org.hibernate=INFO +logging.level.de.thpeetz=DEBUG + +jwt.auth.secret=J6GOtcwC2NJI1l0VkHu20PacPFGTxpirBxWwynoHjsc= diff --git a/springboot/src/main/java/de/thpeetz/kontor/Application.java b/springboot/src/main/java/de/thpeetz/kontor/Application.java new file mode 100644 index 0000000..c304f35 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/Application.java @@ -0,0 +1,24 @@ +package de.thpeetz.kontor; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import com.vaadin.flow.component.page.AppShellConfigurator; +import com.vaadin.flow.server.PWA; +import com.vaadin.flow.theme.Theme; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@Slf4j +@EnableJpaAuditing +@SpringBootApplication +@Theme(value = "kontor") +@PWA(name = "Vaadin Kontor", shortName = "Kontor", offlinePath = "offline.html", offlineResources = { "images/offline.png" }) +public class Application implements AppShellConfigurator { + + public static void main(String[] args) { + log.info("Starting Kontor application"); + SpringApplication.run(Application.class); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/AdminConstants.java b/springboot/src/main/java/de/thpeetz/kontor/admin/AdminConstants.java new file mode 100644 index 0000000..63f11b1 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/AdminConstants.java @@ -0,0 +1,53 @@ +package de.thpeetz.kontor.admin; + +import com.vaadin.flow.component.icon.VaadinIcon; +import com.vaadin.flow.component.sidenav.SideNavItem; +import com.vaadin.flow.router.RouterLink; +import de.thpeetz.kontor.admin.views.*; +import de.thpeetz.kontor.comics.ComicConstants; +import de.thpeetz.kontor.comics.views.ComicWorkView; + +public class AdminConstants { + + + private AdminConstants() { + // private constructor to hide the implicit public one + } + + public static final String ADMIN_TITLE = "Verwaltung"; + public static final String AUTHORIZATION = "Berechtigungen"; + public static final String AUTHORIZATION_ROUTE = "/admin/authorization"; + public static final String DATA = "Daten"; + public static final String METADATA_ROUTE = "admin/metadata"; + public static final String ROLE = "Rollen"; + public static final String ROLE_ROUTE = "/admin/role"; + public static final String USER = "Benutzer"; + public static final String USER_ROUTE = "/admin/user"; + public static final String ADMIN = "admin"; + public static final String ADMIN_ROUTE = "/admin"; + + public static RouterLink getUserNavigation() { + return new RouterLink(USER, UserView.class); + } + + public static RouterLink getRoleNavigation() { + return new RouterLink(ROLE, RoleView.class); + } + + public static RouterLink getAuthorizationNavigation() { + return new RouterLink(AUTHORIZATION, AuthorizationView.class); + } + + public static SideNavItem getAdminNavigation() { + SideNavItem administration = new SideNavItem(ADMIN_TITLE, USER_ROUTE, VaadinIcon.GROUP.create()); + administration.addItem(new SideNavItem(USER, USER_ROUTE, VaadinIcon.USERS.create())); + administration.addItem(new SideNavItem(ROLE, RoleView.class)); + SideNavItem data = new SideNavItem(DATA, AUTHORIZATION_ROUTE, VaadinIcon.DATABASE.create()); + data.addItem(new SideNavItem(ComicConstants.COMICWORK, ComicWorkView.class)); + data.addItem(new SideNavItem(AUTHORIZATION, AuthorizationView.class)); + data.addItem(new SideNavItem("Data Import", ModuleDataView.class)); + data.addItem(new SideNavItem("Meta Data", MetaDataView.class)); + administration.addItem(data); + return administration; + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/MailProperties.java b/springboot/src/main/java/de/thpeetz/kontor/admin/MailProperties.java new file mode 100644 index 0000000..9e8a6b4 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/MailProperties.java @@ -0,0 +1,78 @@ +package de.thpeetz.kontor.admin; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties(prefix = "mail") +public class MailProperties { + + private String protocol; + private String host; + private Integer port; + private String userName; + private String password; + private Boolean startTls; + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public Integer getPort() { + return port; + } + + public void setPort(Integer port) { + this.port = port; + } + + public Boolean getStartTls() { + return startTls; + } + + public void setStartTls(Boolean startTls) { + this.startTls = startTls; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("MailProperties{"); + sb.append("protocol='").append(protocol).append('\''); + sb.append(", host='").append(host).append('\''); + sb.append(", port=").append(port); + sb.append(", starttls=").append(startTls); + sb.append(", userName='").append(userName).append('\''); + sb.append(", password='").append(password).append('\''); + sb.append('}'); + return sb.toString(); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/SetupModuleAdmin.java b/springboot/src/main/java/de/thpeetz/kontor/admin/SetupModuleAdmin.java new file mode 100644 index 0000000..4ac89e0 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/SetupModuleAdmin.java @@ -0,0 +1,336 @@ +package de.thpeetz.kontor.admin; + +import de.thpeetz.kontor.admin.data.*; +import de.thpeetz.kontor.admin.services.AdminService; +import de.thpeetz.kontor.admin.services.MetaDataService; +import de.thpeetz.kontor.mailclient.data.MailAccount; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.List; + +@Slf4j +@Component +public class SetupModuleAdmin implements ApplicationListener { + boolean alreadySetup = false; + + @Autowired + private UserRepository userRepository; + + @Autowired + private AuthorizationMatrixRepository authorizationMatrixRepository; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Autowired + private MailAccountRepository mailAccountRepository; + + @Autowired + private MailProperties mailProperties; + + @Autowired + private AdminService adminService; + + @Autowired + private MetaDataService metaDataService; + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + if (alreadySetup) { + return; + } + + // Create initial roles and users + Role adminRole = adminService.addRole("ROLE_ADMIN"); + Role userRole = adminService.addRole("ROLE_USER"); + List users = userRepository.findAll(); + if (users.isEmpty()) { + User adminUser = initAdminUser(); + initMatrix(adminRole, adminUser); + initMatrix(userRole, adminUser); + } + log.info("MailProperties: {}", mailProperties); + initMail(mailProperties); + + initMetaData(); + } + + private void initMail(MailProperties mailProperties) { + log.info("initMail: Host {} with User {}", mailProperties.getHost(), mailProperties.getUserName()); + if (mailProperties.getHost() == null || mailProperties.getHost().isEmpty()) { + return; + } + boolean addMailAccount = false; + List mailAccounts = mailAccountRepository.findAll(); + if (mailAccounts.isEmpty()) { + addMailAccount = true; + } + for (MailAccount mailAccount : mailAccounts) { + String accountUser = mailAccount.getUserName(); + String propertyUser = mailProperties.getUserName(); + String accountHost = mailAccount.getHost(); + String propertyHost = mailProperties.getHost(); + if (propertyHost.equals(accountHost) && propertyUser.equals(accountUser)) { + log.debug("already configured: {}", mailAccount); + } else { + addMailAccount = true; + } + } + if (addMailAccount) { + log.info("add Mail Account: {}", mailProperties); + MailAccount mailAccount = new MailAccount(); + mailAccount.setProtocol(mailProperties.getProtocol()); + mailAccount.setHost(mailProperties.getHost()); + mailAccount.setPort(mailProperties.getPort()); + mailAccount.setUserName(mailProperties.getUserName()); + mailAccount.setPassword(mailProperties.getPassword()); + mailAccount.setStartTls(mailProperties.getStartTls()); + mailAccountRepository.save(mailAccount); + } + } + + private void initMatrix(Role role, User user) { + log.info("initMatrix: Role {} for User {}", role.getName(), user.getUserName()); + Collection configuredRoles = authorizationMatrixRepository.findByUser(user); + if (configuredRoles.stream().anyMatch(matrix -> matrix.getRole().getId().equals(role.getId()))) { + log.info("Role {} already defined", role.getName()); + } else { + log.info("add Role {} to User {}", role.getName(), user.getUserName()); + final AuthorizationMatrix adminMatrix = new AuthorizationMatrix(); + adminMatrix.setUser(user); + adminMatrix.setRole(role); + authorizationMatrixRepository.save(adminMatrix); + } + } + + private User initAdminUser() { + log.info("initAdminUser"); + User admin = userRepository.findByUserName("admin"); + if (admin == null) { + log.info("User admin not found, will be created."); + admin = new User(); + admin.setFirstName("Admin"); + admin.setLastName("Administrator"); + admin.setUserName("admin"); + admin.setEmail("admin@example.org"); + admin.setPassword(passwordEncoder.encode("admin")); + userRepository.save(admin); + } + return admin; + } + + private void initMetaData() { + log.info("initMetaData"); + MetaDataTable mediaArticleTable = metaDataService.getTable("media_article"); + metaDataService.getColumn(mediaArticleTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.TRUE, "ID", Boolean.FALSE, null); + metaDataService.getColumn(mediaArticleTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(mediaArticleTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(mediaArticleTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(mediaArticleTable, "url", "link_url", "TEXT", "UNIQUE", 5, Boolean.TRUE, "URL", Boolean.FALSE, null); + metaDataService.getColumn(mediaArticleTable, "review", "review", "BOOLEAN", null, 6, Boolean.TRUE, "Review", Boolean.TRUE, "Review"); + metaDataService.getColumn(mediaArticleTable, "title", "title", "TEXT", null, 7, Boolean.TRUE, "Title", Boolean.FALSE, null); + MetaDataTable mediaVideoTable = metaDataService.getTable("media_video"); + metaDataService.getColumn(mediaVideoTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.TRUE, "ID", Boolean.FALSE, null); + metaDataService.getColumn(mediaVideoTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(mediaVideoTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(mediaVideoTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "Version", Boolean.FALSE, null); + metaDataService.getColumn(mediaVideoTable, "url", "link_url", "TEXT", "UNIQUE", 5, Boolean.TRUE, "URL", Boolean.FALSE, null); + metaDataService.getColumn(mediaVideoTable, "review", "review", "BOOLEAN", null, 6, Boolean.TRUE, "Review", Boolean.TRUE, "Review"); + metaDataService.getColumn(mediaVideoTable, "should_download", "should_download", "BOOLEAN", null, 7, Boolean.TRUE, "Download", Boolean.TRUE, "Download"); + metaDataService.getColumn(mediaVideoTable, "title", "title", "TEXT", null, 8, Boolean.TRUE, "Title", Boolean.FALSE, null); + metaDataService.getColumn(mediaVideoTable, "file_name", "file_name", "TEXT", null, 9, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(mediaVideoTable, "path", "path", "TEXT", null, 10, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(mediaVideoTable, "cloud_link", "cloud_link", "TEXT", null, 11, Boolean.FALSE, "", Boolean.FALSE, null); + MetaDataTable mediaFileTable = metaDataService.getTable("media_file"); + metaDataService.getColumn(mediaFileTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.TRUE, "ID", Boolean.FALSE, null); + metaDataService.getColumn(mediaFileTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "Created", Boolean.FALSE, null); + metaDataService.getColumn(mediaFileTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "Modified", Boolean.FALSE, null); + metaDataService.getColumn(mediaFileTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "Version", Boolean.FALSE, null); + metaDataService.getColumn(mediaFileTable, "url", "link_url", "TEXT", "UNIQUE", 5, Boolean.TRUE, "URL", Boolean.FALSE, null); + metaDataService.getColumn(mediaFileTable, "review", "review", "BOOLEAN", null, 6, Boolean.TRUE, "Review", Boolean.TRUE, "Review"); + metaDataService.getColumn(mediaFileTable, "should_download", "should_download", "BOOLEAN", null, 7, Boolean.TRUE, "Download", Boolean.TRUE, "Download"); + metaDataService.getColumn(mediaFileTable, "title", "title", "TEXT", null, 8, Boolean.TRUE, "Title", Boolean.FALSE, null); + metaDataService.getColumn(mediaFileTable, "file_name", "file_name", "TEXT", null, 9, Boolean.TRUE, "Dateiname", Boolean.FALSE, null); + metaDataService.getColumn(mediaFileTable, "path", "path", "TEXT", null, 10, Boolean.TRUE, "Verzeichnis", Boolean.FALSE, null); + metaDataService.getColumn(mediaFileTable, "cloud_link", "cloud_link", "TEXT", null, 11, Boolean.TRUE, "Cloud Link", Boolean.FALSE, null); + MetaDataTable artistTable = metaDataService.getTable("artist"); + metaDataService.getColumn(artistTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(artistTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(artistTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(artistTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(artistTable, "name", "name", "TEXT", "UNIQUE", 5, Boolean.FALSE, "", Boolean.FALSE, null); + MetaDataTable publisherTable = metaDataService.getTable("publisher"); + metaDataService.getColumn(publisherTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(publisherTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(publisherTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(publisherTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(publisherTable, "name", "name", "TEXT", "UNIQUE", 5, Boolean.FALSE, "", Boolean.FALSE, null); + MetaDataTable comicTable = metaDataService.getTable("comic"); + metaDataService.getColumn(comicTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.TRUE, "ID", Boolean.FALSE, null); + metaDataService.getColumn(comicTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(comicTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(comicTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(comicTable, "completed", "completed", "BOOLEAN", null, 5, Boolean.TRUE, "Complete", Boolean.TRUE, "Complete"); + metaDataService.getColumn(comicTable, "current_order", "current_order", "BOOLEAN", null, 6, Boolean.TRUE, "Bestellung", Boolean.TRUE, "Bestellung"); + metaDataService.getColumn(comicTable, "title", "title", "TEXT", "UNIQUE", 7, Boolean.TRUE, "Title", Boolean.FALSE, null); + metaDataService.getColumn(comicTable, "publisher_id", "publisher_id", "TEXT", null, 8, Boolean.TRUE, "Verlag", Boolean.FALSE, null); + MetaDataTable issueTable = metaDataService.getTable("issue"); + metaDataService.getColumn(issueTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(issueTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(issueTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(issueTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(issueTable, "in_stock", "in_stock", "BOOLEAN", null, 5, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(issueTable, "is_read", "is_read", "BOOLEAN", null, 6, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(issueTable, "issue_number", "issue_number", "TEXT", null, 7, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(issueTable, "comic_id", "comic_id", "TEXT", null, 8, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(issueTable, "volume_id", "volume_id", "TEXT", null, 9, Boolean.FALSE, "", Boolean.FALSE, null); + MetaDataTable volumeTable = metaDataService.getTable("volume"); + metaDataService.getColumn(volumeTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(volumeTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(volumeTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(volumeTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(volumeTable, "name", "name", "TEXT", null, 5, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(volumeTable, "comic_id", "comic_id", "TEXT", null, 6, Boolean.FALSE, "", Boolean.FALSE, null); + MetaDataTable tpbTable = metaDataService.getTable("trade_paperback"); + metaDataService.getColumn(tpbTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(tpbTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(tpbTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(tpbTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(tpbTable, "issue_start", "issue_start", "LONG", null, 5, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(tpbTable, "issue_end", "issue_end", "LONG", null, 6, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(tpbTable, "name", "name", "TEXT", null, 7, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(tpbTable, "comic_id", "comic_id", "TEXT", null, 8, Boolean.FALSE, "", Boolean.FALSE, null); + MetaDataTable storyArcTable = metaDataService.getTable("story_arc"); + metaDataService.getColumn(storyArcTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(storyArcTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(storyArcTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(storyArcTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(storyArcTable, "name", "name", "TEXT", null, 5, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(storyArcTable, "comic_id", "comic_id", "TEXT", null, 6, Boolean.FALSE, "", Boolean.FALSE, null); + MetaDataTable worktypeTable = metaDataService.getTable("worktype"); + metaDataService.getColumn(worktypeTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(worktypeTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(worktypeTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(worktypeTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(worktypeTable, "name", "name", "TEXT", "UNIQUE", 5, Boolean.FALSE, "", Boolean.FALSE, null); + MetaDataTable comicworkTable = metaDataService.getTable("comic_work"); + metaDataService.getColumn(comicworkTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(comicworkTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(comicworkTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(comicworkTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(comicworkTable, "artist_id", "artist_id", "TEXT", null, 5, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(comicworkTable, "comic_id", "comic_id", "TEXT", null, 6, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(comicworkTable, "work_type_id", "work_type_id", "TEXT", null, 7, Boolean.FALSE, "", Boolean.FALSE, null); + MetaDataTable authorTable = metaDataService.getTable("author"); + metaDataService.getColumn(authorTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(authorTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(authorTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(authorTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(authorTable, "first_name", "first_name", "TEXT", null, 5, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(authorTable, "last_name", "last_name", "TEXT", null, 6, Boolean.FALSE, "", Boolean.FALSE, null); + MetaDataTable articleTable = metaDataService.getTable("article"); + metaDataService.getColumn(articleTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(articleTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(articleTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(articleTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(articleTable, "title", "title", "TEXT", "UNIQUE", 5, Boolean.FALSE, "", Boolean.FALSE, null); + MetaDataTable articleAuthorTable = metaDataService.getTable("article_author"); + metaDataService.getColumn(articleAuthorTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(articleAuthorTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(articleAuthorTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(articleAuthorTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(articleAuthorTable, "article_id", "article_id", "TEXT", null, 5, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(articleAuthorTable, "author_id", "author_id", "TEXT", null, 6, Boolean.FALSE, "", Boolean.FALSE, null); + MetaDataTable bookTable = metaDataService.getTable("book"); + metaDataService.getColumn(bookTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(bookTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(bookTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(bookTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(bookTable, "isbn", "isbn", "TEXT", "UNIQUE", 5, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(bookTable, "title", "title", "TEXT", null, 6, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(bookTable, "year", "year", "LONG", null, 7, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(bookTable, "publisher_id", "publisher_id", "TEXT", null, 8, Boolean.FALSE, "", Boolean.FALSE, null); + MetaDataTable bookAuthorTable = metaDataService.getTable("book_author"); + metaDataService.getColumn(bookAuthorTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(bookAuthorTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(bookAuthorTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(bookAuthorTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(bookAuthorTable, "author_id", "author_id", "TEXT", null, 5, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(bookAuthorTable, "book_id", "book_id", "TEXT", null, 6, Boolean.FALSE, "", Boolean.FALSE, null); + MetaDataTable bookshelfPublisherTable = metaDataService.getTable("bookshelf_publisher"); + metaDataService.getColumn(bookshelfPublisherTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(bookshelfPublisherTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(bookshelfPublisherTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(bookshelfPublisherTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(bookshelfPublisherTable, "name", "name", "TEXT", "UNIQUE", 5, Boolean.FALSE, "", Boolean.FALSE, null); + MetaDataTable sportTable = metaDataService.getTable("sport"); + metaDataService.getColumn(sportTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(sportTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(sportTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(sportTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(sportTable, "name", "name", "TEXT", "UNIQUE", 5, Boolean.FALSE, "", Boolean.FALSE, null); + MetaDataTable playerTable = metaDataService.getTable("player"); + metaDataService.getColumn(playerTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(playerTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(playerTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(playerTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(playerTable, "first_name", "first_name", "TEXT", null, 5, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(playerTable, "last_name", "last_name", "TEXT", null, 6, Boolean.FALSE, "", Boolean.FALSE, null); + MetaDataTable teamTable = metaDataService.getTable("team"); + metaDataService.getColumn(teamTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(teamTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(teamTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(teamTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(teamTable, "name", "name", "TEXT", "UNIQUE", 5, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(teamTable, "short_name", "short_name", "TEXT", null, 6, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(teamTable, "sport_id", "sport_id", "TEXT", null, 7, Boolean.FALSE, "", Boolean.FALSE, null); + MetaDataTable vendorTable = metaDataService.getTable("vendor"); + metaDataService.getColumn(vendorTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(vendorTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(vendorTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(vendorTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(vendorTable, "name", "name", "TEXT", "UNIQUE", 5, Boolean.FALSE, "", Boolean.FALSE, null); + MetaDataTable fieldPositionTable = metaDataService.getTable("field_position"); + metaDataService.getColumn(fieldPositionTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(fieldPositionTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(fieldPositionTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(fieldPositionTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(fieldPositionTable, "name", "name", "TEXT", null, 5, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(fieldPositionTable, "short_name", "short_name", "TEXT", null, 6, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(fieldPositionTable, "sport_id", "sport_id", "TEXT", null, 7, Boolean.FALSE, "", Boolean.FALSE, null); + MetaDataTable roosterTable = metaDataService.getTable("rooster"); + metaDataService.getColumn(roosterTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(roosterTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(roosterTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(roosterTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(roosterTable, "year", "year", "LONG", null, 5, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(roosterTable, "player_id", "player_id", "TEXT", null, 6, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(roosterTable, "position_id", "position_id", "TEXT", null, 7, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(roosterTable, "team_id", "team_id", "TEXT", null, 8, Boolean.FALSE, "", Boolean.FALSE, null); + MetaDataTable cardSetTable = metaDataService.getTable("card_set"); + metaDataService.getColumn(cardSetTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(cardSetTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(cardSetTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(cardSetTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(cardSetTable, "insert_set", "insert_set", "BOOLEAN", null, 5, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(cardSetTable, "parallel_set", "parallel_set", "BOOLEAN", null, 6, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(cardSetTable, "name", "name", "TEXT", null, 7, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(cardSetTable, "vendor_id", "vendor_id", "TEXT", null, 8, Boolean.FALSE, "", Boolean.FALSE, null); + MetaDataTable cardTable = metaDataService.getTable("card"); + metaDataService.getColumn(cardTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(cardTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(cardTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(cardTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(cardTable, "card_number", "card_number", "LONG", null, 5, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(cardTable, "year", "year", "LONG", null, 6, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(cardTable, "card_set_id", "card_set_id", "TEXT", null, 7, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(cardTable, "rooster_id", "rooster_id", "TEXT", null, 8, Boolean.FALSE, "", Boolean.FALSE, null); + metaDataService.getColumn(cardTable, "vendor_id", "vendor_id", "TEXT", null, 9, Boolean.FALSE, "", Boolean.FALSE, null); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/data/AuthorizationMatrix.java b/springboot/src/main/java/de/thpeetz/kontor/admin/data/AuthorizationMatrix.java new file mode 100644 index 0000000..26b5ebb --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/data/AuthorizationMatrix.java @@ -0,0 +1,35 @@ +package de.thpeetz.kontor.admin.data; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@Entity +public class AuthorizationMatrix extends AbstractEntity { + + @ManyToOne + @JoinColumn(name = "user_id") + @NotNull + private User user; + + @ManyToOne + @JoinColumn(name = "role_id") + @NotNull + private Role role; + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("AuthorizationMatrix{"); + sb.append("user=").append(user.getUserName()); + sb.append(", role=").append(role.getName()); + sb.append('}'); + return sb.toString(); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/data/AuthorizationMatrixRepository.java b/springboot/src/main/java/de/thpeetz/kontor/admin/data/AuthorizationMatrixRepository.java new file mode 100644 index 0000000..25d2fb9 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/data/AuthorizationMatrixRepository.java @@ -0,0 +1,13 @@ +package de.thpeetz.kontor.admin.data; + +import java.util.List; +import java.util.UUID; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AuthorizationMatrixRepository extends JpaRepository { + + List findByUser(User user); + + List findByRole(Role role); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/data/MailAccountRepository.java b/springboot/src/main/java/de/thpeetz/kontor/admin/data/MailAccountRepository.java new file mode 100644 index 0000000..ffe66ae --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/data/MailAccountRepository.java @@ -0,0 +1,8 @@ +package de.thpeetz.kontor.admin.data; + +import de.thpeetz.kontor.mailclient.data.MailAccount; +import org.springframework.data.jpa.repository.JpaRepository; + + +public interface MailAccountRepository extends JpaRepository { +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/data/MetaDataColumn.java b/springboot/src/main/java/de/thpeetz/kontor/admin/data/MetaDataColumn.java new file mode 100644 index 0000000..d08b928 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/data/MetaDataColumn.java @@ -0,0 +1,47 @@ +package de.thpeetz.kontor.admin.data; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.annotation.Nullable; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Entity +@Getter +@Setter +@Table( + indexes = @Index(columnList = "columnName, table_id"), + uniqueConstraints = @UniqueConstraint(columnNames = {"table_id", "columnOrder"}) +) +public class MetaDataColumn extends AbstractEntity { + + @NotNull + private String columnName; + + private String columnSyncName; + + private String columnType; + + @Nullable + private String columnModifier; + + private Integer columnOrder; + + private Boolean isShown; + + private String columnLabel; + + private Boolean showFilter; + + private String filterLabel; + + @ManyToOne + @JoinColumn(name = "table_id") + @NotNull + private MetaDataTable table; + + public String getTableName() { + return table.getTableName(); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/data/MetaDataColumnRepository.java b/springboot/src/main/java/de/thpeetz/kontor/admin/data/MetaDataColumnRepository.java new file mode 100644 index 0000000..f822a14 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/data/MetaDataColumnRepository.java @@ -0,0 +1,16 @@ +package de.thpeetz.kontor.admin.data; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface MetaDataColumnRepository extends JpaRepository { + + List findByTable(MetaDataTable table); + + @Query("select m from MetaDataColumn m " + + "where lower(m.columnName) like lower(concat('%', :searchTerm, '%')) or lower(m.columnLabel) like lower(concat('%', :searchTerm, '%'))") + List search(@Param("searchTerm") String searchTerm); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/data/MetaDataTable.java b/springboot/src/main/java/de/thpeetz/kontor/admin/data/MetaDataTable.java new file mode 100644 index 0000000..639666d --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/data/MetaDataTable.java @@ -0,0 +1,26 @@ +package de.thpeetz.kontor.admin.data; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +import java.util.LinkedList; +import java.util.List; + +@Entity +@Getter +@Setter +@Table( + indexes = @Index(columnList = "tableName"), + uniqueConstraints = @UniqueConstraint(columnNames = {"tableName"}) +) +public class MetaDataTable extends AbstractEntity { + + @NotNull + private String tableName; + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "table") + private List tableColumns = new LinkedList<>(); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/data/MetaDataTableRepository.java b/springboot/src/main/java/de/thpeetz/kontor/admin/data/MetaDataTableRepository.java new file mode 100644 index 0000000..694ee52 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/data/MetaDataTableRepository.java @@ -0,0 +1,8 @@ +package de.thpeetz.kontor.admin.data; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MetaDataTableRepository extends JpaRepository { + + MetaDataTable findByTableName(String tableName); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/data/ModuleData.java b/springboot/src/main/java/de/thpeetz/kontor/admin/data/ModuleData.java new file mode 100644 index 0000000..b884425 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/data/ModuleData.java @@ -0,0 +1,25 @@ +package de.thpeetz.kontor.admin.data; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.persistence.Entity; +import jakarta.persistence.Index; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import jakarta.validation.constraints.NotEmpty; +import lombok.Getter; +import lombok.Setter; + +@Entity +@Getter +@Setter +@Table( + indexes = @Index(columnList = "moduleName"), + uniqueConstraints = @UniqueConstraint(columnNames = {"moduleName"}) +) +public class ModuleData extends AbstractEntity { + + @NotEmpty + private String moduleName; + + private Boolean importData; +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/data/ModuleDataRepository.java b/springboot/src/main/java/de/thpeetz/kontor/admin/data/ModuleDataRepository.java new file mode 100644 index 0000000..484dde3 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/data/ModuleDataRepository.java @@ -0,0 +1,15 @@ +package de.thpeetz.kontor.admin.data; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface ModuleDataRepository extends JpaRepository { + + @Query("select m from ModuleData m where lower(m.moduleName) like lower(concat('%', :searchTerm, '%')) ") + List search(@Param("searchTerm") String searchTerm); + + ModuleData findByModuleName(String moduleName); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/data/Role.java b/springboot/src/main/java/de/thpeetz/kontor/admin/data/Role.java new file mode 100644 index 0000000..5ad81c2 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/data/Role.java @@ -0,0 +1,30 @@ +package de.thpeetz.kontor.admin.data; + +import java.util.List; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.OneToMany; +import jakarta.validation.constraints.NotEmpty; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Nullable; + +@Slf4j +@Getter +@Setter +@ToString +@Entity +public class Role extends AbstractEntity { + + @NotEmpty + private String name; + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "role") + @Nullable + private List matrix; +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/data/RoleRepository.java b/springboot/src/main/java/de/thpeetz/kontor/admin/data/RoleRepository.java new file mode 100644 index 0000000..932ae29 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/data/RoleRepository.java @@ -0,0 +1,18 @@ +package de.thpeetz.kontor.admin.data; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface RoleRepository extends JpaRepository { + + @Query("select r from Role r " + + "where lower(r.name) like lower(concat('%', :searchTerm, '%')) ") + List search(@Param("searchTerm") String searchTerm); + + @Query("select r from Role r " + + "where lower(r.name) like lower(:name) ") + Role findByName(@Param("name") String name); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/data/User.java b/springboot/src/main/java/de/thpeetz/kontor/admin/data/User.java new file mode 100644 index 0000000..a80b6bc --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/data/User.java @@ -0,0 +1,62 @@ +package de.thpeetz.kontor.admin.data; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Index; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import jakarta.validation.constraints.NotEmpty; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Nullable; +import java.util.LinkedList; +import java.util.List; + +@Slf4j +@Getter +@Setter +@ToString +@Entity +@Table(indexes = @Index(columnList = "userName"), uniqueConstraints = @UniqueConstraint(columnNames = {"userName"})) +public class User extends AbstractEntity { + + private String firstName; + + private String lastName; + + @NotEmpty + private String userName; + + private String email; + + private String password; + + private boolean enabled; + + private boolean tokenExpired; + + private String token; + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "user") + @Nullable + private List matrix = new LinkedList<>(); + + public String getFullName() { + StringBuilder fullNamBuilder = new StringBuilder(); + if (firstName != null) { + fullNamBuilder.append(firstName); + } + if (lastName != null) { + if (fullNamBuilder.length() > 0) { + fullNamBuilder.append(" "); + } + fullNamBuilder.append(lastName); + } + return fullNamBuilder.toString(); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/data/UserRepository.java b/springboot/src/main/java/de/thpeetz/kontor/admin/data/UserRepository.java new file mode 100644 index 0000000..21d93e0 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/data/UserRepository.java @@ -0,0 +1,16 @@ +package de.thpeetz.kontor.admin.data; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface UserRepository extends JpaRepository { + + @Query("select u from User u " + + "where lower(u.lastName) like lower(concat('%', :searchTerm, '%')) ") + List search(@Param("searchTerm") String searchTerm); + + User findByUserName(String userName); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/services/AdminService.java b/springboot/src/main/java/de/thpeetz/kontor/admin/services/AdminService.java new file mode 100644 index 0000000..77a6448 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/services/AdminService.java @@ -0,0 +1,110 @@ +package de.thpeetz.kontor.admin.services; + +import java.util.Collection; +import java.util.List; + +import org.springframework.stereotype.Service; + +import de.thpeetz.kontor.admin.data.AuthorizationMatrix; +import de.thpeetz.kontor.admin.data.AuthorizationMatrixRepository; +import de.thpeetz.kontor.admin.data.Role; +import de.thpeetz.kontor.admin.data.RoleRepository; +import de.thpeetz.kontor.admin.data.User; +import de.thpeetz.kontor.admin.data.UserRepository; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +public class AdminService { + + private final UserRepository userRepository; + private final RoleRepository roleRepository; + private final AuthorizationMatrixRepository authorizationMatrixRepository; + + public AdminService(UserRepository userRepository, RoleRepository roleRepository, + AuthorizationMatrixRepository authorizationMatrixRepository) { + this.userRepository = userRepository; + this.roleRepository = roleRepository; + this.authorizationMatrixRepository = authorizationMatrixRepository; + } + + public List findAllUsers() { + return userRepository.findAll(); + } + + public List findAllRoles() { + return roleRepository.findAll(); + } + + public Collection findAllRoles(String stringFilter) { + if (stringFilter == null || stringFilter.isEmpty()) { + return roleRepository.findAll(); + } else { + return roleRepository.search(stringFilter); + } + } + + public Role addRole(String roleName) { + Role role = roleRepository.findByName(roleName); + if (role == null) { + log.info("Role {} was not found, will create it.", roleName); + role = new Role(); + role.setName(roleName); + roleRepository.save(role); + } + return role; + } + + public void saveRole(Role role) { + if (role == null) { + log.warn("Role is null. Can't save it."); + } + log.info("saveRole: role={}", role); + roleRepository.save(role); + } + + public void deleteRole(Role role) { + if (role == null) { + log.warn("Role is null. Can't delete it."); + } + log.info("deleteRole: role={}", role); + roleRepository.delete(role); + } + + public List findAllAuthorizationMatrices() { + return authorizationMatrixRepository.findAll(); + } + + public void saveAuthorizationMatrix(AuthorizationMatrix authorizationMatrix) { + if (authorizationMatrix == null) { + log.warn("AuthorizationMatrix is null. Can't save it."); + } + log.info("saveAuthorizationMatrix: authorizationMatrix={}", authorizationMatrix); + authorizationMatrixRepository.save(authorizationMatrix); + } + + public void deleteAuthorizationMatrix(AuthorizationMatrix authorizationMatrix) { + if (authorizationMatrix == null) { + log.warn("AuthorizationMatrix is null. Can't delete it."); + } + log.info("deleteAuthorizationMatrix: authorizationMatrix={}", authorizationMatrix); + authorizationMatrixRepository.delete(authorizationMatrix); + } + + public String getUserFullName(String userName) { + log.debug("get Fullname für user {}", userName); + User user = userRepository.findByUserName(userName); + if (user == null) { + log.info("keinen Eintrag für {} gefunden", userName); + return userName; + } else { + log.info("Voller Name des User {}: {}", userName, user.getFullName()); + return user.getFullName(); + } + } + + public User getUser(String userName) { + log.debug("get User {}", userName); + return userRepository.findByUserName(userName); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/services/KontorUserDetailsService.java b/springboot/src/main/java/de/thpeetz/kontor/admin/services/KontorUserDetailsService.java new file mode 100644 index 0000000..f76d6df --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/services/KontorUserDetailsService.java @@ -0,0 +1,125 @@ +package de.thpeetz.kontor.admin.services; + +import de.thpeetz.kontor.admin.data.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Service("userDetailsService") +public class KontorUserDetailsService implements UserDetailsService { + + private static SecureRandom random = new SecureRandom(); + + @Autowired + private UserRepository userRepository; + + @Autowired + private RoleRepository roleRepository; + + @Autowired + private AuthorizationMatrixRepository authorizationMatrixRepository; + + public Collection findAllUsers(String stringFilter) { + if (stringFilter == null || stringFilter.isEmpty()) { + return userRepository.findAll(); + } else { + return userRepository.search(stringFilter); + } + } + + public void saveUser(User user) { + if (user == null) { + log.warn("User is null. Can't save it."); + } + log.info("saveUser: user={}", user); + userRepository.save(user); + } + + public void saveUser(User user, List roles) { + if (user == null) { + log.warn("User is null. Can't save it."); + } + log.info("First save user: {}", user); + user = userRepository.save(user); + List copy = roles.stream().collect(Collectors.toList()); + List permissions = user.getMatrix(); + permissions.forEach(matrix -> { + if (roles.contains(matrix.getRole())) { + log.info("Role {} already assigned", matrix.getRole()); + copy.remove(matrix.getRole()); + } else { + log.info("Role {} has to be removed", matrix.getRole()); + authorizationMatrixRepository.delete(matrix); + } + }); + log.info("remaining roles: {}", copy); + for (Role role : copy) { + AuthorizationMatrix matrix = new AuthorizationMatrix(); + matrix.setUser(user); + matrix.setRole(role); + authorizationMatrixRepository.save(matrix); + } + } + + public void deleteUser(User user) { + if (user == null) { + log.warn("User is null. Can't delete it."); + } + log.info("deleteUser: user={}", user); + userRepository.delete(user); + } + + @Override + public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException { + + log.info("loadUserByUsername: userName={}", userName); + User user = userRepository.findByUserName(userName); + if (user == null) { + log.info("User not found"); + return null; + } + + Collection authorities = getAuthorities(user); + log.info("User {} hat Rolen: {}", userName, authorities); + return new org.springframework.security.core.userdetails.User(user.getUserName(), user.getPassword(), + authorities); + } + + private Collection getAuthorities(User user) { + return authorizationMatrixRepository.findByUser(user).stream() + .map(matrix -> matrix.getRole().getName()) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + } + + public String getRememberedUser(String id) { + log.info("getRememberedUser: id={}", id); + return "admin"; + } + + public String rememberUser(String username) { + String randomId = new BigInteger(130, random).toString(32); + log.info("rememberUser: username={}", username); + return randomId; + } + + public void removeRememberedUser(String id) { + log.info("removeRememberedUser: id={}", id); + } + + public List findAllRoles() { + return roleRepository.findAll(); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/services/MailService.java b/springboot/src/main/java/de/thpeetz/kontor/admin/services/MailService.java new file mode 100644 index 0000000..dbe9270 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/services/MailService.java @@ -0,0 +1,24 @@ +package de.thpeetz.kontor.admin.services; + +import de.thpeetz.kontor.mailclient.data.MailAccount; +import de.thpeetz.kontor.admin.data.MailAccountRepository; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +import org.springframework.stereotype.Service; + +@Slf4j +@Service +public class MailService { + + private final MailAccountRepository mailAccountRepository; + + public MailService(MailAccountRepository mailAccountRepository) { + this.mailAccountRepository = mailAccountRepository; + } + + public List findAllMailAccounts() { + return mailAccountRepository.findAll(); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/services/MetaDataService.java b/springboot/src/main/java/de/thpeetz/kontor/admin/services/MetaDataService.java new file mode 100644 index 0000000..98ae1c3 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/services/MetaDataService.java @@ -0,0 +1,120 @@ +package de.thpeetz.kontor.admin.services; + +import org.springframework.stereotype.Service; + +import de.thpeetz.kontor.admin.data.MetaDataColumn; +import de.thpeetz.kontor.admin.data.MetaDataColumnRepository; +import de.thpeetz.kontor.admin.data.MetaDataTable; +import de.thpeetz.kontor.admin.data.MetaDataTableRepository; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +@Slf4j +@Service +public class MetaDataService { + + private final MetaDataTableRepository metaDataTableRepository; + + private final MetaDataColumnRepository metaDataColumnRepository; + + public MetaDataService(MetaDataTableRepository metaDataTableRepository, MetaDataColumnRepository metaDataColumnRepository) { + this.metaDataTableRepository = metaDataTableRepository; + this.metaDataColumnRepository = metaDataColumnRepository; + } + + public MetaDataTable getTable(String tableName) { + MetaDataTable table = metaDataTableRepository.findByTableName(tableName); + if (table == null) { + log.info("Metadata for table {} not found, will create it", tableName); + table = new MetaDataTable(); + table.setTableName(tableName); + metaDataTableRepository.save(table); + } + return table; + } + + public void getColumn(MetaDataTable table, String columnName, String columnSyncName, String columnType, String columnModifier, Integer columnOrder, Boolean isShown, String columnLabel, Boolean showFilter, String filterLabel) { + if (table.getTableColumns().stream().anyMatch(column -> column.getColumnName().equals(columnName))) { + log.debug("Column {} with name {} of table {} found, check Values", columnOrder, columnName, table.getTableName()); + MetaDataColumn column = table.getTableColumns().get(columnOrder.intValue()-1); + if (!column.getColumnName().equals(columnName)) { + log.debug("columnName has to be changed to {}", columnName); + column.setColumnName(columnName); + } + if (!column.getColumnSyncName().equals(columnSyncName)) { + log.debug("columnSyncName has to be changed to {}", columnSyncName); + column.setColumnSyncName(columnSyncName); + } + if (!column.getColumnType().equals(columnType)) { + log.debug("columnType has to be changed to {}", columnType); + column.setColumnType(columnType); + } + if (columnModifier != null && !column.getColumnModifier().equals(columnModifier)) { + log.debug("columnModifier has to be changed to {}", columnModifier); + column.setColumnModifier(columnModifier); + } + if (isShown != null && !isShown.equals(column.getIsShown())) { + log.debug("isShown has to be change to {}}", isShown); + column.setIsShown(isShown); + } + if (columnLabel != null && !columnLabel.equals(column.getColumnLabel())) { + log.debug("columnLabel has to be change to {}}", columnLabel); + column.setColumnLabel(columnLabel); + } + if (showFilter != null &&!showFilter.equals(column.getShowFilter())) { + log.debug("showFilter has to be change to {}}", showFilter); + column.setShowFilter(showFilter); + } + if (filterLabel != null && !filterLabel.equals(column.getFilterLabel())) { + log.debug("filterLabel has to be change to {}}", filterLabel); + column.setFilterLabel(filterLabel); + } + metaDataColumnRepository.save(column); + } else { + log.info("Column {} of table {} not found, will create it", columnName, table.getTableName()); + MetaDataColumn column = new MetaDataColumn(); + column.setTable(table); + column.setColumnName(columnName); + column.setColumnSyncName(columnSyncName); + column.setColumnType(columnType); + column.setColumnModifier(columnModifier); + column.setColumnOrder(columnOrder); + column.setIsShown(Boolean.FALSE); + metaDataColumnRepository.save(column); + } + } + + public List findAllMetaDataColumns(String stringFilter) { + if (stringFilter == null || stringFilter.isEmpty()) { + log.debug("Found " + metaDataColumnRepository.count()+ " entries"); + return metaDataColumnRepository.findAll(); + } else { + List results = metaDataColumnRepository.search(stringFilter); + log.debug("Found " + results.size() + " entries"); + return results; + } + } + + public void deleteMetaDataColumn(MetaDataColumn metaDataColumn) { + if (metaDataColumn == null) { + log.warn("MetaDataColumn is null, can't delete it"); + return; + } + log.debug("deleteMetaDataColumn: MetaDataColumn={}", metaDataColumn); + metaDataColumnRepository.delete(metaDataColumn); + } + + public void saveMetaDataColumn(MetaDataColumn metaDataColumn) { + if (metaDataColumn == null) { + log.warn("MetaDataColumn is null, can't save it"); + return; + } + log.debug("saveMetaDataColumn: MetaDataColumn={}", metaDataColumn); + metaDataColumnRepository.save(metaDataColumn); + } + + public List findAllTables() { + return metaDataTableRepository.findAll(); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/services/ModuleService.java b/springboot/src/main/java/de/thpeetz/kontor/admin/services/ModuleService.java new file mode 100644 index 0000000..7596701 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/services/ModuleService.java @@ -0,0 +1,76 @@ +package de.thpeetz.kontor.admin.services; + +import de.thpeetz.kontor.admin.data.ModuleData; +import de.thpeetz.kontor.admin.data.ModuleDataRepository; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +public class ModuleService { + + private final ModuleDataRepository moduleDataRepository; + + public ModuleService(ModuleDataRepository moduleDataRepository) { + this.moduleDataRepository = moduleDataRepository; + } + + public List findAll(String stringFilter) { + if (stringFilter == null || stringFilter.isEmpty()) { + return moduleDataRepository.findAll(); + } else { + return moduleDataRepository.search(stringFilter); + } + } + + public ModuleData findByName(String moduleName) { + if (moduleName == null || moduleName.isEmpty()) { + return null; + } else { + return moduleDataRepository.findByModuleName(moduleName); + } + } + + public boolean importData(String moduleName) { + ModuleData module = moduleDataRepository.findByModuleName(moduleName); + if (module != null) { + return module.getImportData(); + } else { + log.info("Module {} not found, should import data", moduleName); + return true; + } + } + + public void setDataImported(String moduleName) { + ModuleData module = moduleDataRepository.findByModuleName(moduleName); + if (module == null) { + log.info("Module {} not found, will create it", moduleName); + module = new ModuleData(); + module.setModuleName(moduleName); + module.setImportData(false); + moduleDataRepository.save(module); + } else { + log.info("Module {} found, change import data", module); + module.setImportData(false); + moduleDataRepository.save(module); + } + } + + public void saveModuleData(ModuleData moduleData) { + if (moduleData == null) { + log.warn("ModuleData is null, can't save it."); + } else { + moduleDataRepository.save(moduleData); + } + } + + public void deleteModuleData(ModuleData moduleData) { + if (moduleData == null) { + log.warn("ModuleData is null, can't delete it."); + } else { + moduleDataRepository.delete(moduleData); + } + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/views/AdminLayout.java b/springboot/src/main/java/de/thpeetz/kontor/admin/views/AdminLayout.java new file mode 100644 index 0000000..5ed32c5 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/views/AdminLayout.java @@ -0,0 +1,36 @@ +package de.thpeetz.kontor.admin.views; + +import com.vaadin.flow.component.applayout.AppLayout; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.theme.lumo.LumoUtility; + +import de.thpeetz.kontor.admin.AdminConstants; +import de.thpeetz.kontor.admin.services.AdminService; +import de.thpeetz.kontor.common.views.KontorLayoutUtil; +import de.thpeetz.kontor.security.SecurityService; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class AdminLayout extends AppLayout { + + private final AdminService adminService; + + private final SecurityService securityService; + + public AdminLayout(AdminService adminService, SecurityService securityService) { + this.adminService = adminService; + this.securityService = securityService; + + KontorLayoutUtil layout = new KontorLayoutUtil(this, adminService, securityService); + layout.setSecondaryNavigation(getSecondaryNavigation()); + layout.createHeader(AdminConstants.ADMIN_TITLE); + } + + private HorizontalLayout getSecondaryNavigation() { + HorizontalLayout navigation = new HorizontalLayout(); + navigation.addClassNames(LumoUtility.JustifyContent.CENTER, LumoUtility.Gap.SMALL, LumoUtility.Height.MEDIUM); + navigation.add(AdminConstants.getUserNavigation(), AdminConstants.getRoleNavigation(), + AdminConstants.getAuthorizationNavigation()); + return navigation; + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/views/AuthorizationForm.java b/springboot/src/main/java/de/thpeetz/kontor/admin/views/AuthorizationForm.java new file mode 100644 index 0000000..da593f8 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/views/AuthorizationForm.java @@ -0,0 +1,112 @@ +package de.thpeetz.kontor.admin.views; + +import java.util.List; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.data.binder.BeanValidationBinder; +import com.vaadin.flow.data.binder.Binder; + +import de.thpeetz.kontor.admin.data.AuthorizationMatrix; +import de.thpeetz.kontor.admin.data.Role; +import de.thpeetz.kontor.admin.data.User; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class AuthorizationForm extends FormLayout { + + ComboBox user = new ComboBox<>("User"); + ComboBox role = new ComboBox<>("Role"); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(AuthorizationMatrix.class); + + public AuthorizationForm(List users, List roles) { + addClassName("authorizationmatrix-form"); + binder.bindInstanceFields(this); + + user.setItems(users); + user.setItemLabelGenerator(User::getUserName); + role.setItems(roles); + role.setItemLabelGenerator(Role::getName); + add(user, role, createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new SaveEvent(this, binder.getBean())); + } + } + + public void setAuthorizationMatrix(AuthorizationMatrix authorizationMatrix) { + binder.setBean(authorizationMatrix); + } + + public abstract static class AuthorizationFormEvent extends ComponentEvent { + private AuthorizationMatrix authorizationMatrix; + + protected AuthorizationFormEvent(AuthorizationForm source, AuthorizationMatrix authorizationMatrix) { + super(source, false); + this.authorizationMatrix = authorizationMatrix; + } + + public AuthorizationMatrix getAuthorizationMatrix() { + return authorizationMatrix; + } + } + + public static class SaveEvent extends AuthorizationFormEvent { + SaveEvent(AuthorizationForm source, AuthorizationMatrix authorizationMatrix) { + super(source, authorizationMatrix); + } + } + + public static class DeleteEvent extends AuthorizationFormEvent { + DeleteEvent(AuthorizationForm source, AuthorizationMatrix authorizationMatrix) { + super(source, authorizationMatrix); + } + } + + public static class CloseEvent extends AuthorizationFormEvent { + CloseEvent(AuthorizationForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/views/AuthorizationView.java b/springboot/src/main/java/de/thpeetz/kontor/admin/views/AuthorizationView.java new file mode 100644 index 0000000..435ae8f --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/views/AuthorizationView.java @@ -0,0 +1,114 @@ +package de.thpeetz.kontor.admin.views; + +import org.springframework.context.annotation.Scope; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; + +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; + +import de.thpeetz.kontor.admin.AdminConstants; +import de.thpeetz.kontor.admin.data.AuthorizationMatrix; +import de.thpeetz.kontor.admin.services.AdminService; +import de.thpeetz.kontor.common.views.MainLayout; +import jakarta.annotation.security.RolesAllowed; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@SpringComponent +@Scope("prototype") +@RolesAllowed("ROLE_ADMIN") +@Route(value = AdminConstants.AUTHORIZATION_ROUTE, layout = MainLayout.class) +@PageTitle("Authorization | Admin | Kontor") +public class AuthorizationView extends VerticalLayout { + + Grid grid = new Grid<>(AuthorizationMatrix.class); + AuthorizationForm form; + AdminService service; + + public AuthorizationView(AdminService service) { + this.service = service; + addClassName("authoriaztionmatrix-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + private void configureGrid() { + grid.addClassName("authorizationmatrix-grid"); + grid.setSizeFull(); + grid.setColumns("user.userName", "role.name"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editAuthorizationMatrix(event.getValue())); + } + + private void configureForm() { + form = new AuthorizationForm(service.findAllUsers(), service.findAllRoles()); + form.setWidth("25em"); + form.addSaveListener(this::saveAuthorizationMatrix); + form.addDeleteListener(this::deleteAuthorizationMatrix); + form.addCloseListener(e -> closeEditor()); + } + + private void saveAuthorizationMatrix(AuthorizationForm.SaveEvent event) { + AuthorizationMatrix authorizationMatrix = event.getAuthorizationMatrix(); + service.saveAuthorizationMatrix(authorizationMatrix); + updateList(); + closeEditor(); + } + + private void deleteAuthorizationMatrix(AuthorizationForm.DeleteEvent event) { + service.deleteAuthorizationMatrix(event.getAuthorizationMatrix()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + Button addAuthorizationMaxtrixButton = new Button("Add permssion", click -> addAuthorizationMatrix()); + HorizontalLayout toolbar = new HorizontalLayout(addAuthorizationMaxtrixButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editAuthorizationMatrix(AuthorizationMatrix authorizationMatrix) { + if (authorizationMatrix == null) { + closeEditor(); + } else { + form.setAuthorizationMatrix(authorizationMatrix); + form.setVisible(true); + addClassName("editing"); + } + } + + public void closeEditor() { + form.setAuthorizationMatrix(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addAuthorizationMatrix() { + grid.asSingleSelect().clear(); + editAuthorizationMatrix(new AuthorizationMatrix()); + } + + private void updateList() { + grid.setItems(service.findAllAuthorizationMatrices()); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/views/LoginView.java b/springboot/src/main/java/de/thpeetz/kontor/admin/views/LoginView.java new file mode 100644 index 0000000..286bc03 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/views/LoginView.java @@ -0,0 +1,51 @@ +package de.thpeetz.kontor.admin.views; + +import org.springframework.security.core.context.SecurityContextHolder; + +import com.vaadin.flow.component.html.H1; +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.component.login.LoginForm; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.BeforeEnterEvent; +import com.vaadin.flow.router.BeforeEnterObserver; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.auth.AnonymousAllowed; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Route("login") +@PageTitle("Login | Vaadin Kontor") +@AnonymousAllowed +public class LoginView extends VerticalLayout implements BeforeEnterObserver { + + private final LoginForm login = new LoginForm(); + + public LoginView() { + addClassName("login-view"); + setSizeFull(); + setAlignItems(Alignment.CENTER); + setJustifyContentMode(JustifyContentMode.CENTER); + + login.setAction("login"); + + add(new H1("Vaadin Kontor")); + add(new Span("Username: user, Password: password")); + add(new Span("Username: admin, Password: password")); + add(login); + } + + @Override + public void beforeEnter(BeforeEnterEvent beforeEnterEvent) { + log.info("beforeEnter: {}", beforeEnterEvent.getLocation()); + log.info("beforeEnter: {}", SecurityContextHolder.getContext().getAuthentication()); + // inform the user about an authentication error + if (beforeEnterEvent.getLocation() + .getQueryParameters() + .getParameters() + .containsKey("error")) { + login.setError(true); + } + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/views/MetaDataForm.java b/springboot/src/main/java/de/thpeetz/kontor/admin/views/MetaDataForm.java new file mode 100644 index 0000000..b65b099 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/views/MetaDataForm.java @@ -0,0 +1,121 @@ +package de.thpeetz.kontor.admin.views; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +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.formlayout.FormLayout; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.textfield.IntegerField; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.binder.BeanValidationBinder; +import com.vaadin.flow.data.binder.Binder; +import de.thpeetz.kontor.admin.data.MetaDataColumn; +import de.thpeetz.kontor.admin.data.MetaDataTable; + +import java.util.List; + +public class MetaDataForm extends FormLayout { + + ComboBox table = new ComboBox<>("Table"); + TextField columnName = new TextField("Column Name"); + TextField columnSyncName = new TextField("Column Sync Name"); + TextField columnModifier = new TextField("Column Modifier"); + IntegerField columnOrder = new IntegerField("Column Order"); + Checkbox isShown = new Checkbox("Is Shown"); + TextField columnLabel = new TextField("Column Label"); + Checkbox showFilter = new Checkbox("Show Filter"); + TextField filterLabel = new TextField("Filter Label"); + + Button save = new com.vaadin.flow.component.button.Button("Save"); + Button delete = new com.vaadin.flow.component.button.Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(MetaDataColumn.class); + + public MetaDataForm(List tables) { + addClassName("metaData-form"); + binder.bindInstanceFields(this); + + table.setItems(tables); + table.setItemLabelGenerator(MetaDataTable::getTableName); + add(table, columnName, columnSyncName, columnModifier, columnOrder); + add(isShown, columnLabel); + isShown.addClickListener(click -> columnLabel.setEnabled(isShown.getValue())); + add(showFilter, filterLabel); + showFilter.addClickListener(click -> filterLabel.setEnabled(showFilter.getValue())); + add(createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new MetaDataForm.DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new MetaDataForm.CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new MetaDataForm.SaveEvent(this, binder.getBean())); + } + } + + public void setMetaDataColumn(MetaDataColumn metaDataColumn) { + binder.setBean(metaDataColumn); + } + + public abstract static class MetaDataFormEvent extends ComponentEvent { + private MetaDataColumn metaDataColumn; + + protected MetaDataFormEvent(MetaDataForm source, MetaDataColumn metaDataColumn) { + super(source, false); + this.metaDataColumn = metaDataColumn; + } + + public MetaDataColumn getMetaDataColumn() { + return metaDataColumn; + } + } + + public static class SaveEvent extends MetaDataForm.MetaDataFormEvent { + SaveEvent(MetaDataForm source, MetaDataColumn metaDataColumn) { + super(source, metaDataColumn); + } + } + + public static class DeleteEvent extends MetaDataForm.MetaDataFormEvent { + DeleteEvent(MetaDataForm source, MetaDataColumn metaDataColumn) { + super(source, metaDataColumn); + } + } + + public static class CloseEvent extends MetaDataForm.MetaDataFormEvent { + CloseEvent(MetaDataForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(MetaDataForm.DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(MetaDataForm.SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(MetaDataForm.CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/views/MetaDataView.java b/springboot/src/main/java/de/thpeetz/kontor/admin/views/MetaDataView.java new file mode 100644 index 0000000..780126a --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/views/MetaDataView.java @@ -0,0 +1,219 @@ +package de.thpeetz.kontor.admin.views; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.contextmenu.ContextMenu; +import com.vaadin.flow.component.contextmenu.MenuItem; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.grid.GridSortOrder; +import com.vaadin.flow.component.icon.Icon; +import com.vaadin.flow.component.icon.VaadinIcon; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.provider.SortDirection; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; +import de.thpeetz.kontor.admin.AdminConstants; +import de.thpeetz.kontor.admin.data.MetaDataColumn; +import de.thpeetz.kontor.admin.services.MetaDataService; +import de.thpeetz.kontor.common.views.MainLayout; +import jakarta.annotation.security.RolesAllowed; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Scope; + +import java.util.ArrayList; +import java.util.List; + +@Slf4j +@SpringComponent +@Scope("prototype") +@RolesAllowed("ROLE_ADMIN") +@Route(value = AdminConstants.METADATA_ROUTE, layout = MainLayout.class) +@PageTitle("Meta Data | Admin | Kontor") +public class MetaDataView extends VerticalLayout { + + Grid grid = new Grid<>(MetaDataColumn.class, false); + Grid.Column idColumn = grid.addColumn(MetaDataColumn::getId) + .setHeader("ID").setResizable(true).setSortable(true); + Grid.Column createdColumn = grid.addColumn(MetaDataColumn::getCreatedDate) + .setHeader("Erstellt").setResizable(true).setSortable(true); + Grid.Column modifiedColumn = grid.addColumn(MetaDataColumn::getLastModifiedDate) + .setHeader("Geändert").setResizable(true).setSortable(true); + Grid.Column versionColumn = grid.addColumn(MetaDataColumn::getVersion) + .setHeader("Version").setResizable(true).setSortable(true); + Grid.Column tableColumn = grid.addColumn(MetaDataColumn::getTableName) + .setHeader("Table").setResizable(true).setSortable(true); + Grid.Column columnNameColumn = grid.addColumn(MetaDataColumn::getColumnName) + .setHeader("Column Name").setResizable(true).setSortable(true); + Grid.Column columnSyncNameColumn = grid.addColumn(MetaDataColumn::getColumnSyncName) + .setHeader("Column Sync Name").setResizable(true).setSortable(true); + Grid.Column columnTypeColumn = grid.addColumn(MetaDataColumn::getColumnType) + .setHeader("Column Type").setResizable(true).setSortable(true); + Grid.Column columnModifierColumn = grid.addColumn(MetaDataColumn::getColumnModifier) + .setHeader("Column Modifier").setResizable(true).setSortable(true); + Grid.Column columnOrderColumn = grid.addColumn(MetaDataColumn::getColumnOrder) + .setHeader("Column Order").setResizable(true).setSortable(true); + Grid.Column isShownColumn = grid.addComponentColumn(metaDataColumn -> createStatusIcon(metaDataColumn.getIsShown())). + setHeader("Anzeige?").setWidth("6rem").setSortable(true); + Grid.Column columnLabelColumn = grid.addColumn(MetaDataColumn::getColumnLabel) + .setHeader("Spaltenname").setResizable(true).setSortable(true); + Grid.Column showFilterColumn = grid.addComponentColumn(metaDataColumn -> createStatusIcon(metaDataColumn.getShowFilter())). + setHeader("Zeige Filter").setWidth("6rem").setSortable(true); + Grid.Column filterLabelColumn = grid.addColumn(MetaDataColumn::getFilterLabel) + .setHeader("Filter Name").setResizable(true).setSortable(true); + TextField searchField = new TextField(); + @Getter + MetaDataForm form; + MetaDataService service; + + private static class ColumnToggleContextMenu extends ContextMenu { + public ColumnToggleContextMenu(Component target) { + super(target); + setOpenOnClick(true); + } + + void addColumnToggleItem(String label, Grid.Column column) { + MenuItem menuItem = this.addItem(label, e -> { + column.setVisible(e.getSource().isChecked()); + }); + menuItem.setCheckable(true); + menuItem.setChecked(column.isVisible()); + menuItem.setKeepOpen(true); + } + } + + public MetaDataView(MetaDataService service) { + this.service = service; + addClassName("metadata-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + private void configureGrid() { + grid.addClassName("metadata-grid"); + grid.setSizeFull(); + //grid.setColumns("table.tableName", "columnName", "columnSyncName", "columnModifier", "columnOrder", "isShown", "columnLabel", "showFilter", "filterLabel"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + idColumn.setVisible(false); + createdColumn.setVisible(false); + modifiedColumn.setVisible(false); + versionColumn.setVisible(false); + grid.setMultiSort(true); + List sortOrder = new ArrayList(); + sortOrder.add(new GridSortOrder(tableColumn, SortDirection.ASCENDING)); + sortOrder.add(new GridSortOrder(columnOrderColumn, SortDirection.ASCENDING)); + grid.sort(sortOrder); + grid.asSingleSelect().addValueChangeListener(event -> editMetaData(event.getValue())); + } + + private void configureForm() { + form = new MetaDataForm(service.findAllTables()); + form.setWidth("25em"); + form.setVisible(false); + form.addSaveListener(this::saveMetaData); + form.addDeleteListener(this::deleteMetaData); + form.addCloseListener(e -> closeEditor()); + } + + private Icon createStatusIcon(boolean status) { + Icon icon; + if (status) { + icon = VaadinIcon.CHECK.create(); + icon.getElement().getThemeList().add("badge success"); + } else { + icon = VaadinIcon.CLOSE_SMALL.create(); + icon.getElement().getThemeList().add("badge error"); + } + icon.getStyle().set("padding", "var(--lumo-space-xs"); + return icon; + } + + private void saveMetaData(MetaDataForm.SaveEvent event) { + MetaDataColumn metaDataColumn = event.getMetaDataColumn(); + service.saveMetaDataColumn(metaDataColumn); + updateList(); + closeEditor(); + } + + private void deleteMetaData(MetaDataForm.DeleteEvent event) { + service.deleteMetaDataColumn(event.getMetaDataColumn()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + searchField.setPlaceholder("Search"); + searchField.setClearButtonVisible(true); + searchField.setPrefixComponent(new Icon(VaadinIcon.SEARCH)); + searchField.setValueChangeMode(ValueChangeMode.EAGER); + searchField.addValueChangeListener(e -> updateList()); + + Button addMetaDataButton = new Button("Add Meta Data"); + addMetaDataButton.addClickListener(click -> addMetaDataColumn()); + + Button menuButton = new Button("Show/Hide Columns"); + menuButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + MetaDataView.ColumnToggleContextMenu columnToggleContextMenu = new MetaDataView.ColumnToggleContextMenu(menuButton); + columnToggleContextMenu.addColumnToggleItem("ID", idColumn); + columnToggleContextMenu.addColumnToggleItem("Erstellt", createdColumn); + columnToggleContextMenu.addColumnToggleItem("Geändert", modifiedColumn); + columnToggleContextMenu.addColumnToggleItem(versionColumn.getHeaderText(), versionColumn); + columnToggleContextMenu.addColumnToggleItem(tableColumn.getHeaderText(), tableColumn); + columnToggleContextMenu.addColumnToggleItem(columnNameColumn.getHeaderText(), columnNameColumn); + columnToggleContextMenu.addColumnToggleItem(columnSyncNameColumn.getHeaderText(), columnSyncNameColumn); + columnToggleContextMenu.addColumnToggleItem(columnTypeColumn.getHeaderText(), columnTypeColumn); + columnToggleContextMenu.addColumnToggleItem(columnModifierColumn.getHeaderText(), columnModifierColumn); + columnToggleContextMenu.addColumnToggleItem(columnOrderColumn.getHeaderText(), columnOrderColumn); + columnToggleContextMenu.addColumnToggleItem(isShownColumn.getHeaderText(), isShownColumn); + columnToggleContextMenu.addColumnToggleItem(columnLabelColumn.getHeaderText(), columnLabelColumn); + columnToggleContextMenu.addColumnToggleItem(showFilterColumn.getHeaderText(), showFilterColumn); + columnToggleContextMenu.addColumnToggleItem(filterLabelColumn.getHeaderText(), filterLabelColumn); + + HorizontalLayout toolbar = new HorizontalLayout(searchField, addMetaDataButton, menuButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editMetaData(MetaDataColumn metaDataColumn) { + if (metaDataColumn == null) { + closeEditor(); + } else { + form.setMetaDataColumn(metaDataColumn); + form.setVisible(true); + addClassName("editing"); + } + } + + public void closeEditor() { + form.setMetaDataColumn(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addMetaDataColumn() { + grid.asSingleSelect().clear(); + editMetaData(new MetaDataColumn()); + } + + private void updateList() { + grid.setItems(service.findAllMetaDataColumns(searchField.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/views/ModuleDataForm.java b/springboot/src/main/java/de/thpeetz/kontor/admin/views/ModuleDataForm.java new file mode 100644 index 0000000..b2c5f8c --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/views/ModuleDataForm.java @@ -0,0 +1,100 @@ +package de.thpeetz.kontor.admin.views; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +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.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 de.thpeetz.kontor.admin.data.ModuleData; + +public class ModuleDataForm extends FormLayout { + TextField moduleName = new TextField("Module Name"); + Checkbox importData = new Checkbox("Import Data"); + + Button save = new com.vaadin.flow.component.button.Button("Save"); + Button delete = new com.vaadin.flow.component.button.Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(ModuleData.class); + + public ModuleDataForm() { + addClassName("moduleData-form"); + binder.bindInstanceFields(this); + add(moduleName, importData, createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new ModuleDataForm.DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new ModuleDataForm.CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new ModuleDataForm.SaveEvent(this, binder.getBean())); + } + } + + public void setModuleData(ModuleData moduleData) { + binder.setBean(moduleData); + } + + public abstract static class ModuleDataFormEvent extends ComponentEvent { + private ModuleData moduleData; + + protected ModuleDataFormEvent(ModuleDataForm source, ModuleData moduleData) { + super(source, false); + this.moduleData = moduleData; + } + + public ModuleData getModuleData() { + return moduleData; + } + } + + public static class SaveEvent extends ModuleDataForm.ModuleDataFormEvent { + SaveEvent(ModuleDataForm source, ModuleData moduleData) { + super(source, moduleData); + } + } + + public static class DeleteEvent extends ModuleDataForm.ModuleDataFormEvent { + DeleteEvent(ModuleDataForm source, ModuleData moduleData) { + super(source, moduleData); + } + } + + public static class CloseEvent extends ModuleDataForm.ModuleDataFormEvent { + CloseEvent(ModuleDataForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(ModuleDataForm.DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(ModuleDataForm.SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(ModuleDataForm.CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/views/ModuleDataView.java b/springboot/src/main/java/de/thpeetz/kontor/admin/views/ModuleDataView.java new file mode 100644 index 0000000..bc335f3 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/views/ModuleDataView.java @@ -0,0 +1,118 @@ +package de.thpeetz.kontor.admin.views; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; +import de.thpeetz.kontor.admin.data.ModuleData; +import de.thpeetz.kontor.admin.services.ModuleService; +import de.thpeetz.kontor.common.views.MainLayout; +import jakarta.annotation.security.RolesAllowed; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Scope; + +@Slf4j +@SpringComponent +@Scope("prototype") +@RolesAllowed("ROLE_ADMIN") +@Route(value = "admin/module", layout = MainLayout.class) +@PageTitle("Module Data | Admin | Kontor") +public class ModuleDataView extends VerticalLayout { + + Grid grid = new Grid<>(ModuleData.class); + TextField filterText = new TextField(); + ModuleDataForm form; + ModuleService service; + + public ModuleDataView(ModuleService service) { + this.service = service; + addClassName("moduleData-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + private void configureGrid() { + grid.addClassName("moduleData-grid"); + grid.setSizeFull(); + grid.setColumns("moduleName", "importData"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editModuleData(event.getValue())); + } + + private void configureForm() { + form = new ModuleDataForm(); + form.setWidth("25em"); + form.setVisible(false); + form.addSaveListener(this::saveModuleData); + form.addDeleteListener(this::deleteModuleData); + form.addCloseListener(e -> closeEditor()); + } + + private void saveModuleData(ModuleDataForm.SaveEvent event) { + ModuleData moduleData = event.getModuleData(); + service.saveModuleData(moduleData); + updateList(); + closeEditor(); + } + + private void deleteModuleData(ModuleDataForm.DeleteEvent event) { + service.deleteModuleData(event.getModuleData()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + filterText.setPlaceholder("Filter by module name..."); + filterText.setClearButtonVisible(true); + filterText.setValueChangeMode(ValueChangeMode.LAZY); + filterText.addValueChangeListener(e -> updateList()); + Button addModuleDataButton = new Button("Add module", click -> addModuleData()); + HorizontalLayout toolbar = new HorizontalLayout(filterText, addModuleDataButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editModuleData(ModuleData moduleData) { + if (moduleData == null) { + closeEditor(); + } else { + form.setModuleData(moduleData); + form.setVisible(true); + addClassName("editing"); + } + } + + public void closeEditor() { + form.setModuleData(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addModuleData() { + grid.asSingleSelect().clear(); + editModuleData(new ModuleData()); + } + + private void updateList() { + grid.setItems(service.findAll(filterText.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/views/RoleForm.java b/springboot/src/main/java/de/thpeetz/kontor/admin/views/RoleForm.java new file mode 100644 index 0000000..671d0f5 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/views/RoleForm.java @@ -0,0 +1,102 @@ +package de.thpeetz.kontor.admin.views; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +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 de.thpeetz.kontor.admin.data.Role; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class RoleForm extends FormLayout { + + TextField name = new TextField("Role name"); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(Role.class); + + public RoleForm() { + addClassName("role-form"); + binder.bindInstanceFields(this); + add(name, createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new SaveEvent(this, binder.getBean())); + } + } + + public void setRole(Role role) { + binder.setBean(role); + } + + public abstract static class RoleFormEvent extends ComponentEvent { + private Role role; + + protected RoleFormEvent(RoleForm source, Role role) { + super(source, false); + this.role = role; + } + + public Role getRole() { + return role; + } + } + + public static class SaveEvent extends RoleFormEvent { + SaveEvent(RoleForm source, Role role) { + super(source, role); + } + } + + public static class DeleteEvent extends RoleFormEvent { + DeleteEvent(RoleForm source, Role role) { + super(source, role); + } + } + + public static class CloseEvent extends RoleFormEvent { + CloseEvent(RoleForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/views/RoleView.java b/springboot/src/main/java/de/thpeetz/kontor/admin/views/RoleView.java new file mode 100644 index 0000000..1f13c9a --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/views/RoleView.java @@ -0,0 +1,118 @@ +package de.thpeetz.kontor.admin.views; + +import org.springframework.context.annotation.Scope; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; + +import de.thpeetz.kontor.admin.AdminConstants; +import de.thpeetz.kontor.admin.data.Role; +import de.thpeetz.kontor.admin.services.AdminService; +import de.thpeetz.kontor.common.views.MainLayout; +import jakarta.annotation.security.RolesAllowed; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@SpringComponent +@Scope("prototype") +@RolesAllowed("ROLE_ADMIN") +@Route(value = AdminConstants.ROLE_ROUTE, layout = MainLayout.class) +@PageTitle("Rollen | Admin | Kontor") +public class RoleView extends VerticalLayout { + Grid grid = new Grid<>(Role.class); + TextField filterText = new TextField(); + RoleForm form; + AdminService service; + + public RoleView(AdminService service) { + this.service = service; + addClassName("user-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + private void configureGrid() { + grid.addClassName("user-grid"); + grid.setSizeFull(); + grid.setColumns("name"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editRole(event.getValue())); + } + + private void configureForm() { + form = new RoleForm(); + form.setWidth("25em"); + form.addSaveListener(this::saveRole); + form.addDeleteListener(this::deleteRole); + form.addCloseListener(e -> closeEditor()); + } + + private void saveRole(RoleForm.SaveEvent event) { + service.saveRole(event.getRole()); + updateList(); + closeEditor(); + } + + private void deleteRole(RoleForm.DeleteEvent event) { + service.deleteRole(event.getRole()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + filterText.setPlaceholder("Filter by user name..."); + filterText.setClearButtonVisible(true); + filterText.setValueChangeMode(ValueChangeMode.LAZY); + filterText.addValueChangeListener(e -> updateList()); + Button addUserButton = new Button("Add user", click -> addUser()); + HorizontalLayout toolbar = new HorizontalLayout(filterText, addUserButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editRole(Role role) { + if (role == null) { + closeEditor(); + } else { + form.setRole(role); + form.setVisible(true); + addClassName("editing"); + } + } + + public void closeEditor() { + form.setRole(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addUser() { + grid.asSingleSelect().clear(); + editRole(new Role()); + } + + private void updateList() { + grid.setItems(service.findAllRoles(filterText.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/views/UserForm.java b/springboot/src/main/java/de/thpeetz/kontor/admin/views/UserForm.java new file mode 100644 index 0000000..21298f3 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/views/UserForm.java @@ -0,0 +1,143 @@ +package de.thpeetz.kontor.admin.views; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +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.checkbox.CheckboxGroup; +import com.vaadin.flow.component.checkbox.CheckboxGroupVariant; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.textfield.EmailField; +import com.vaadin.flow.component.textfield.PasswordField; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.binder.BeanValidationBinder; +import com.vaadin.flow.data.binder.Binder; +import de.thpeetz.kontor.admin.data.Role; +import de.thpeetz.kontor.admin.data.User; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +@Slf4j +public class UserForm extends FormLayout { + + TextField userName = new TextField("User name"); + PasswordField password = new PasswordField("Password"); + EmailField email = new EmailField("Email"); + TextField firstName = new TextField("First name"); + TextField lastName = new TextField("Last name"); + Checkbox enabled = new Checkbox("Enabled"); + String originalPassword; + + CheckboxGroup permissions = new CheckboxGroup<>("Permissions"); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(User.class); + + public UserForm() { + addClassName("user-form"); + binder.bindInstanceFields(this); + add(userName, password, email, firstName, lastName, enabled, configurePermissionsGroup(), createButtonsLayout()); + } + + private CheckboxGroup configurePermissionsGroup() { + permissions.addThemeVariants(CheckboxGroupVariant.LUMO_VERTICAL); + permissions.setItemLabelGenerator(Role::getName); + permissions.addValueChangeListener(event -> { + log.debug("permissions changed: {}", event); + }); + return permissions; + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new SaveEvent(this, binder.getBean())); + } + } + + public void setUser(User user) { + binder.setBean(user); + //log.debug("UserForm.setUser: {}", user); + if (user != null) { + this.originalPassword = user.getPassword(); + } else { + this.originalPassword = null; + } + } + + public void setRoles(List roles, User user) { + permissions.setItems(roles); + user.getMatrix().stream().forEach(authorizationMatrix -> { + permissions.select(authorizationMatrix.getRole()); + }); + } + + public boolean hasPasswordChanged(User user) { + return !originalPassword.equals(user.getPassword()); + } + + public abstract static class UserFormEvent extends ComponentEvent { + private User user; + + protected UserFormEvent(UserForm source, User user) { + super(source, false); + this.user = user; + } + + public User getUser() { + return user; + } + } + + public static class SaveEvent extends UserFormEvent { + SaveEvent(UserForm source, User user) { + super(source, user); + } + } + + public static class DeleteEvent extends UserFormEvent { + DeleteEvent(UserForm source, User user) { + super(source, user); + } + } + + public static class CloseEvent extends UserFormEvent { + CloseEvent(UserForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/views/UserProfileView.java b/springboot/src/main/java/de/thpeetz/kontor/admin/views/UserProfileView.java new file mode 100644 index 0000000..b2a9ef8 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/views/UserProfileView.java @@ -0,0 +1,73 @@ +package de.thpeetz.kontor.admin.views; + +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +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.router.PageTitle; +import com.vaadin.flow.router.Route; +import de.thpeetz.kontor.admin.data.User; +import de.thpeetz.kontor.admin.services.AdminService; +import de.thpeetz.kontor.common.views.MainLayout; +import de.thpeetz.kontor.security.SecurityService; +import jakarta.annotation.security.PermitAll; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Route(value="user/profile", layout = MainLayout.class) +@PermitAll +@PageTitle("Profile | User | Kontor") +public class UserProfileView extends VerticalLayout { + + private SecurityService securityService; + + private AdminService adminService; + + TextField firstName = new TextField("First name"); + TextField lastName = new TextField("Last name"); + + Button save = new Button("Save"); + Button close = new Button("Cancel"); + Binder binder = new BeanValidationBinder<>(User.class); + + public UserProfileView(AdminService adminService, SecurityService securityService) { + this.adminService = adminService; + this.securityService = securityService; + binder.bindInstanceFields(this); + + add(firstName, lastName, createButtonsLayout()); + securityService.getAuthenticatedUser().ifPresent(user -> { + log.info("UserProfileView: {}", user.getUsername()); + binder.setBean(adminService.getUser(user.getUsername())); + }); + + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + close.addClickListener(event -> closeView()); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + log.info("update user profile"); + } + } + + private void closeView() { + log.info("close user profile view"); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/admin/views/UserView.java b/springboot/src/main/java/de/thpeetz/kontor/admin/views/UserView.java new file mode 100644 index 0000000..9f981ec --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/admin/views/UserView.java @@ -0,0 +1,135 @@ +package de.thpeetz.kontor.admin.views; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; +import de.thpeetz.kontor.admin.data.Role; +import de.thpeetz.kontor.admin.data.User; +import de.thpeetz.kontor.admin.services.KontorUserDetailsService; +import de.thpeetz.kontor.common.views.MainLayout; +import jakarta.annotation.security.RolesAllowed; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@SpringComponent +@Scope("prototype") +@RolesAllowed("ROLE_ADMIN") +@Route(value = "admin/user", layout = MainLayout.class) +@PageTitle("User | Admin | Kontor") +public class UserView extends VerticalLayout { + + Grid grid = new Grid<>(User.class); + TextField filterText = new TextField(); + UserForm form; + KontorUserDetailsService service; + + @Autowired + PasswordEncoder passwordEncoder; + + public UserView(KontorUserDetailsService service) { + this.service = service; + addClassName("user-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + private void configureGrid() { + grid.addClassName("user-grid"); + grid.setSizeFull(); + grid.setColumns("userName", "email", "firstName", "lastName", "enabled"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editUser(event.getValue())); + } + + private void configureForm() { + form = new UserForm(); + form.setWidth("25em"); + form.setVisible(false); + form.addSaveListener(this::saveUser); + form.addDeleteListener(this::deleteUser); + form.addCloseListener(e -> closeEditor()); + } + + private void saveUser(UserForm.SaveEvent event) { + User user = event.getUser(); + log.debug("UserView.saveUser: {}", user); + List permissions = form.permissions.getSelectedItems().stream().collect(Collectors.toList()); + log.info("selected permissions: {}", permissions); + if (form.hasPasswordChanged(user)) { + user.setPassword(passwordEncoder.encode(user.getPassword())); + log.debug("password changed for user {}", user); + } + service.saveUser(user, permissions); + updateList(); + closeEditor(); + } + + private void deleteUser(UserForm.DeleteEvent event) { + service.deleteUser(event.getUser()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + filterText.setPlaceholder("Filter by user name..."); + filterText.setClearButtonVisible(true); + filterText.setValueChangeMode(ValueChangeMode.LAZY); + filterText.addValueChangeListener(e -> updateList()); + Button addUserButton = new Button("Add user", click -> addUser()); + HorizontalLayout toolbar = new HorizontalLayout(filterText, addUserButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editUser(User user) { + if (user == null) { + closeEditor(); + } else { + form.setUser(user); + form.setRoles(service.findAllRoles(), user); + form.setVisible(true); + addClassName("editing"); + } + } + + public void closeEditor() { + form.setUser(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addUser() { + grid.asSingleSelect().clear(); + editUser(new User()); + } + + private void updateList() { + grid.setItems(service.findAllUsers(filterText.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/bookshelf/BookshelfConstants.java b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/BookshelfConstants.java new file mode 100644 index 0000000..58661a0 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/BookshelfConstants.java @@ -0,0 +1,53 @@ +package de.thpeetz.kontor.bookshelf; + +import com.vaadin.flow.component.icon.VaadinIcon; +import com.vaadin.flow.component.sidenav.SideNav; +import com.vaadin.flow.component.sidenav.SideNavItem; +import com.vaadin.flow.router.RouterLink; + +import de.thpeetz.kontor.bookshelf.views.ArticleView; +import de.thpeetz.kontor.bookshelf.views.AuthorView; +import de.thpeetz.kontor.bookshelf.views.BookView; +import de.thpeetz.kontor.bookshelf.views.BookshelfPublisherView; + +public class BookshelfConstants { + + public static final String AUTHOR = "Author"; + public static final String ARTICLE = "Article"; + public static final String BOOK = "Book"; + public static final String BOOKSHELF = "Bookshelf"; + public static final String PUBLISHER = "Publisher"; + public static final String PUBLISHER_ROUTE = "bookshelf/publisher"; + public static final String AUTHOR_ROUTE = "bookshelf/author"; + public static final String BOOK_ROUTE = "bookshelf/book"; + public static final String ARTICLE_ROUTE = "bookshelf/article"; + + public static RouterLink getPublisherLink() { + return new RouterLink(PUBLISHER, BookshelfPublisherView.class); + } + + public static RouterLink getAuthorLink() { + return new RouterLink(AUTHOR, AuthorView.class); + } + + public static RouterLink getBookLink() { + return new RouterLink(BOOK, BookView.class); + } + + public static RouterLink getArticleLink() { + return new RouterLink(ARTICLE, ArticleView.class); + } + + public static SideNavItem getBookshelfNavigation() { + SideNavItem bookshelf = new SideNavItem(BOOKSHELF, BookView.class, VaadinIcon.OPEN_BOOK.create()); + bookshelf.addItem(new SideNavItem(BOOK, BookView.class)); + bookshelf.addItem(new SideNavItem(PUBLISHER, BookshelfPublisherView.class)); + bookshelf.addItem(new SideNavItem(AUTHOR, AuthorView.class)); + bookshelf.addItem(new SideNavItem(ARTICLE, ArticleView.class)); + return bookshelf; + } + + private BookshelfConstants() { + // private constructor to hide the implicit public one + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/bookshelf/SetupModuleBookshelf.java b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/SetupModuleBookshelf.java new file mode 100644 index 0000000..abb0e9a --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/SetupModuleBookshelf.java @@ -0,0 +1,82 @@ +package de.thpeetz.kontor.bookshelf; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.stereotype.Component; + +import de.thpeetz.kontor.admin.services.ModuleService; +import de.thpeetz.kontor.bookshelf.data.ArticleAuthorRepository; +import de.thpeetz.kontor.bookshelf.data.Author; +import de.thpeetz.kontor.bookshelf.data.AuthorRepository; +import de.thpeetz.kontor.bookshelf.data.BookAuthorRepository; +import de.thpeetz.kontor.bookshelf.data.BookRepository; +import de.thpeetz.kontor.bookshelf.data.BookshelfPublisher; +import de.thpeetz.kontor.bookshelf.data.BookshelfPublisherRepository; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +public class SetupModuleBookshelf implements ApplicationListener { + + boolean alreadySetup = false; + + @Autowired + private BookshelfPublisherRepository publisherRepository; + + @Autowired + private AuthorRepository authorRepository; + + @Autowired + private ArticleAuthorRepository articleAuthorRepository; + + @Autowired + private BookRepository bookRepository; + + @Autowired + private BookAuthorRepository bookAuthorRepository; + + @Autowired + private ModuleService moduleService; + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + if (alreadySetup) { + log.info("SetupModuleBookshelf already executed, skipping"); + return; + } + if (!moduleService.importData(BookshelfConstants.BOOKSHELF)) { + log.info("Module Bookshelf should not setup data"); + return; + } + + log.info("Set up Bookshelf data"); + Author douglasadams = createAuthorIfNotFound("Douglas", "Adams"); + moduleService.setDataImported(BookshelfConstants.BOOKSHELF); + } + + private Author createAuthorIfNotFound(String firstName, String lastName) { + log.info("createAuthorIfNotFound {} {}", firstName, lastName); + Author author = authorRepository.findByFirstNameAndLastName(firstName, lastName); + if (author == null) { + log.info("Author {} {} not found, will create it", firstName, lastName); + author = new Author(); + author.setFirstName(firstName); + author.setLastName(lastName); + authorRepository.save(author); + } + return author; + } + + private BookshelfPublisher createPublisherIfNotFound(String publisherName) { + log.info("createPublisherIfNotFound {}", publisherName); + BookshelfPublisher publisher = publisherRepository.findByName(publisherName); + if (publisher == null) { + log.info("Publisher {} not found, will create it", publisherName); + publisher = new BookshelfPublisher(); + publisher.setName(publisherName); + publisherRepository.save(publisher); + } + return publisher; + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/Article.java b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/Article.java new file mode 100644 index 0000000..3ee6a16 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/Article.java @@ -0,0 +1,27 @@ +package de.thpeetz.kontor.bookshelf.data; + +import java.util.LinkedList; +import java.util.List; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.annotation.Nullable; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotEmpty; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@Entity +@Table(indexes = @Index(columnList = "title"), uniqueConstraints = @UniqueConstraint(columnNames = {"title"})) +public class Article extends AbstractEntity { + + @NotEmpty + private String title; + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "article") + @Nullable + private List authors = new LinkedList<>(); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/ArticleAuthor.java b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/ArticleAuthor.java new file mode 100644 index 0000000..c2b3851 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/ArticleAuthor.java @@ -0,0 +1,29 @@ +package de.thpeetz.kontor.bookshelf.data; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.persistence.Entity; +import jakarta.persistence.Index; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Entity +@Table(indexes = @Index(columnList = "author_id, article_id"), uniqueConstraints = @UniqueConstraint(columnNames = {"author_id", "article_id"})) +public class ArticleAuthor extends AbstractEntity { + + @ManyToOne + @JoinColumn(name = "author_id") + @NotNull + private Author author; + + @ManyToOne + @JoinColumn(name = "article_id") + @NotNull + private Article article; +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/ArticleAuthorRepository.java b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/ArticleAuthorRepository.java new file mode 100644 index 0000000..d7933de --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/ArticleAuthorRepository.java @@ -0,0 +1,12 @@ +package de.thpeetz.kontor.bookshelf.data; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ArticleAuthorRepository extends JpaRepository { + + List findByAuthor(Author author); + + List findByArticle(Article article); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/ArticleRepository.java b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/ArticleRepository.java new file mode 100644 index 0000000..c6b2602 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/ArticleRepository.java @@ -0,0 +1,16 @@ +package de.thpeetz.kontor.bookshelf.data; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface ArticleRepository extends JpaRepository { + + @Query("select a from Article a " + + "where lower(a.title) like lower(concat('%', :searchTerm, '%'))") + List
search(@Param("searchTerm") String searchTerm); + + List
findByTitle(String title); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/Author.java b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/Author.java new file mode 100644 index 0000000..42d83e0 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/Author.java @@ -0,0 +1,44 @@ +package de.thpeetz.kontor.bookshelf.data; + +import java.util.LinkedList; +import java.util.List; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.annotation.Nullable; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Index; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import jakarta.validation.constraints.NotEmpty; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@Entity +@Table(indexes = { + @Index(columnList = "firstName, lastName"), + @Index(columnList = "lastName, firstName") +}, uniqueConstraints = { + @UniqueConstraint(columnNames = { "firstName", "lastName" }) +}) +public class Author extends AbstractEntity { + + @NotEmpty + private String firstName; + + @NotEmpty + private String lastName; + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "author") + @Nullable + private List bookAuthors = new LinkedList<>(); + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "author") + @Nullable + private List articleAuthors = new LinkedList<>(); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/AuthorRepository.java b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/AuthorRepository.java new file mode 100644 index 0000000..3e87334 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/AuthorRepository.java @@ -0,0 +1,18 @@ +package de.thpeetz.kontor.bookshelf.data; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface AuthorRepository extends JpaRepository { + + @Query("select a from Author a " + + "where lower(a.firstName) like lower(concat('%', :searchTerm, '%')) " + + "or lower(a.lastName) like lower(concat('%', :searchTerm, '%'))") + List search(@Param("searchTerm") String searchTerm); + + Author findByFirstNameAndLastName(String firstName, String lastName); + +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/Book.java b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/Book.java new file mode 100644 index 0000000..e0f6782 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/Book.java @@ -0,0 +1,42 @@ +package de.thpeetz.kontor.bookshelf.data; + +import java.util.LinkedList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.annotation.Nullable; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@Entity +@Table(indexes = @Index(columnList = "title, isbn"), uniqueConstraints = @UniqueConstraint(columnNames = {"isbn"})) +public class Book extends AbstractEntity { + + @NotEmpty + private String title; + + @NotEmpty + private String isbn; + + @Nullable + private int year; + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "book") + @Nullable + private List authors = new LinkedList<>(); + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "publisher_id") + @NotNull + @JsonIgnoreProperties({ "books" }) + private BookshelfPublisher publisher; +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/BookAuthor.java b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/BookAuthor.java new file mode 100644 index 0000000..86a0e10 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/BookAuthor.java @@ -0,0 +1,29 @@ +package de.thpeetz.kontor.bookshelf.data; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.persistence.Entity; +import jakarta.persistence.Index; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Entity +@Table(indexes = @Index(columnList = "author_id, book_id"), uniqueConstraints = @UniqueConstraint(columnNames = {"author_id", "book_id"})) +public class BookAuthor extends AbstractEntity { + + @ManyToOne + @JoinColumn(name = "author_id") + @NotNull + private Author author; + + @ManyToOne + @JoinColumn(name = "book_id") + @NotNull + private Book book; +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/BookAuthorRepository.java b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/BookAuthorRepository.java new file mode 100644 index 0000000..a0cf650 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/BookAuthorRepository.java @@ -0,0 +1,12 @@ +package de.thpeetz.kontor.bookshelf.data; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface BookAuthorRepository extends JpaRepository { + + List findByAuthor(Author author); + + List findByBook(Book book); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/BookRepository.java b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/BookRepository.java new file mode 100644 index 0000000..e6ee013 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/BookRepository.java @@ -0,0 +1,21 @@ +package de.thpeetz.kontor.bookshelf.data; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface BookRepository extends JpaRepository { + + @Query("select b from Book b " + + "where lower(b.title) like lower(concat('%', :searchTerm, '%')) " + + "or lower(b.isbn) like lower(concat('%', :searchTerm, '%'))") + List search(@Param("searchTerm") String searchTerm); + + List findByTitle(String name); + + List findByTitleIgnoreCase(String name); + + List findByIsbn(String isbn); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/BookshelfPublisher.java b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/BookshelfPublisher.java new file mode 100644 index 0000000..da97d7a --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/BookshelfPublisher.java @@ -0,0 +1,32 @@ +package de.thpeetz.kontor.bookshelf.data; + +import java.util.LinkedList; +import java.util.List; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.annotation.Nullable; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Index; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import jakarta.validation.constraints.NotEmpty; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@Entity +@Table(indexes = @Index(columnList = "name"), uniqueConstraints = @UniqueConstraint(columnNames = { "name" })) +public class BookshelfPublisher extends AbstractEntity { + + @NotEmpty + private String name; + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "publisher", orphanRemoval = true) + @Nullable + List books = new LinkedList<>(); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/BookshelfPublisherRepository.java b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/BookshelfPublisherRepository.java new file mode 100644 index 0000000..b50ba84 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/data/BookshelfPublisherRepository.java @@ -0,0 +1,18 @@ +package de.thpeetz.kontor.bookshelf.data; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface BookshelfPublisherRepository extends JpaRepository { + + @Query("select p from BookshelfPublisher p " + + "where lower(p.name) like lower(concat('%', :searchTerm, '%')) ") + List search(@Param("searchTerm") String searchTerm); + + BookshelfPublisher findByName(String name); + + List findByNameIgnoreCase(String name); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/bookshelf/services/BookshelfService.java b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/services/BookshelfService.java new file mode 100644 index 0000000..6d92a2c --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/services/BookshelfService.java @@ -0,0 +1,118 @@ +package de.thpeetz.kontor.bookshelf.services; + +import java.util.List; + +import de.thpeetz.kontor.bookshelf.data.*; +import org.springframework.stereotype.Service; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +public class BookshelfService { + + private final AuthorRepository authorRepository; + private final ArticleAuthorRepository articleAuthorRepository; + private final ArticleRepository articleRepository; + private final BookRepository bookRepository; + private final BookAuthorRepository bookAuthorRepository; + private final BookshelfPublisherRepository publisherRepository; + + public BookshelfService(AuthorRepository authorRepository, ArticleAuthorRepository articleAuthorRepository, + ArticleRepository articleRepository, BookRepository bookRepository, + BookAuthorRepository bookAuthorRepository, BookshelfPublisherRepository publisherRepository) { + this.authorRepository = authorRepository; + this.articleAuthorRepository = articleAuthorRepository; + this.articleRepository = articleRepository; + this.bookRepository = bookRepository; + this.bookAuthorRepository = bookAuthorRepository; + this.publisherRepository = publisherRepository; + } + + public List findAllPublishers(String stringFilter) { + if (stringFilter == null || stringFilter.isEmpty()) { + return publisherRepository.findAll(); + } else { + return publisherRepository.search(stringFilter); + } + } + + public BookshelfPublisher findPublisherByName(String publisherName) { + return publisherRepository.findByName(publisherName); + } + + public void deletePublisher(BookshelfPublisher publisher) { + publisherRepository.delete(publisher); + } + + public void savePublisher(BookshelfPublisher publisher) { + if (publisher == null) { + log.warn("Publisher is null. Are you sure you have connected your form to the application?"); + return; + } + publisherRepository.save(publisher); + } + + public List findAllAuthors(String filter) { + if (filter == null || filter.isEmpty()) { + return authorRepository.findAll(); + } else { + return authorRepository.search(filter); + } + } + + public void saveAuthor(Author author) { + if (author == null) { + log.warn("Author is null. Are you sure you have connected your form to the application?"); + return; + } + authorRepository.save(author); + } + + public void deleteAuthor(Author author) { + authorRepository.delete(author); + } + + public List findAllBooks(String filter) { + if (filter == null || filter.isEmpty()) { + return bookRepository.findAll(); + } else { + return bookRepository.search(filter); + } + } + + public void saveBook(Book book) { + if (book == null) { + log.warn("Book is null. Are you sure you have connected your form to the application?"); + return; + } + bookRepository.save(book); + } + + public void deleteBook(Book book) { + BookshelfPublisher publisher = book.getPublisher(); + publisher.getBooks().remove(book); + publisherRepository.save(publisher); + bookRepository.delete(book); + } + + public List
findAllArticles(String filter) { + if (filter == null || filter.isEmpty()) { + return articleRepository.findAll(); + } else { + return articleRepository.search(filter); + } + } + + public void saveArticle(Article article) { + if (article == null) { + log.warn("Article is null, Are you sure you have connected your form to the application?"); + return; + } + articleRepository.save(article); + } + + public void deleteArticle(Article article) { + articleRepository.delete(article); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/ArticleForm.java b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/ArticleForm.java new file mode 100644 index 0000000..7fb7958 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/ArticleForm.java @@ -0,0 +1,106 @@ +package de.thpeetz.kontor.bookshelf.views; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.grid.Grid; +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 de.thpeetz.kontor.bookshelf.data.Article; +import de.thpeetz.kontor.bookshelf.data.Author; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +@Slf4j +public class ArticleForm extends FormLayout { + + TextField title = new TextField("Title"); + Grid author = new Grid<>(Author.class); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder
binder = new BeanValidationBinder<>(Article.class); + + public ArticleForm() { + addClassName("article-form"); + binder.bindInstanceFields(this); + + add(title, author, createButtonsLayout()); + } + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new ArticleForm.DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new ArticleForm.CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new ArticleForm.SaveEvent(this, binder.getBean())); + } + } + + public void setArticle(Article article) { + binder.setBean(article); + } + + public abstract static class ArticleFormEvent extends ComponentEvent { + private Article article; + + protected ArticleFormEvent(ArticleForm source, Article article) { + super(source, false); + this.article = article; + } + + public Article getArticle() { + return article; + } + } + + public static class SaveEvent extends ArticleForm.ArticleFormEvent { + SaveEvent(ArticleForm source, Article article) { + super(source, article); + } + } + + public static class DeleteEvent extends ArticleForm.ArticleFormEvent { + DeleteEvent(ArticleForm source, Article article) { + super(source, article); + } + } + + public static class CloseEvent extends ArticleForm.ArticleFormEvent { + CloseEvent(ArticleForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(ArticleForm.DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(ArticleForm.SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(ArticleForm.CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/ArticleView.java b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/ArticleView.java new file mode 100644 index 0000000..334f577 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/ArticleView.java @@ -0,0 +1,126 @@ +package de.thpeetz.kontor.bookshelf.views; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; +import de.thpeetz.kontor.bookshelf.BookshelfConstants; +import de.thpeetz.kontor.bookshelf.data.Article; +import de.thpeetz.kontor.bookshelf.data.Book; +import de.thpeetz.kontor.bookshelf.data.BookAuthor; +import de.thpeetz.kontor.bookshelf.services.BookshelfService; +import de.thpeetz.kontor.common.views.MainLayout; +import jakarta.annotation.security.PermitAll; +import lombok.Getter; +import org.springframework.context.annotation.Scope; + +import java.util.stream.Collectors; + +@SpringComponent +@Scope("prototype") +@PermitAll +@Route(value = BookshelfConstants.ARTICLE_ROUTE, layout = MainLayout.class) +@PageTitle("Article | Bookshelf | Kontor") +public class ArticleView extends VerticalLayout { + + @Getter + Grid
grid = new Grid<>(Article.class); + TextField filterText = new TextField(); + @Getter + ArticleForm form; + BookshelfService service; + + public ArticleView(BookshelfService service) { + this.service = service; + addClassName("article-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + private void configureGrid() { + grid.addClassName("article-grid"); + grid.setSizeFull(); + grid.setColumns("title"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editArticle(event.getValue())); + } + + private void configureForm() { + form = new ArticleForm(); + form.setWidth("25em"); + form.setVisible(false); + form.addSaveListener(this::saveArticle); + form.addDeleteListener(this::deleteArticle); + form.addCloseListener(e -> closeEditor()); + } + + private void saveArticle(ArticleForm.SaveEvent event) { + service.saveArticle(event.getArticle()); + updateList(); + closeEditor(); + } + + private void deleteArticle(ArticleForm.DeleteEvent event) { + service.deleteArticle(event.getArticle()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + filterText.setPlaceholder("Filter by title or isbn..."); + filterText.setClearButtonVisible(true); + filterText.setValueChangeMode(ValueChangeMode.LAZY); + filterText.addValueChangeListener(e -> updateList()); + + Button addArticleButton = new Button("Add article"); + addArticleButton.addClickListener(click -> addArticle()); + + HorizontalLayout toolbar = new HorizontalLayout(filterText, addArticleButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editArticle(Article article) { + if (article == null) { + closeEditor(); + } else { + form.setArticle(article); + form.setVisible(true); + addClassName("editing"); + } + } + + private void closeEditor() { + form.setArticle(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addArticle() { + grid.asSingleSelect().clear(); + editArticle(new Article()); + } + + public void updateList() { + grid.setItems(service.findAllArticles(filterText.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/AuthorForm.java b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/AuthorForm.java new file mode 100644 index 0000000..34d0f5d --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/AuthorForm.java @@ -0,0 +1,117 @@ +package de.thpeetz.kontor.bookshelf.views; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.listbox.ListBox; +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 de.thpeetz.kontor.bookshelf.data.Author; +import de.thpeetz.kontor.bookshelf.data.Book; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +@Slf4j +public class AuthorForm extends FormLayout { + + TextField firstName = new TextField("First Name"); + TextField lastName = new TextField("Last Name"); + ListBox books = new ListBox<>(); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(Author.class); + + public AuthorForm() { + addClassName("author-form"); + binder.bindInstanceFields(this); + + books.setHeight("100px"); + //books.setColumns("title", "publisher.name"); + //books.getColumns().forEach(col -> col.setAutoWidth(true)); + add(firstName, lastName, books, createButtonsLayout()); + } + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new AuthorForm.DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new AuthorForm.CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new AuthorForm.SaveEvent(this, binder.getBean())); + } + } + + public void setAuthor(Author author) { + binder.setBean(author); + } + + public void setBooks(List books) { + log.info("setting Books: ", books); + this.books.setItems(books); + } + + public abstract static class AuthorFormEvent extends ComponentEvent { + private Author author; + + protected AuthorFormEvent(AuthorForm source, Author author) { + super(source, false); + this.author = author; + } + + public Author getAuthor() { + return author; + } + } + + public static class SaveEvent extends AuthorForm.AuthorFormEvent { + SaveEvent(AuthorForm source, Author author) { + super(source, author); + } + } + + public static class DeleteEvent extends AuthorForm.AuthorFormEvent { + DeleteEvent(AuthorForm source, Author author) { + super(source, author); + } + } + + public static class CloseEvent extends AuthorForm.AuthorFormEvent { + CloseEvent(AuthorForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(AuthorForm.DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(AuthorForm.SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(AuthorForm.CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/AuthorView.java b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/AuthorView.java new file mode 100644 index 0000000..17897b0 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/AuthorView.java @@ -0,0 +1,132 @@ +package de.thpeetz.kontor.bookshelf.views; + +import java.util.stream.Collectors; + +import org.springframework.context.annotation.Scope; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; + +import de.thpeetz.kontor.bookshelf.BookshelfConstants; +import de.thpeetz.kontor.bookshelf.data.Author; +import de.thpeetz.kontor.bookshelf.data.BookAuthor; +import de.thpeetz.kontor.bookshelf.services.BookshelfService; +import de.thpeetz.kontor.common.views.MainLayout; +import jakarta.annotation.security.PermitAll; + +@SpringComponent +@Scope("prototype") +@PermitAll +@Route(value = BookshelfConstants.AUTHOR_ROUTE, layout = MainLayout.class) +@PageTitle("Author | Bookshelf | Kontor") +public class AuthorView extends VerticalLayout { + + Grid grid = new Grid<>(Author.class); + TextField filterText = new TextField(); + AuthorForm form; + BookshelfService service; + + public AuthorView(BookshelfService service) { + this.service = service; + addClassName("author-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + public Grid getGrid() { + return grid; + } + + public AuthorForm getForm() { + return form; + } + + private void configureGrid() { + grid.addClassName("author-grid"); + grid.setSizeFull(); + grid.setColumns("firstName", "lastName"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editAuthor(event.getValue())); + } + + private void configureForm() { + form = new AuthorForm(); + form.setWidth("25em"); + form.setVisible(false); + form.addSaveListener(this::saveAuthor); + form.addDeleteListener(this::deleteAuthor); + form.addCloseListener(e -> closeEditor()); + } + private void saveAuthor(AuthorForm.SaveEvent event) { + service.saveAuthor(event.getAuthor()); + updateList(); + closeEditor(); + } + + private void deleteAuthor(AuthorForm.DeleteEvent event) { + service.deleteAuthor(event.getAuthor()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + filterText.setPlaceholder("Filter by name..."); + filterText.setClearButtonVisible(true); + filterText.setValueChangeMode(ValueChangeMode.LAZY); + filterText.addValueChangeListener(e -> updateList()); + + Button addAuthorButton = new Button("Add author"); + addAuthorButton.addClickListener(click -> addAuthor()); + + HorizontalLayout toolbar = new HorizontalLayout(filterText, addAuthorButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editAuthor(Author author) { + if (author == null) { + closeEditor(); + } else { + form.setAuthor(author); + form.setBooks(author.getBookAuthors().stream().map(BookAuthor::getBook).collect(Collectors.toList())); + form.setVisible(true); + addClassName("editing"); + } + } + + private void closeEditor() { + form.setAuthor(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addAuthor() { + grid.asSingleSelect().clear(); + editAuthor(new Author()); + } + + public void updateList() { + grid.setItems(service.findAllAuthors(filterText.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/BookForm.java b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/BookForm.java new file mode 100644 index 0000000..61da274 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/BookForm.java @@ -0,0 +1,111 @@ +package de.thpeetz.kontor.bookshelf.views; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.textfield.IntegerField; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.binder.BeanValidationBinder; +import com.vaadin.flow.data.binder.Binder; +import de.thpeetz.kontor.bookshelf.data.Book; +import de.thpeetz.kontor.bookshelf.data.BookshelfPublisher; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +@Slf4j +public class BookForm extends FormLayout { + + TextField title = new TextField("Title"); + TextField isbn = new TextField("ISBN"); + IntegerField year = new IntegerField("Year"); + ComboBox publisher = new ComboBox<>("Publisher"); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(Book.class); + + public BookForm(List publishers) { + addClassName("book-form"); + binder.bindInstanceFields(this); + + publisher.setItems(publishers); + publisher.setItemLabelGenerator(BookshelfPublisher::getName); + add(title, isbn, year, publisher, createButtonsLayout()); + } + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new BookForm.DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new BookForm.CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new BookForm.SaveEvent(this, binder.getBean())); + } + } + + public void setBook(Book book) { + binder.setBean(book); + } + + public abstract static class BookFormEvent extends ComponentEvent { + private Book book; + + protected BookFormEvent(BookForm source, Book book) { + super(source, false); + this.book = book; + } + + public Book getBook() { + return book; + } + } + + public static class SaveEvent extends BookForm.BookFormEvent { + SaveEvent(BookForm source, Book book) { + super(source, book); + } + } + + public static class DeleteEvent extends BookForm.BookFormEvent { + DeleteEvent(BookForm source, Book book) { + super(source, book); + } + } + + public static class CloseEvent extends BookForm.BookFormEvent { + CloseEvent(BookForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(BookForm.DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(BookForm.SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(BookForm.CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/BookView.java b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/BookView.java new file mode 100644 index 0000000..9e04dc6 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/BookView.java @@ -0,0 +1,124 @@ +package de.thpeetz.kontor.bookshelf.views; + +import org.springframework.context.annotation.Scope; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; + +import de.thpeetz.kontor.bookshelf.BookshelfConstants; +import de.thpeetz.kontor.bookshelf.data.Book; +import de.thpeetz.kontor.bookshelf.services.BookshelfService; +import de.thpeetz.kontor.common.views.MainLayout; +import jakarta.annotation.security.PermitAll; +import lombok.Getter; + +@SpringComponent +@Scope("prototype") +@PermitAll +@Route(value = BookshelfConstants.BOOK_ROUTE, layout = MainLayout.class) +@PageTitle("Book | Bookshelf | Kontor") +public class BookView extends VerticalLayout { + + @Getter + Grid grid = new Grid<>(Book.class); + TextField filterText = new TextField(); + @Getter + BookForm form; + BookshelfService service; + + public BookView(BookshelfService service) { + this.service = service; + addClassName("book-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + private void configureGrid() { + grid.addClassName("book-grid"); + grid.setSizeFull(); + grid.setColumns("title", "isbn", "publisher.name", "year"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editBook(event.getValue())); + } + + private void configureForm() { + form = new BookForm(service.findAllPublishers(null)); + form.setWidth("25em"); + form.setVisible(false); + form.addSaveListener(this::saveBook); + form.addDeleteListener(this::deleteBook); + form.addCloseListener(e -> closeEditor()); + } + + private void saveBook(BookForm.SaveEvent event) { + service.saveBook(event.getBook()); + updateList(); + closeEditor(); + } + + private void deleteBook(BookForm.DeleteEvent event) { + service.deleteBook(event.getBook()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + filterText.setPlaceholder("Filter by title or isbn..."); + filterText.setClearButtonVisible(true); + filterText.setValueChangeMode(ValueChangeMode.LAZY); + filterText.addValueChangeListener(e -> updateList()); + + Button addBookButton = new Button("Add book"); + addBookButton.addClickListener(click -> addBook()); + + HorizontalLayout toolbar = new HorizontalLayout(filterText, addBookButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editBook(Book book) { + if (book == null) { + closeEditor(); + } else { + form.setBook(book); + form.setVisible(true); + addClassName("editing"); + } + } + + private void closeEditor() { + form.setBook(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addBook() { + grid.asSingleSelect().clear(); + editBook(new Book()); + } + + public void updateList() { + grid.setItems(service.findAllBooks(filterText.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/BookshelfLayout.java b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/BookshelfLayout.java new file mode 100644 index 0000000..6105a47 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/BookshelfLayout.java @@ -0,0 +1,34 @@ +package de.thpeetz.kontor.bookshelf.views; + +import com.vaadin.flow.component.applayout.AppLayout; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.theme.lumo.LumoUtility; + +import de.thpeetz.kontor.admin.services.AdminService; +import de.thpeetz.kontor.bookshelf.BookshelfConstants; +import de.thpeetz.kontor.common.views.KontorLayoutUtil; +import de.thpeetz.kontor.security.SecurityService; + +public class BookshelfLayout extends AppLayout { + + private final AdminService adminService; + + private final SecurityService securityService; + + public BookshelfLayout(AdminService adminService, SecurityService securityService) { + this.adminService = adminService; + this.securityService = securityService; + + KontorLayoutUtil layout = new KontorLayoutUtil(this, adminService, securityService); + layout.setSecondaryNavigation(getSecondaryNavigation()); + layout.createHeader(BookshelfConstants.BOOKSHELF); + } + + private HorizontalLayout getSecondaryNavigation() { + HorizontalLayout navigation = new HorizontalLayout(); + navigation.addClassNames(LumoUtility.JustifyContent.CENTER, LumoUtility.Gap.SMALL, LumoUtility.Height.MEDIUM); + navigation.add(BookshelfConstants.getBookLink(), BookshelfConstants.getArticleLink(), + BookshelfConstants.getPublisherLink(), BookshelfConstants.getAuthorLink()); + return navigation; + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/BookshelfPublisherView.java b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/BookshelfPublisherView.java new file mode 100644 index 0000000..de5f9b5 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/BookshelfPublisherView.java @@ -0,0 +1,131 @@ +package de.thpeetz.kontor.bookshelf.views; + +import de.thpeetz.kontor.common.views.MainLayout; +import de.thpeetz.kontor.common.views.SeparateMainLayout; +import org.springframework.context.annotation.Scope; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; + +import de.thpeetz.kontor.bookshelf.BookshelfConstants; +import de.thpeetz.kontor.bookshelf.data.BookshelfPublisher; +import de.thpeetz.kontor.bookshelf.services.BookshelfService; +import jakarta.annotation.security.PermitAll; + +@SpringComponent +@Scope("prototype") +@PermitAll +@Route(value = BookshelfConstants.PUBLISHER_ROUTE, layout = MainLayout.class) +@PageTitle("Publisher | Bookshelf | Kontor") +public class BookshelfPublisherView extends VerticalLayout { + + Grid grid = new Grid<>(BookshelfPublisher.class); + TextField filterText = new TextField(); + PublisherForm form; + + BookshelfService service; + + public BookshelfPublisherView(BookshelfService service) { + this.service = service; + addClassName("publisher-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + private void configureGrid() { + grid.addClassName("publisher-grid"); + grid.setSizeFull(); + grid.setColumns("name"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editPublisher(event.getValue())); + } + + private void configureForm() { + form = new PublisherForm(); + form.setWidth("25em"); + form.setVisible(false); + form.addSaveListener(this::savePublisher); + form.addDeleteListener(this::deletePublisher); + form.addCloseListener(e -> closeEditor()); + } + + private void savePublisher(PublisherForm.SaveEvent event) { + service.savePublisher(event.getPublisher()); + updateList(); + closeEditor(); + } + + private void deletePublisher(PublisherForm.DeleteEvent event) { + service.deletePublisher(event.getPublisher()); + updateList(); + closeEditor(); + } + + public Grid getGrid() { + return grid; + } + + public PublisherForm getForm() { + return form; + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + filterText.setPlaceholder("Filter by name..."); + filterText.setClearButtonVisible(true); + filterText.setValueChangeMode(ValueChangeMode.LAZY); + filterText.addValueChangeListener(e -> updateList()); + + Button addPublisherButton = new Button("Add publisher"); + addPublisherButton.addClickListener(click -> addPublisher()); + + HorizontalLayout toolbar = new HorizontalLayout(filterText, addPublisherButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editPublisher(BookshelfPublisher publisher) { + if (publisher == null) { + closeEditor(); + } else { + form.setPublisher(publisher); + form.setVisible(true); + addClassName("editing"); + } + } + + private void closeEditor() { + form.setPublisher(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addPublisher() { + grid.asSingleSelect().clear(); + editPublisher(new BookshelfPublisher()); + } + + public void updateList() { + grid.setItems(service.findAllPublishers(filterText.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/PublisherForm.java b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/PublisherForm.java new file mode 100644 index 0000000..7b48122 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/bookshelf/views/PublisherForm.java @@ -0,0 +1,108 @@ +package de.thpeetz.kontor.bookshelf.views; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +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 de.thpeetz.kontor.bookshelf.data.BookshelfPublisher; + +public class PublisherForm extends FormLayout { + private TextField name = new TextField("Name"); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder(BookshelfPublisher.class); + + public PublisherForm() { + addClassName("publisher-form"); + binder.bindInstanceFields(this); + + add(name, createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new SaveEvent(this, binder.getBean())); + } + } + + public TextField getName() { + return name; + } + + public void setName(TextField name) { + this.name = name; + } + + public void setPublisher(BookshelfPublisher publisher) { + binder.setBean(publisher); + } + + public abstract static class PublisherFormEvent extends ComponentEvent { + private BookshelfPublisher publisher; + + protected PublisherFormEvent(PublisherForm source, BookshelfPublisher publisher) { + super(source, false); + this.publisher = publisher; + } + + public BookshelfPublisher getPublisher() { + return publisher; + } + } + + public static class SaveEvent extends PublisherFormEvent { + SaveEvent(PublisherForm source, BookshelfPublisher publisher) { + super(source, publisher); + } + } + + public static class DeleteEvent extends PublisherFormEvent { + DeleteEvent(PublisherForm source, BookshelfPublisher publisher) { + super(source, publisher); + } + } + + public static class CloseEvent extends PublisherFormEvent { + CloseEvent(PublisherForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/ComicConstants.java b/springboot/src/main/java/de/thpeetz/kontor/comics/ComicConstants.java new file mode 100644 index 0000000..c89f7b1 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/ComicConstants.java @@ -0,0 +1,93 @@ +package de.thpeetz.kontor.comics; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.icon.VaadinIcon; +import com.vaadin.flow.component.sidenav.SideNavItem; +import com.vaadin.flow.router.RouterLink; + +import de.thpeetz.kontor.comics.views.ArtistView; +import de.thpeetz.kontor.comics.views.ComicView; +import de.thpeetz.kontor.comics.views.ComicWorkView; +import de.thpeetz.kontor.comics.views.IssueView; +import de.thpeetz.kontor.comics.views.PublisherView; +import de.thpeetz.kontor.comics.views.StoryArcView; +import de.thpeetz.kontor.comics.views.TradePaperbackView; +import de.thpeetz.kontor.comics.views.VolumeView; +import de.thpeetz.kontor.comics.views.WorktypeView; + +/** + * The {@code ComicConstants} class contains constant values related to comics. + */ +public class ComicConstants { + + public static final String COMICS = "Comics"; + public static final String COMICS_ROUTE = "comics/comic"; + public static final String PUBLISHER = "Publisher"; + public static final String PUBLISHER_ROUTE = "comics/publisher"; + public static final String COMICWORK = "ComicWork"; + public static final String COMICWORK_ROUTE = "comics/comicwork"; + public static final String WORKTYPE = "Worktype"; + public static final String WORKTYPE_ROUTE = "comics/worktype"; + public static final String ARTIST = "Artist"; + public static final String ARTIST_ROUTE = "comics/artist"; + public static final String TPB = "TradePaperback"; + public static final String TPB_ROUTE = "comics/tradepaperback"; + public static final String STORYARC = "Story Arc"; + public static final String STORYARC_ROTE = "comics/storyarc"; + public static final String ISSUE = "Issue"; + public static final String ISSUE_ROUTE = "comics/issue"; + public static final String VOLUME = "Volume"; + public static final String VOLUME_ROUTE = "comics/volume"; + + public static RouterLink getArtistLink() { + return new RouterLink(ARTIST, ArtistView.class); + } + + public static RouterLink getComicLink() { + return new RouterLink(COMICS, ComicView.class); + } + + public static RouterLink getPublisherLink() { + return new RouterLink(PUBLISHER, PublisherView.class); + } + + public static RouterLink getComicWorkLink() { + return new RouterLink(COMICWORK, ComicWorkView.class); + } + + public static RouterLink getIssueLink() { + return new RouterLink(ISSUE, IssueView.class); + } + + public static RouterLink getTradePaperbackLink() { + return new RouterLink(TPB, TradePaperbackView.class); + } + + public static Component getStoryArcLink() { + return new RouterLink(STORYARC, StoryArcView.class); + } + + public static RouterLink getWorktypeLink() { + return new RouterLink(WORKTYPE, WorktypeView.class); + } + + public static RouterLink getVolumeLink() { + return new RouterLink(VOLUME, VolumeView.class); + } + + public static SideNavItem getComicsNavigation() { + SideNavItem comics = new SideNavItem(COMICS, COMICS_ROUTE, VaadinIcon.RECORDS.create()); + comics.addItem(new SideNavItem(ARTIST, ArtistView.class)); + comics.addItem(new SideNavItem(COMICS, ComicView.class)); + comics.addItem(new SideNavItem(PUBLISHER, PublisherView.class)); + comics.addItem(new SideNavItem(ISSUE, IssueView.class)); + comics.addItem(new SideNavItem(TPB, TradePaperbackView.class)); + comics.addItem(new SideNavItem(STORYARC, StoryArcView.class)); + comics.addItem(new SideNavItem(VOLUME, VolumeView.class)); + return comics; + } + + private ComicConstants() { + // private constructor to hide the implicit public one + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/SetupModuleComics.java b/springboot/src/main/java/de/thpeetz/kontor/comics/SetupModuleComics.java new file mode 100644 index 0000000..52674fd --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/SetupModuleComics.java @@ -0,0 +1,1198 @@ +package de.thpeetz.kontor.comics; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.stereotype.Component; + +import de.thpeetz.kontor.admin.services.ModuleService; +import de.thpeetz.kontor.comics.data.Artist; +import de.thpeetz.kontor.comics.data.ArtistRepository; +import de.thpeetz.kontor.comics.data.Comic; +import de.thpeetz.kontor.comics.data.ComicRepository; +import de.thpeetz.kontor.comics.data.ComicWork; +import de.thpeetz.kontor.comics.data.ComicWorkRepository; +import de.thpeetz.kontor.comics.data.Issue; +import de.thpeetz.kontor.comics.data.IssueRepository; +import de.thpeetz.kontor.comics.data.Publisher; +import de.thpeetz.kontor.comics.data.PublisherRepository; +import de.thpeetz.kontor.comics.data.StoryArc; +import de.thpeetz.kontor.comics.data.StoryArcRepository; +import de.thpeetz.kontor.comics.data.TradePaperback; +import de.thpeetz.kontor.comics.data.TradePaperbackRepository; +import de.thpeetz.kontor.comics.data.Volume; +import de.thpeetz.kontor.comics.data.VolumeRepository; +import de.thpeetz.kontor.comics.data.Worktype; +import de.thpeetz.kontor.comics.data.WorktypeRepository; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +public class SetupModuleComics implements ApplicationListener { + + boolean alreadySetup = false; + + @Autowired + private PublisherRepository publisherRepository; + + @Autowired + private ArtistRepository artistRepository; + + @Autowired + private WorktypeRepository worktypeRepository; + + @Autowired + private ComicRepository comicRepository; + + @Autowired + private ComicWorkRepository comicWorkRepository; + + @Autowired + private StoryArcRepository storyArcRepository; + + @Autowired + private TradePaperbackRepository tradePaperbackRepository; + + @Autowired + private IssueRepository issueRepository; + + @Autowired + private VolumeRepository volumeRepository; + + @Autowired + private ModuleService moduleService; + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + if (alreadySetup) { + log.info("SetupModuleComics already executed, skipping"); + return; + } + if (!moduleService.importData(ComicConstants.COMICS)) { + log.info("Module comics should not setup data"); + return; + } + log.info("Set up Comics data"); + Publisher marvel = createPublisherIfNotFound("Marvel"); + Publisher alias = createPublisherIfNotFound("Alias"); + Publisher crossgen = createPublisherIfNotFound("Crossgen"); + Publisher image = createPublisherIfNotFound("Image"); + Publisher ddp = createPublisherIfNotFound("Devils Due Publishing"); + Publisher aspen = createPublisherIfNotFound("Aspen"); + Publisher bongo = createPublisherIfNotFound("Bongo Comics"); + Publisher kandora = createPublisherIfNotFound("Kandora"); + Publisher dc = createPublisherIfNotFound("DC"); + Publisher marvelknights = createPublisherIfNotFound("Marvel Knights"); + Publisher wildstorm = createPublisherIfNotFound("WildStorm"); + Publisher cliffhanger = createPublisherIfNotFound("Cliffhanger"); + Publisher darkhorse = createPublisherIfNotFound("Dark Horse Comics"); + Publisher broadsword = createPublisherIfNotFound("Broadsword"); + Publisher dynamite = createPublisherIfNotFound("Dynamite Entertainment"); + Publisher redeagle = createPublisherIfNotFound("Red Eagle Entertainment"); + Publisher topcow = createPublisherIfNotFound("Top Cow Productions"); + Publisher pulpfiction = createPublisherIfNotFound("Pulp Fiction"); + Artist michaelturner = createArtistIfNotFound("Turner, Michael"); + createArtistIfNotFound("Marz, Ron"); + createArtistIfNotFound("Whedon, Joss"); + createArtistIfNotFound("Land, Greg"); + Artist brianbendis = createArtistIfNotFound("Bendis, Brian Michael"); + Worktype writer = createWorktypeIfNotFound("Writer"); + createWorktypeIfNotFound("Penciler"); + createWorktypeIfNotFound("Inker"); + Comic comic1602 = createComicIfNotFound(marvel, "1602", false, false); + createComicIfNotFound(alias, "10th Muse", false, false); + Comic abadazad = createComicIfNotFound(crossgen, "Abadazad", false, false); + Comic amazingfantasy = createComicIfNotFound(marvel, "Amazing Fantasy", false, false); + Comic amazingspiderman = createComicIfNotFound(marvel, "Amazing Spider-Man", false, false); + Comic arana = createComicIfNotFound(marvel, "Arana", false, false); + Comic aria = createComicIfNotFound(image, "Aria", false, false); + createComicIfNotFound(ddp, "Army of Darkness", false, false); + Comic aspenComic = createComicIfNotFound(aspen, "Aspen", false, false); + Comic astonishingxmen = createComicIfNotFound(marvel, "Astonishing X-Men", false, false); + Comic athena = createComicIfNotFound(image, "Athena Inc. The Beginning", false, false); + Comic athenamanhunter = createComicIfNotFound(image, "Athena Inc. The Manhunter Project", false, false); + Comic barbarossa = createComicIfNotFound(kandora, "Barbarossa & The Lost Corsairs", false, false); + Comic bartsimpson = createComicIfNotFound(bongo, "Bart Simpson", false, false); + Comic bartsimpsontree = createComicIfNotFound(bongo, "Bart Simpsons Treehouse of Horror", false, false); + Comic battlepope = createComicIfNotFound(image, "Battle Pope", false, false); + Comic birdsofprey = createComicIfNotFound(dc, "Birds of Prey", false, false); + Comic blackwidow = createComicIfNotFound(marvelknights, "Black Widow", false, false); + Comic blackwidow2 = createComicIfNotFound(marvelknights, "Black Widow 2", false, false); + createComicIfNotFound(image, "Bluntman and Chronic", false, false); + Comic brath = createComicIfNotFound(crossgen, "Brath", false, false); + Comic catwomanrome = createComicIfNotFound(dc, "Catwoman When In Rome", false, false); + Comic crimson = createComicIfNotFound(wildstorm, "Crimson", false, false); + Comic crossgencomic = createComicIfNotFound(crossgen, "Crossgen", false, false); + Comic dangergirl = createComicIfNotFound(cliffhanger, "Danger Girl", false, false); + Comic dangergirlbackinblack = createComicIfNotFound(wildstorm, "Danger Girl Back in Black", false, false); + Comic daringescapes = createComicIfNotFound(image, "Daring Escapes", false, true); + Comic darknesssuperman = createComicIfNotFound(image, "Darkness / Superman", false, false); + Comic darknesstombraider = createComicIfNotFound(image, "Darkness / Tomb Raider", false, false); + Comic darknessvampirella = createComicIfNotFound(image, "Darkness / Vampirella", false, false); + Comic darknessblacksails = createComicIfNotFound(image, "Darkness Black Sails", false, false); + Comic darknessvol2 = createComicIfNotFound(image, "Darkness Vol. 2", false, false); + Comic districtx = createComicIfNotFound(marvel, "District X", false, false); + Comic dragonlance = createComicIfNotFound(ddp, "Dragonlance: Chronicles", false, false); + Comic dreampolice = createComicIfNotFound(marvel, "Dream Police", false, false); + Comic elcazador = createComicIfNotFound(crossgen, "El Cazador", false, false); + Comic elcazadortom = createComicIfNotFound(crossgen, "El Cazador The Bloody Ballad of Blackjack Tom", false, + false); + Comic elsinore = createComicIfNotFound(alias, "Elsinore", false, false); + Comic emmafrost = createComicIfNotFound(marvel, "Emma Frost", false, false); + Comic excalibur = createComicIfNotFound(marvel, "Excalibur", false, false); + Comic fathom = createComicIfNotFound(aspen, "Fathom", false, false); + Comic fathombeginnings = createComicIfNotFound(aspen, "Fathom Beginnings", false, false); + Comic fathomcannon = createComicIfNotFound(aspen, "Fathom Cannon Hawke", false, false); + Comic fathomcannonprelude = createComicIfNotFound(aspen, "Fathom Cannon Hawke Prelude", false, false); + Comic fathomdawnofwar = createComicIfNotFound(aspen, "Fathom Dawn of War", false, false); + Comic fathomprelude = createComicIfNotFound(aspen, "Fathom Prelude", false, false); + Comic fathomswimsuit = createComicIfNotFound(aspen, "Fathom Swimsuit Special", false, false); + Comic fathomswimsuit2000 = createComicIfNotFound(aspen, "Fathom Swimsuit Special 2000", false, false); + Comic fathomvol2 = createComicIfNotFound(aspen, "Fathom Vol. 2", false, false); + Comic fathomkillian = createComicIfNotFound(aspen, "Fathom: Killians Tide", false, false); + Comic flakriot = createComicIfNotFound(image, "Flak Riot", false, false); + Comic freshmen = createComicIfNotFound(image, "Freshmen", false, false); + Comic friendlyneighborspider = createComicIfNotFound(marvel, "Friendly Neighborhood Spider-Man", false, false); + Comic futurama = createComicIfNotFound(bongo, "Futurama", false, false); + Comic futuramaco2 = createComicIfNotFound(bongo, "Futurama Simpsons Crossover Crisis Part 2", false, false); + Comic ghostrider = createComicIfNotFound(marvelknights, "Ghostrider", false, false); + Comic gift = createComicIfNotFound(image, "Gift", false, false); + Comic hackslashtoys = createComicIfNotFound(ddp, "Hack Slash Land of Lost Toys", false, false); + Comic hackslashgirlsgonedead = createComicIfNotFound(ddp, "Hack/Slash: Girls Gone Dead", false, false); + Comic harryjohnson = createComicIfNotFound(pulpfiction, "Harry Johnson", false, false); + Comic hellcop = createComicIfNotFound(image, "Hellcop", false, false); + Comic holidayspecial2004 = createComicIfNotFound(marvel, "Holiday Special 2004", false, false); + Comic housem = createComicIfNotFound(marvel, "House of M", false, false); + Comic hunterkiller = createComicIfNotFound(image, "Hunter-Killer", false, false); + Comic hunterkillerdossier = createComicIfNotFound(image, "Hunter-Killer Dossier", false, false); + Comic ironghost = createComicIfNotFound(image, "Iron Ghost", false, false); + Comic judge = createComicIfNotFound(image, "J.U.D.G.E.: Secret Rage", false, false); + Comic kisskissbangbang = createComicIfNotFound(crossgen, "Kiss Kiss Bang Bang", false, false); + Comic legendofisis = createComicIfNotFound(alias, "Legend of Isis", false, false); + Comic loki = createComicIfNotFound(marvel, "Loki", false, false); + Comic lullaby = createComicIfNotFound(image, "Lullaby", false, false); + Comic magdalenavampirella2 = createComicIfNotFound(topcow, "Magdalena / Vampirella 2", false, false); + Comic marvelknightspider = createComicIfNotFound(marvelknights, "Marvel Knights Spider-Man", false, false); + Comic marville = createComicIfNotFound(marvel, "Marville", false, false); + Comic maryjane = createComicIfNotFound(marvel, "Mary Jane", false, false); + Comic maryjanehome = createComicIfNotFound(marvel, "Mary Jane Homecoming", false, false); + Comic megacity = createComicIfNotFound(ddp, "Megacity 909", false, false); + Comic meridian = createComicIfNotFound(crossgen, "Meridian", false, false); + Comic midnightnation = createComicIfNotFound(image, "Midnight Nation", false, true); + Comic monsterwar = createComicIfNotFound(image, "Monster War", false, false); + Comic monsterwar2005 = createComicIfNotFound(image, "Monster War 2005", false, false); + Comic mystic = createComicIfNotFound(crossgen, "Mystic", false, false); + Comic mystique = createComicIfNotFound(marvel, "Mystique", false, false); + Comic necromancer = createComicIfNotFound(image, "Necromancer", false, false); + Comic negationwar = createComicIfNotFound(crossgen, "Negation War", false, false); + Comic newavengers = createComicIfNotFound(marvel, "New Avengers", false, false); + Comic newmutants = createComicIfNotFound(marvel, "New Mutants", false, false); + Comic newxmen = createComicIfNotFound(marvel, "New X-Men", false, false); + Comic newxmenacademy = createComicIfNotFound(marvel, "New X-Men Academy X", false, false); + Comic newxmenhellions = createComicIfNotFound(marvel, "New X-Men Hellions", false, false); + Comic nightcrawler = createComicIfNotFound(marvel, "Nightcrawler", false, false); + Comic ororo = createComicIfNotFound(marvel, "Ororo: Before the Storm", false, false); + Comic radix = createComicIfNotFound(image, "Radix", false, false); + Comic redsonja = createComicIfNotFound(dynamite, "Red Sonja", false, false); + Comic revelations = createComicIfNotFound(darkhorse, "Revelations", false, false); + Comic rogue = createComicIfNotFound(marvel, "Rogue", false, false); + Comic ruse = createComicIfNotFound(crossgen, "Ruse", false, false); + Comic samurai = createComicIfNotFound(darkhorse, "Samurai: Heaven & Earth", false, false); + Comic scion = createComicIfNotFound(crossgen, "Scion", false, false); + Comic shanna = createComicIfNotFound(marvelknights, "Shanna, The She-Devil", false, false); + Comic shehulk = createComicIfNotFound(marvel, "She-Hulk", false, false); + Comic shehulk2 = createComicIfNotFound(marvel, "She-Hulk 2", false, false); + Comic shijunen = createComicIfNotFound(darkhorse, "Shi Ju-Nen", false, false); + Comic shrek = createComicIfNotFound(darkhorse, "Shrek", false, false); + Comic simpsons = createComicIfNotFound(bongo, "Simpsons", false, false); + Comic sojourn = createComicIfNotFound(crossgen, "Sojourn", false, false); + Comic solus = createComicIfNotFound(crossgen, "Solus", false, false); + Comic soulfire = createComicIfNotFound(aspen, "Soulfire", false, false); + Comic soulfirelight = createComicIfNotFound(aspen, "Soulfire Dying of the Light", false, false); + Comic spectacularspiderman = createComicIfNotFound(marvel, "Spectacular Spider-Man", false, false); + Comic spellbinders = createComicIfNotFound(marvel, "Spellbinders", false, false); + Comic spidermanindia = createComicIfNotFound(marvel, "Spider-Man India", false, false); + Comic spidermanlovesmary = createComicIfNotFound(marvel, "Spider-Man loves Mary Jane", false, false); + Comic spidermanteam = createComicIfNotFound(marvel, "Spider-Man Team Up", false, false); + Comic spidermanbreakout = createComicIfNotFound(marvel, "Spider-Man: Breakout", false, false); + Comic spidermanhousem = createComicIfNotFound(marvel, "Spider-Man: House of M", false, false); + Comic starwars = createComicIfNotFound(darkhorse, "Star Wars", false, false); + Comic stardustkid = createComicIfNotFound(image, "Stardust Kid", false, false); + Comic strange = createComicIfNotFound(marvel, "Strange", false, false); + Comic supergirl = createComicIfNotFound(dc, "Supergirl", false, false); + Comic superman = createComicIfNotFound(dc, "Superman", false, false); + Comic supermanbatman = createComicIfNotFound(dc, "Superman/Batman", false, false); + Comic tarotblackrose = createComicIfNotFound(broadsword, "Tarot: Witch of the Black Rose", false, false); + Comic artgreghorn = createComicIfNotFound(image, "The Art of Greg Horn", false, false); + Comic devilskeeper = createComicIfNotFound(alias, "The Devil´s Keeper", false, false); + Comic thetenth = createComicIfNotFound(image, "The Tenth", false, false); + Comic tombdracula = createComicIfNotFound(marvel, "The Tomb of Dracula", false, false); + Comic robertjordanwheeloftime = createComicIfNotFound(redeagle, "Robert Jordan´s The Wheel of Time: New Spring", + false, false); + Comic thorsonasgard = createComicIfNotFound(marvel, "Thor: Son of Asgard", false, false); + Comic tomstrong = createComicIfNotFound(wildstorm, "Tom Strong", false, false); + Comic tombraider = createComicIfNotFound(image, "Tomb Raider", false, false); + Comic tombraidergreatesttreasure = createComicIfNotFound(image, "Tomb Raider: The Greatest Treasure of All", + false, false); + Comic toxin = createComicIfNotFound(marvel, "Toxin", false, false); + Comic ultimatefantasticfour = createComicIfNotFound(marvel, "Ultimate Fantastic Four", false, false); + Comic ultimatespidermanannual = createComicIfNotFound(marvel, "Ultimate Spider-Man Annual", false, false); + Comic uncannyxmen = createComicIfNotFound(marvel, "Uncanny X-Men", false, false); + Comic vampirella = createComicIfNotFound(dynamite, "Vampirella", false, false); + Comic wildgirl = createComicIfNotFound(wildstorm, "Wild Girl", false, false); + Comic wildcats = createComicIfNotFound(wildstorm, "Wildcats: Nemesis", false, false); + Comic wildsiderz = createComicIfNotFound(wildstorm, "Wildsiderz", false, false); + Comic witchblade = createComicIfNotFound(image, "Witchblade", false, false); + Comic witchbladetombraider = createComicIfNotFound(image, "Witchblade / Tomb Raider", false, false); + Comic wolverine = createComicIfNotFound(marvel, "Wolverine: The End", false, false); + Comic woodboy = createComicIfNotFound(image, "Wood Boy", false, false); + Comic wraithborn = createComicIfNotFound(wildstorm, "Wraithborn", false, false); + Comic x23 = createComicIfNotFound(marvel, "X-23", false, false); + Comic xmenageofapocalypse = createComicIfNotFound(marvel, "X-Men: Age of Apocalypse", false, false); + Comic xmenageofapocalypseoneshot = createComicIfNotFound(marvel, "X-Men: Age of Apocalypse One Shot", false, + false); + Comic xmenkittypryde = createComicIfNotFound(marvel, "X-Men: Kitty Pryde", false, false); + Comic phoenix = createComicIfNotFound(marvel, "X-Men: Phoenix - Endsong", false, false); + Comic xtremexmen = createComicIfNotFound(marvel, "X-treme X-Men", false, false); + Comic armydarknessreanim = createComicIfNotFound(dynamite, "Army of Darkness vs. Re-Animator", false, false); + Comic runaways = createComicIfNotFound(marvel, "Runaways", false, false); + Comic crux = createComicIfNotFound(crossgen, "Crux", false, false); + Comic ariasoulmarket = createComicIfNotFound(image, "Aria: The Soul Market", false, false); + Comic ariasummerspell = createComicIfNotFound(image, "Aria: Summer´s Spell", false, false); + Comic ariaenchantment = createComicIfNotFound(image, "Aria: The Uses of Enchantment", false, false); + Comic armydarknessashes = createComicIfNotFound(darkhorse, "Army of Darkness: Ashes 2 Ashes", false, false); + Comic armydarknessshop = createComicIfNotFound(darkhorse, "Army of Darkness: Shop Till You Drop Dead", false, + false); + createComicIfNotFound(marvel, "X-Men", false, false); + createComicIfNotFound(image, "Bomb Queen II: Queen of Hearts", false, false); + createComicIfNotFound(image, "Bomb Queen III: The Good, The Bad and The Lovely", false, false); + createComicIfNotFound(image, "Bomb Queen IV: Suicide Bomber", false, false); + createComicIfNotFound(wildstorm, "Gen13", false, false); + createComicIfNotFound(aspen, "Iron & The Maiden", false, false); + createComicIfNotFound(darkhorse, "Star Wars: Rebellion", false, false); + createComicIfNotFound(darkhorse, "Star Wars: Knights of the Old Republic", false, false); + createComicIfNotFound(darkhorse, "Star Wars: Legacy", false, false); + createComicIfNotFound(darkhorse, "Star Wars: Dark Times", false, false); + createComicWorkIfNotFound(crossgencomic, michaelturner, writer); + createComicWorkIfNotFound(dangergirl, michaelturner, writer); + createComicWorkIfNotFound(ultimatefantasticfour, michaelturner, writer); + createComicWorkIfNotFound(ultimatespidermanannual, michaelturner, writer); + createComicWorkIfNotFound(uncannyxmen, michaelturner, writer); + createComicWorkIfNotFound(starwars, michaelturner, writer); + createComicWorkIfNotFound(shehulk, michaelturner, writer); + createComicWorkIfNotFound(shehulk2, michaelturner, writer); + createComicWorkIfNotFound(scion, michaelturner, writer); + createComicWorkIfNotFound(newavengers, michaelturner, writer); + createComicWorkIfNotFound(newmutants, michaelturner, writer); + createComicWorkIfNotFound(midnightnation, michaelturner, writer); + createComicWorkIfNotFound(monsterwar, michaelturner, writer); + createComicWorkIfNotFound(monsterwar2005, michaelturner, writer); + createComicWorkIfNotFound(mystic, michaelturner, writer); + createComicWorkIfNotFound(holidayspecial2004, michaelturner, writer); + createComicWorkIfNotFound(hackslashgirlsgonedead, michaelturner, writer); + createComicWorkIfNotFound(uncannyxmen, brianbendis, writer); + createStoryArcIfNotFound("Higher Learning", emmafrost); + createStoryArcIfNotFound("Mind Games", emmafrost); + createStoryArcIfNotFound("Bloom", emmafrost); + createTradePaperbackIfNotFound("Vol. 1", midnightnation, 1, 12); + createTradePaperbackIfNotFound("From The Ashes", sojourn, 1, 6); + createTradePaperbackIfNotFound("The Dragons Tale", sojourn, 7, 12); + createTradePaperbackIfNotFound("The Warriors Tale", sojourn, 13, 18); + createTradePaperbackIfNotFound("The Thiefs Tale", sojourn, 19, 24); + createTradePaperbackIfNotFound("Vol. 1", runaways, 1, 18); + createTradePaperbackIfNotFound("Choices", gift, 1, 5); + createTradePaperbackIfNotFound("Atlantis Rising", crux, 1, 6); + createTradePaperbackIfNotFound("Test Of Time", crux, 7, 12); + createTradePaperbackIfNotFound("Strangers in Atlantis", crux, 13, 18); + createTradePaperbackIfNotFound("Flying Solo", meridian, 1, 7); + createTradePaperbackIfNotFound("Going To Ground", meridian, 8, 14); + createTradePaperbackIfNotFound("Taking The Skies", meridian, 15, 20); + createTradePaperbackIfNotFound("Coming Home", meridian, 21, 26); + createTradePaperbackIfNotFound("Rite of Passage", mystic, 1, 7); + createTradePaperbackIfNotFound("The Demon Queen", mystic, 8, 14); + createTradePaperbackIfNotFound("Siege of Scales", mystic, 15, 20); + createTradePaperbackIfNotFound("Out All Night", mystic, 21, 26); + createTradePaperbackIfNotFound("Single Green Female", shehulk, 1, 6); + createTradePaperbackIfNotFound("Superhuman Law", shehulk, 7, 12); + createTradePaperbackIfNotFound("Conflict of Conscience", scion, 1, 7); + createTradePaperbackIfNotFound("Blood For Blood", scion, 8, 14); + createTradePaperbackIfNotFound("Divided Loyalties", scion, 15, 21); + createTradePaperbackIfNotFound("Sanctuary", scion, 22, 27); + createTradePaperbackIfNotFound("The End Of History", uncannyxmen, 444, 449); + createTradePaperbackIfNotFound("Public Enemies", supermanbatman, 1, 6); + createTradePaperbackIfNotFound("Loyalty And Loss", crimson, 1, 6); + createTradePaperbackIfNotFound("Heaven & Earth", crimson, 7, 12); + createTradePaperbackIfNotFound("Earth Angel", crimson, 13, 18); + createTradePaperbackIfNotFound("Redemption", crimson, 19, 24); + createTradePaperbackIfNotFound("1602", comic1602, 1, 8); + createTradePaperbackIfNotFound("Coming Home", amazingspiderman, 30, 35); + createTradePaperbackIfNotFound("Revelations", amazingspiderman, 36, 39); + createTradePaperbackIfNotFound("Until the Stars Turn Cold", amazingspiderman, 40, 45); + createTradePaperbackIfNotFound("The Life & Death of Spiders", amazingspiderman, 46, 50); + createTradePaperbackIfNotFound("Unintended Consequences", amazingspiderman, 51, 56); + createTradePaperbackIfNotFound("Happy Birthday", amazingspiderman, 500, 502); + createTradePaperbackIfNotFound("Sonderband 1", dangergirl, 1, 2); + createTradePaperbackIfNotFound("Of Like Minds", birdsofprey, 56, 61); + createTradePaperbackIfNotFound("Sensei & Student", birdsofprey, 62, 68); + createIssueIfNotFound("1", phoenix, false, false); + createIssueIfNotFound("2", phoenix, false, false); + createIssueIfNotFound("3", phoenix, false, false); + createIssueIfNotFound("4", phoenix, false, false); + createIssueIfNotFound("5", phoenix, false, false); + createIssueIfNotFound("1", midnightnation, true, false); + createIssueIfNotFound("2", midnightnation, true, false); + createIssueIfNotFound("3", midnightnation, true, false); + createIssueIfNotFound("4", midnightnation, true, false); + createIssueIfNotFound("5", midnightnation, true, false); + createIssueIfNotFound("6", midnightnation, true, false); + createIssueIfNotFound("7", midnightnation, true, false); + createIssueIfNotFound("8", midnightnation, true, false); + createIssueIfNotFound("9", midnightnation, true, false); + createIssueIfNotFound("10", midnightnation, true, false); + createIssueIfNotFound("11", midnightnation, true, false); + createIssueIfNotFound("12", midnightnation, true, false); + createIssueIfNotFound("1", arana, false, false); + createIssueIfNotFound("2", arana, false, false); + createIssueIfNotFound("3", arana, false, false); + createIssueIfNotFound("4", arana, false, false); + createIssueIfNotFound("5", arana, false, false); + createIssueIfNotFound("6", arana, false, false); + createIssueIfNotFound("7", arana, false, false); + createIssueIfNotFound("8", arana, false, false); + createIssueIfNotFound("9", arana, false, false); + createIssueIfNotFound("10", arana, false, false); + createIssueIfNotFound("11", arana, false, false); + createIssueIfNotFound("1", futurama, false, false); + createIssueIfNotFound("2", futurama, false, false); + createIssueIfNotFound("3", futurama, false, false); + createIssueIfNotFound("4", futurama, false, false); + createIssueIfNotFound("5", futurama, false, false); + createIssueIfNotFound("6", futurama, false, false); + createIssueIfNotFound("7", futurama, false, false); + createIssueIfNotFound("8", futurama, false, false); + createIssueIfNotFound("9", futurama, false, false); + createIssueIfNotFound("10", futurama, false, false); + createIssueIfNotFound("11", futurama, false, false); + createIssueIfNotFound("12", futurama, false, false); + createIssueIfNotFound("13", futurama, false, false); + createIssueIfNotFound("14", futurama, false, false); + createIssueIfNotFound("15", futurama, false, false); + createIssueIfNotFound("16", futurama, false, false); + createIssueIfNotFound("17", futurama, false, false); + createIssueIfNotFound("18", futurama, false, false); + createIssueIfNotFound("19", futurama, false, false); + createIssueIfNotFound("20", futurama, false, false); + createIssueIfNotFound("21", futurama, false, false); + createIssueIfNotFound("22", futurama, false, false); + createIssueIfNotFound("1", futuramaco2, false, false); + createIssueIfNotFound("2", futuramaco2, false, false); + createIssueIfNotFound("1", battlepope, false, false); + createIssueIfNotFound("2", battlepope, false, false); + createIssueIfNotFound("3", battlepope, false, false); + createIssueIfNotFound("11", bartsimpsontree, false, false); + createIssueIfNotFound("20", bartsimpson, false, false); + createIssueIfNotFound("21", bartsimpson, false, false); + createIssueIfNotFound("22", bartsimpson, false, false); + createIssueIfNotFound("23", bartsimpson, false, false); + createIssueIfNotFound("24", bartsimpson, false, false); + createIssueIfNotFound("25", bartsimpson, false, false); + createIssueIfNotFound("1", athena, false, false); + createIssueIfNotFound("1", athenamanhunter, false, false); + createIssueIfNotFound("2", athenamanhunter, false, false); + createIssueIfNotFound("3", athenamanhunter, false, false); + createIssueIfNotFound("4", athenamanhunter, false, false); + createIssueIfNotFound("5", athenamanhunter, false, false); + createIssueIfNotFound("6", athenamanhunter, false, false); + createIssueIfNotFound("1", blackwidow, false, false); + createIssueIfNotFound("2", blackwidow, false, false); + createIssueIfNotFound("3", blackwidow, false, false); + createIssueIfNotFound("4", blackwidow, false, false); + createIssueIfNotFound("5", blackwidow, false, false); + createIssueIfNotFound("6", blackwidow, false, false); + createIssueIfNotFound("1", blackwidow2, false, false); + createIssueIfNotFound("2", blackwidow2, false, false); + createIssueIfNotFound("3", blackwidow2, false, false); + createIssueIfNotFound("4", blackwidow2, false, false); + createIssueIfNotFound("5", blackwidow2, false, false); + createIssueIfNotFound("1", ruse, false, false); + createIssueIfNotFound("2", ruse, false, false); + createIssueIfNotFound("3", ruse, false, false); + createIssueIfNotFound("4", ruse, false, false); + createIssueIfNotFound("5", ruse, false, false); + createIssueIfNotFound("6", ruse, false, false); + createIssueIfNotFound("7", ruse, false, false); + createIssueIfNotFound("8", ruse, false, false); + createIssueIfNotFound("9", ruse, false, false); + createIssueIfNotFound("10", ruse, false, false); + createIssueIfNotFound("11", ruse, false, false); + createIssueIfNotFound("12", ruse, false, false); + createIssueIfNotFound("13", ruse, false, false); + createIssueIfNotFound("14", ruse, false, false); + createIssueIfNotFound("15", ruse, false, false); + createIssueIfNotFound("16", ruse, false, false); + createIssueIfNotFound("17", ruse, false, false); + createIssueIfNotFound("18", ruse, false, false); + createIssueIfNotFound("19", ruse, false, false); + createIssueIfNotFound("20", ruse, false, false); + createIssueIfNotFound("21", ruse, false, false); + createIssueIfNotFound("22", ruse, false, false); + createIssueIfNotFound("23", ruse, false, false); + createIssueIfNotFound("24", ruse, false, false); + createIssueIfNotFound("25", ruse, false, false); + createIssueIfNotFound("26", ruse, false, false); + createIssueIfNotFound("1", samurai, false, false); + createIssueIfNotFound("2", samurai, false, false); + createIssueIfNotFound("3", samurai, false, false); + createIssueIfNotFound("4", samurai, false, false); + createIssueIfNotFound("1", amazingfantasy, false, false); + createIssueIfNotFound("2", amazingfantasy, false, false); + createIssueIfNotFound("3", amazingfantasy, false, false); + createIssueIfNotFound("4", amazingfantasy, false, false); + createIssueIfNotFound("5", amazingfantasy, false, false); + createIssueIfNotFound("6", amazingfantasy, false, false); + createIssueIfNotFound("7", amazingfantasy, false, false); + createIssueIfNotFound("8", amazingfantasy, false, false); + createIssueIfNotFound("9", amazingfantasy, false, false); + createIssueIfNotFound("10", amazingfantasy, false, false); + createIssueIfNotFound("11", amazingfantasy, false, false); + createIssueIfNotFound("12", amazingfantasy, false, false); + createIssueIfNotFound("13", amazingfantasy, false, false); + createIssueIfNotFound("14", amazingfantasy, false, false); + createIssueIfNotFound("15", amazingfantasy, false, false); + createIssueIfNotFound("1", excalibur, false, false); + createIssueIfNotFound("2", excalibur, false, false); + createIssueIfNotFound("3", excalibur, false, false); + createIssueIfNotFound("4", excalibur, false, false); + createIssueIfNotFound("5", excalibur, false, false); + createIssueIfNotFound("6", excalibur, false, false); + createIssueIfNotFound("7", excalibur, false, false); + createIssueIfNotFound("8", excalibur, false, false); + createIssueIfNotFound("9", excalibur, false, false); + createIssueIfNotFound("10", excalibur, false, false); + createIssueIfNotFound("11", excalibur, false, false); + createIssueIfNotFound("12", excalibur, false, false); + createIssueIfNotFound("1", emmafrost, false, false); + createIssueIfNotFound("2", emmafrost, false, false); + createIssueIfNotFound("3", emmafrost, false, false); + createIssueIfNotFound("4", emmafrost, false, false); + createIssueIfNotFound("5", emmafrost, false, false); + createIssueIfNotFound("6", emmafrost, false, false); + createIssueIfNotFound("7", emmafrost, false, false); + createIssueIfNotFound("8", emmafrost, false, false); + createIssueIfNotFound("9", emmafrost, false, false); + createIssueIfNotFound("10", emmafrost, false, false); + createIssueIfNotFound("11", emmafrost, false, false); + createIssueIfNotFound("12", emmafrost, false, false); + createIssueIfNotFound("13", emmafrost, false, false); + createIssueIfNotFound("14", emmafrost, false, false); + createIssueIfNotFound("15", emmafrost, false, false); + createIssueIfNotFound("16", emmafrost, false, false); + createIssueIfNotFound("17", emmafrost, false, false); + createIssueIfNotFound("18", emmafrost, false, false); + createIssueIfNotFound("1", catwomanrome, false, false); + createIssueIfNotFound("2", catwomanrome, false, false); + createIssueIfNotFound("3", catwomanrome, false, false); + createIssueIfNotFound("4", catwomanrome, false, false); + createIssueIfNotFound("5", catwomanrome, false, false); + createIssueIfNotFound("6", catwomanrome, false, false); + createIssueIfNotFound("1", districtx, false, false); + createIssueIfNotFound("2", districtx, false, false); + createIssueIfNotFound("3", districtx, false, false); + createIssueIfNotFound("4", districtx, false, false); + createIssueIfNotFound("5", districtx, false, false); + createIssueIfNotFound("6", districtx, false, false); + createIssueIfNotFound("7", districtx, false, false); + createIssueIfNotFound("8", districtx, false, false); + createIssueIfNotFound("9", districtx, false, false); + createIssueIfNotFound("10", districtx, false, false); + createIssueIfNotFound("11", districtx, false, false); + createIssueIfNotFound("12", districtx, false, false); + createIssueIfNotFound("13", districtx, false, false); + createIssueIfNotFound("14", districtx, false, false); + createIssueIfNotFound("1", elcazador, false, false); + createIssueIfNotFound("2", elcazador, false, false); + createIssueIfNotFound("3", elcazador, false, false); + createIssueIfNotFound("4", elcazador, false, false); + createIssueIfNotFound("5", elcazador, false, false); + createIssueIfNotFound("6", elcazador, false, false); + createIssueIfNotFound("1", elcazadortom, false, false); + createIssueIfNotFound("1", elsinore, false, false); + createIssueIfNotFound("1", dreampolice, false, false); + createIssueIfNotFound("1", dragonlance, false, false); + createIssueIfNotFound("2", dragonlance, false, false); + createIssueIfNotFound("1", ghostrider, false, false); + createIssueIfNotFound("2", ghostrider, false, false); + createIssueIfNotFound("3", ghostrider, false, false); + createIssueIfNotFound("4", ghostrider, false, false); + createIssueIfNotFound("5", ghostrider, false, false); + createIssueIfNotFound("6", ghostrider, false, false); + createIssueIfNotFound("1", fathomkillian, false, false); + createIssueIfNotFound("2", fathomkillian, false, false); + createIssueIfNotFound("3", fathomkillian, false, false); + createIssueIfNotFound("4", fathomkillian, false, false); + createIssueIfNotFound("1", maryjane, false, false); + createIssueIfNotFound("2", maryjane, false, false); + createIssueIfNotFound("3", maryjane, false, false); + createIssueIfNotFound("4", maryjane, false, false); + createIssueIfNotFound("1", maryjanehome, false, false); + createIssueIfNotFound("2", maryjanehome, false, false); + createIssueIfNotFound("3", maryjanehome, false, false); + createIssueIfNotFound("4", maryjanehome, false, false); + createIssueIfNotFound("1", marville, false, false); + createIssueIfNotFound("2", marville, false, false); + createIssueIfNotFound("3", marville, false, false); + createIssueIfNotFound("4", marville, false, false); + createIssueIfNotFound("5", marville, false, false); + createIssueIfNotFound("6", marville, false, false); + createIssueIfNotFound("7", marville, false, false); + createIssueIfNotFound("1", megacity, false, false); + createIssueIfNotFound("2", megacity, false, false); + createIssueIfNotFound("3", megacity, false, false); + createIssueIfNotFound("4", megacity, false, false); + createIssueIfNotFound("5", megacity, false, false); + createIssueIfNotFound("6", megacity, false, false); + createIssueIfNotFound("7", megacity, false, false); + createIssueIfNotFound("8", megacity, false, false); + createIssueIfNotFound("1", nightcrawler, false, false); + createIssueIfNotFound("2", nightcrawler, false, false); + createIssueIfNotFound("3", nightcrawler, false, false); + createIssueIfNotFound("4", nightcrawler, false, false); + createIssueIfNotFound("5", nightcrawler, false, false); + createIssueIfNotFound("6", nightcrawler, false, false); + createIssueIfNotFound("7", nightcrawler, false, false); + createIssueIfNotFound("8", nightcrawler, false, false); + createIssueIfNotFound("9", nightcrawler, false, false); + createIssueIfNotFound("10", nightcrawler, false, false); + createIssueIfNotFound("11", nightcrawler, false, false); + createIssueIfNotFound("12", nightcrawler, false, false); + createIssueIfNotFound("1", ororo, false, false); + createIssueIfNotFound("1", radix, false, false); + createIssueIfNotFound("2", radix, false, false); + createIssueIfNotFound("3", radix, false, false); + createIssueIfNotFound("1", rogue, false, false); + createIssueIfNotFound("2", rogue, false, false); + createIssueIfNotFound("3", rogue, false, false); + createIssueIfNotFound("4", rogue, false, false); + createIssueIfNotFound("5", rogue, false, false); + createIssueIfNotFound("6", rogue, false, false); + createIssueIfNotFound("7", rogue, false, false); + createIssueIfNotFound("8", rogue, false, false); + createIssueIfNotFound("9", rogue, false, false); + createIssueIfNotFound("10", rogue, false, false); + createIssueIfNotFound("11", rogue, false, false); + createIssueIfNotFound("12", rogue, false, false); + createIssueIfNotFound("1", shijunen, false, false); + createIssueIfNotFound("2", shijunen, false, false); + createIssueIfNotFound("3", shijunen, false, false); + createIssueIfNotFound("4", shijunen, false, false); + createIssueIfNotFound("1", solus, false, false); + createIssueIfNotFound("2", solus, false, false); + createIssueIfNotFound("3", solus, false, false); + createIssueIfNotFound("4", solus, false, false); + createIssueIfNotFound("5", solus, false, false); + createIssueIfNotFound("6", solus, false, false); + createIssueIfNotFound("7", solus, false, false); + createIssueIfNotFound("8", solus, false, false); + createIssueIfNotFound("1", toxin, false, false); + createIssueIfNotFound("2", toxin, false, false); + createIssueIfNotFound("3", toxin, false, false); + createIssueIfNotFound("4", toxin, false, false); + createIssueIfNotFound("5", toxin, false, false); + createIssueIfNotFound("6", toxin, false, false); + createIssueIfNotFound("1", wildgirl, false, false); + createIssueIfNotFound("2", wildgirl, false, false); + createIssueIfNotFound("3", wildgirl, false, false); + createIssueIfNotFound("4", wildgirl, false, false); + createIssueIfNotFound("5", wildgirl, false, false); + createIssueIfNotFound("6", wildgirl, false, false); + createIssueIfNotFound("1", wildcats, false, false); + createIssueIfNotFound("1", wildsiderz, false, false); + createIssueIfNotFound("19", vampirella, false, false); + createIssueIfNotFound("1", wolverine, false, false); + createIssueIfNotFound("2", wolverine, false, false); + createIssueIfNotFound("3", wolverine, false, false); + createIssueIfNotFound("4", wolverine, false, false); + createIssueIfNotFound("5", wolverine, false, false); + createIssueIfNotFound("6", wolverine, false, false); + createIssueIfNotFound("1", woodboy, false, false); + createIssueIfNotFound("1", wraithborn, false, false); + createIssueIfNotFound("2", wraithborn, false, false); + createIssueIfNotFound("3", wraithborn, false, false); + createIssueIfNotFound("1", x23, false, false); + createIssueIfNotFound("2", x23, false, false); + createIssueIfNotFound("3", x23, false, false); + createIssueIfNotFound("4", x23, false, false); + createIssueIfNotFound("5", x23, false, false); + createIssueIfNotFound("6", x23, false, false); + createIssueIfNotFound("46", xtremexmen, false, false); + createIssueIfNotFound("1", xmenkittypryde, false, false); + createIssueIfNotFound("2", xmenkittypryde, false, false); + createIssueIfNotFound("3", xmenkittypryde, false, false); + createIssueIfNotFound("4", xmenkittypryde, false, false); + createIssueIfNotFound("5", xmenkittypryde, false, false); + createIssueIfNotFound("1", witchbladetombraider, false, false); + createIssueIfNotFound("1", xmenageofapocalypseoneshot, false, false); + createIssueIfNotFound("1", xmenageofapocalypse, false, false); + createIssueIfNotFound("2", xmenageofapocalypse, false, false); + createIssueIfNotFound("3", xmenageofapocalypse, false, false); + createIssueIfNotFound("4", xmenageofapocalypse, false, false); + createIssueIfNotFound("5", xmenageofapocalypse, false, false); + createIssueIfNotFound("6", xmenageofapocalypse, false, false); + createIssueIfNotFound("1", thorsonasgard, false, false); + createIssueIfNotFound("2", thorsonasgard, false, false); + createIssueIfNotFound("3", thorsonasgard, false, false); + createIssueIfNotFound("4", thorsonasgard, false, false); + createIssueIfNotFound("5", thorsonasgard, false, false); + createIssueIfNotFound("6", thorsonasgard, false, false); + createIssueIfNotFound("7", thorsonasgard, false, false); + createIssueIfNotFound("8", thorsonasgard, false, false); + createIssueIfNotFound("9", thorsonasgard, false, false); + createIssueIfNotFound("10", thorsonasgard, false, false); + createIssueIfNotFound("11", thorsonasgard, false, false); + createIssueIfNotFound("12", thorsonasgard, false, false); + createIssueIfNotFound("1", strange, false, false); + createIssueIfNotFound("2", strange, false, false); + createIssueIfNotFound("3", strange, false, false); + createIssueIfNotFound("4", strange, false, false); + createIssueIfNotFound("5", strange, false, false); + createIssueIfNotFound("6", strange, false, false); + createIssueIfNotFound("0", supergirl, false, false); + createIssueIfNotFound("1", supergirl, false, false); + createIssueIfNotFound("2", supergirl, false, false); + createIssueIfNotFound("3", supergirl, false, false); + createIssueIfNotFound("4", supergirl, false, false); + createIssueIfNotFound("1", robertjordanwheeloftime, false, false); + createIssueIfNotFound("2", robertjordanwheeloftime, false, false); + createIssueIfNotFound("1", stardustkid, false, false); + createIssueIfNotFound("2", stardustkid, false, false); + createIssueIfNotFound("3", stardustkid, false, false); + createIssueIfNotFound("207", superman, false, false); + createIssueIfNotFound("208", superman, false, false); + createIssueIfNotFound("209", superman, false, false); + createIssueIfNotFound("210", superman, false, false); + createIssueIfNotFound("211", superman, false, false); + createIssueIfNotFound("212", superman, false, false); + createIssueIfNotFound("213", superman, false, false); + createIssueIfNotFound("214", superman, false, false); + createIssueIfNotFound("215", superman, false, false); + createIssueIfNotFound("17", supermanbatman, false, false); + createIssueIfNotFound("18", supermanbatman, false, false); + createIssueIfNotFound("19", supermanbatman, false, false); + createIssueIfNotFound("20", supermanbatman, false, false); + createIssueIfNotFound("21", supermanbatman, false, false); + createIssueIfNotFound("22", supermanbatman, false, false); + createIssueIfNotFound("1", tombraidergreatesttreasure, false, false); + createIssueIfNotFound("48", tombraider, false, false); + createIssueIfNotFound("49", tombraider, false, false); + createIssueIfNotFound("50", tombraider, false, false); + createIssueIfNotFound("1", tomstrong, false, false); + createIssueIfNotFound("1", tombdracula, false, false); + createIssueIfNotFound("1", thetenth, false, false); + createIssueIfNotFound("1", devilskeeper, false, false); + createIssueIfNotFound("1", artgreghorn, false, false); + createIssueIfNotFound("81", witchblade, false, false); + createIssueIfNotFound("82", witchblade, false, false); + createIssueIfNotFound("83", witchblade, false, false); + createIssueIfNotFound("84", witchblade, false, false); + createIssueIfNotFound("85", witchblade, false, false); + createIssueIfNotFound("86", witchblade, false, false); + createIssueIfNotFound("87", witchblade, false, false); + createIssueIfNotFound("88", witchblade, false, false); + createIssueIfNotFound("89", witchblade, false, false); + createIssueIfNotFound("90", witchblade, false, false); + createIssueIfNotFound("91", witchblade, false, false); + createIssueIfNotFound("92", witchblade, false, false); + createIssueIfNotFound("19", tarotblackrose, false, false); + createIssueIfNotFound("20", tarotblackrose, false, false); + createIssueIfNotFound("21", tarotblackrose, false, false); + createIssueIfNotFound("22", tarotblackrose, false, false); + createIssueIfNotFound("23", tarotblackrose, false, false); + createIssueIfNotFound("24", tarotblackrose, false, false); + createIssueIfNotFound("25", tarotblackrose, false, false); + createIssueIfNotFound("26", tarotblackrose, false, false); + createIssueIfNotFound("27", tarotblackrose, false, false); + createIssueIfNotFound("28", tarotblackrose, false, false); + createIssueIfNotFound("29", tarotblackrose, false, false); + createIssueIfNotFound("30", tarotblackrose, false, false); + createIssueIfNotFound("31", tarotblackrose, false, false); + createIssueIfNotFound("32", tarotblackrose, false, false); + createIssueIfNotFound("33", tarotblackrose, false, false); + createIssueIfNotFound("34", tarotblackrose, false, false); + createIssueIfNotFound("35", tarotblackrose, false, false); + createIssueIfNotFound("1", spectacularspiderman, false, false); + createIssueIfNotFound("2", spectacularspiderman, false, false); + createIssueIfNotFound("3", spectacularspiderman, false, false); + createIssueIfNotFound("4", spectacularspiderman, false, false); + createIssueIfNotFound("5", spectacularspiderman, false, false); + createIssueIfNotFound("6", spectacularspiderman, false, false); + createIssueIfNotFound("7", spectacularspiderman, false, false); + createIssueIfNotFound("8", spectacularspiderman, false, false); + createIssueIfNotFound("9", spectacularspiderman, false, false); + createIssueIfNotFound("10", spectacularspiderman, false, false); + createIssueIfNotFound("11", spectacularspiderman, false, false); + createIssueIfNotFound("12", spectacularspiderman, false, false); + createIssueIfNotFound("13", spectacularspiderman, false, false); + createIssueIfNotFound("14", spectacularspiderman, false, false); + createIssueIfNotFound("15", spectacularspiderman, false, false); + createIssueIfNotFound("16", spectacularspiderman, false, false); + createIssueIfNotFound("17", spectacularspiderman, false, false); + createIssueIfNotFound("18", spectacularspiderman, false, false); + createIssueIfNotFound("19", spectacularspiderman, false, false); + createIssueIfNotFound("20", spectacularspiderman, false, false); + createIssueIfNotFound("21", spectacularspiderman, false, false); + createIssueIfNotFound("22", spectacularspiderman, false, false); + createIssueIfNotFound("23", spectacularspiderman, false, false); + createIssueIfNotFound("24", spectacularspiderman, false, false); + createIssueIfNotFound("25", spectacularspiderman, false, false); + createIssueIfNotFound("26", spectacularspiderman, false, false); + createIssueIfNotFound("1", soulfire, false, false); + createIssueIfNotFound("2", soulfire, false, false); + createIssueIfNotFound("3", soulfire, false, false); + createIssueIfNotFound("4", soulfire, false, false); + createIssueIfNotFound("5", soulfire, false, false); + createIssueIfNotFound("1", soulfirelight, false, false); + createIssueIfNotFound("2", soulfirelight, false, false); + createIssueIfNotFound("3", soulfirelight, false, false); + createIssueIfNotFound("4", soulfirelight, false, false); + createIssueIfNotFound("1", spellbinders, false, false); + createIssueIfNotFound("2", spellbinders, false, false); + createIssueIfNotFound("3", spellbinders, false, false); + createIssueIfNotFound("4", spellbinders, false, false); + createIssueIfNotFound("5", spellbinders, false, false); + createIssueIfNotFound("6", spellbinders, false, false); + createIssueIfNotFound("1", spidermanlovesmary, false, false); + createIssueIfNotFound("1", spidermanindia, false, false); + createIssueIfNotFound("2", spidermanindia, false, false); + createIssueIfNotFound("3", spidermanindia, false, false); + createIssueIfNotFound("4", spidermanindia, false, false); + createIssueIfNotFound("1", spidermanteam, false, false); + createIssueIfNotFound("2", spidermanteam, false, false); + createIssueIfNotFound("3", spidermanteam, false, false); + createIssueIfNotFound("4", spidermanteam, false, false); + createIssueIfNotFound("5", spidermanteam, false, false); + createIssueIfNotFound("1", spidermanbreakout, false, false); + createIssueIfNotFound("2", spidermanbreakout, false, false); + createIssueIfNotFound("3", spidermanbreakout, false, false); + createIssueIfNotFound("4", spidermanbreakout, false, false); + createIssueIfNotFound("5", spidermanbreakout, false, false); + createIssueIfNotFound("1", spidermanhousem, false, false); + createIssueIfNotFound("2", spidermanhousem, false, false); + createIssueIfNotFound("3", spidermanhousem, false, false); + createIssueIfNotFound("4", spidermanhousem, false, false); + createIssueIfNotFound("13", sojourn, false, false); + createIssueIfNotFound("14", sojourn, false, false); + createIssueIfNotFound("15", sojourn, false, false); + createIssueIfNotFound("16", sojourn, false, false); + createIssueIfNotFound("17", sojourn, false, false); + createIssueIfNotFound("18", sojourn, false, false); + createIssueIfNotFound("19", sojourn, false, false); + createIssueIfNotFound("20", sojourn, false, false); + createIssueIfNotFound("21", sojourn, false, false); + createIssueIfNotFound("22", sojourn, false, false); + createIssueIfNotFound("23", sojourn, false, false); + createIssueIfNotFound("24", sojourn, false, false); + createIssueIfNotFound("25", sojourn, false, false); + createIssueIfNotFound("26", sojourn, false, false); + createIssueIfNotFound("27", sojourn, false, false); + createIssueIfNotFound("28", sojourn, false, false); + createIssueIfNotFound("29", sojourn, false, false); + createIssueIfNotFound("30", sojourn, false, false); + createIssueIfNotFound("31", sojourn, false, false); + createIssueIfNotFound("32", sojourn, false, false); + createIssueIfNotFound("33", sojourn, false, false); + createIssueIfNotFound("34", sojourn, false, false); + createIssueIfNotFound("1", shrek, false, false); + createIssueIfNotFound("2", shrek, false, false); + createIssueIfNotFound("1", shanna, false, false); + createIssueIfNotFound("2", shanna, false, false); + createIssueIfNotFound("3", shanna, false, false); + createIssueIfNotFound("4", shanna, false, false); + createIssueIfNotFound("5", shanna, false, false); + createIssueIfNotFound("6", shanna, false, false); + createIssueIfNotFound("7", shanna, false, false); + createIssueIfNotFound("100", simpsons, false, false); + createIssueIfNotFound("101", simpsons, false, false); + createIssueIfNotFound("102", simpsons, false, false); + createIssueIfNotFound("103", simpsons, false, false); + createIssueIfNotFound("104", simpsons, false, false); + createIssueIfNotFound("105", simpsons, false, false); + createIssueIfNotFound("106", simpsons, false, false); + createIssueIfNotFound("107", simpsons, false, false); + createIssueIfNotFound("108", simpsons, false, false); + createIssueIfNotFound("109", simpsons, false, false); + createIssueIfNotFound("110", simpsons, false, false); + createIssueIfNotFound("111", simpsons, false, false); + createIssueIfNotFound("112", simpsons, false, false); + createIssueIfNotFound("1", newxmenhellions, false, false); + createIssueIfNotFound("2", newxmenhellions, false, false); + createIssueIfNotFound("3", newxmenhellions, false, false); + createIssueIfNotFound("4", newxmenhellions, false, false); + createIssueIfNotFound("1", newxmenacademy, false, false); + createIssueIfNotFound("2", newxmenacademy, false, false); + createIssueIfNotFound("3", newxmenacademy, false, false); + createIssueIfNotFound("4", newxmenacademy, false, false); + createIssueIfNotFound("5", newxmenacademy, false, false); + createIssueIfNotFound("6", newxmenacademy, false, false); + createIssueIfNotFound("7", newxmenacademy, false, false); + createIssueIfNotFound("8", newxmenacademy, false, false); + createIssueIfNotFound("9", newxmenacademy, false, false); + createIssueIfNotFound("10", newxmenacademy, false, false); + createIssueIfNotFound("11", newxmenacademy, false, false); + createIssueIfNotFound("12", newxmenacademy, false, false); + createIssueIfNotFound("13", newxmenacademy, false, false); + createIssueIfNotFound("14", newxmenacademy, false, false); + createIssueIfNotFound("15", newxmenacademy, false, false); + createIssueIfNotFound("16", newxmenacademy, false, false); + createIssueIfNotFound("151", newxmen, false, false); + createIssueIfNotFound("152", newxmen, false, false); + createIssueIfNotFound("153", newxmen, false, false); + createIssueIfNotFound("154", newxmen, false, false); + createIssueIfNotFound("1", redsonja, false, false); + createIssueIfNotFound("2", redsonja, false, false); + createIssueIfNotFound("3", redsonja, false, false); + createIssueIfNotFound("1", revelations, false, false); + createIssueIfNotFound("2", revelations, false, false); + createIssueIfNotFound("3", revelations, false, false); + createIssueIfNotFound("4", revelations, false, false); + createIssueIfNotFound("1", kisskissbangbang, false, false); + createIssueIfNotFound("2", kisskissbangbang, false, false); + createIssueIfNotFound("3", kisskissbangbang, false, false); + createIssueIfNotFound("4", kisskissbangbang, false, false); + createIssueIfNotFound("5", kisskissbangbang, false, false); + createIssueIfNotFound("1", loki, false, false); + createIssueIfNotFound("2", loki, false, false); + createIssueIfNotFound("3", loki, false, false); + createIssueIfNotFound("4", loki, false, false); + createIssueIfNotFound("1", lullaby, false, false); + createIssueIfNotFound("2", lullaby, false, false); + createIssueIfNotFound("3", lullaby, false, false); + createIssueIfNotFound("1", legendofisis, false, false); + createIssueIfNotFound("2", legendofisis, false, false); + createIssueIfNotFound("1", judge, false, false); + createIssueIfNotFound("2", judge, false, false); + createIssueIfNotFound("3", judge, false, false); + createIssueIfNotFound("1", friendlyneighborspider, false, false); + createIssueIfNotFound("2", friendlyneighborspider, false, false); + createIssueIfNotFound("3", friendlyneighborspider, false, false); + createIssueIfNotFound("1", gift, false, false); + createIssueIfNotFound("2", gift, false, false); + createIssueIfNotFound("3", gift, false, false); + createIssueIfNotFound("4", gift, false, false); + createIssueIfNotFound("5", gift, false, false); + createIssueIfNotFound("6", gift, false, false); + createIssueIfNotFound("7", gift, false, false); + createIssueIfNotFound("8", gift, false, false); + createIssueIfNotFound("9", gift, false, false); + createIssueIfNotFound("10", gift, false, false); + createIssueIfNotFound("11", gift, false, false); + createIssueIfNotFound("12", gift, false, false); + createIssueIfNotFound("13", gift, false, false); + createIssueIfNotFound("1/2", fathom, false, false); + createIssueIfNotFound("1", fathom, false, false); + createIssueIfNotFound("2", fathom, false, false); + createIssueIfNotFound("3", fathom, false, false); + createIssueIfNotFound("4", fathom, false, false); + createIssueIfNotFound("5", fathom, false, false); + createIssueIfNotFound("6", fathom, false, false); + createIssueIfNotFound("7", fathom, false, false); + createIssueIfNotFound("8", fathom, false, false); + createIssueIfNotFound("9", fathom, false, false); + createIssueIfNotFound("10", fathom, false, false); + createIssueIfNotFound("11", fathom, false, false); + createIssueIfNotFound("12", fathom, false, false); + createIssueIfNotFound("13", fathom, false, false); + createIssueIfNotFound("14", fathom, false, false); + createIssueIfNotFound("1", fathombeginnings, false, false); + createIssueIfNotFound("1", fathomcannon, false, false); + createIssueIfNotFound("2", fathomcannon, false, false); + createIssueIfNotFound("3", fathomcannon, false, false); + createIssueIfNotFound("1", fathomcannonprelude, false, false); + createIssueIfNotFound("1", fathomdawnofwar, false, false); + createIssueIfNotFound("2", fathomdawnofwar, false, false); + createIssueIfNotFound("3", fathomdawnofwar, false, false); + createIssueIfNotFound("1", fathomprelude, false, false); + createIssueIfNotFound("1", fathomswimsuit, false, false); + createIssueIfNotFound("1", fathomswimsuit2000, false, false); + createIssueIfNotFound("0", fathomvol2, false, false); + createIssueIfNotFound("1", fathomvol2, false, false); + createIssueIfNotFound("2", fathomvol2, false, false); + createIssueIfNotFound("3", fathomvol2, false, false); + createIssueIfNotFound("4", fathomvol2, false, false); + createIssueIfNotFound("5", fathomvol2, false, false); + createIssueIfNotFound("1", flakriot, false, false); + createIssueIfNotFound("1", freshmen, false, false); + createIssueIfNotFound("1", daringescapes, false, false); + createIssueIfNotFound("2", daringescapes, false, false); + createIssueIfNotFound("3", daringescapes, false, false); + createIssueIfNotFound("4", daringescapes, false, false); + createIssueIfNotFound("1", brath, false, false); + createIssueIfNotFound("2", brath, false, false); + createIssueIfNotFound("3", brath, false, false); + createIssueIfNotFound("4", brath, false, false); + createIssueIfNotFound("5", brath, false, false); + createIssueIfNotFound("6", brath, false, false); + createIssueIfNotFound("7", brath, false, false); + createIssueIfNotFound("8", brath, false, false); + createIssueIfNotFound("9", brath, false, false); + createIssueIfNotFound("10", brath, false, false); + createIssueIfNotFound("11", brath, false, false); + createIssueIfNotFound("12", brath, false, false); + createIssueIfNotFound("13", brath, false, false); + createIssueIfNotFound("14", brath, false, false); + createIssueIfNotFound("1", astonishingxmen, false, false); + createIssueIfNotFound("2", astonishingxmen, false, false); + createIssueIfNotFound("3", astonishingxmen, false, false); + createIssueIfNotFound("4", astonishingxmen, false, false); + createIssueIfNotFound("5", astonishingxmen, false, false); + createIssueIfNotFound("6", astonishingxmen, false, false); + createIssueIfNotFound("7", astonishingxmen, false, false); + createIssueIfNotFound("8", astonishingxmen, false, false); + createIssueIfNotFound("9", astonishingxmen, false, false); + createIssueIfNotFound("10", astonishingxmen, false, false); + createIssueIfNotFound("11", astonishingxmen, false, false); + createIssueIfNotFound("12", astonishingxmen, false, false); + createIssueIfNotFound("1", barbarossa, false, false); + createIssueIfNotFound("1", aspenComic, false, false); + createIssueIfNotFound("2", aspenComic, false, false); + createIssueIfNotFound("3", aspenComic, false, false); + createIssueIfNotFound("1", armydarknessreanim, false, false); + createIssueIfNotFound("2", armydarknessreanim, false, false); + createIssueIfNotFound("3", armydarknessreanim, false, false); + createIssueIfNotFound("1", hellcop, false, false); + createIssueIfNotFound("2", hellcop, false, false); + createIssueIfNotFound("3", hellcop, false, false); + createIssueIfNotFound("4", hellcop, false, false); + createIssueIfNotFound("1", housem, false, false); + createIssueIfNotFound("2", housem, false, false); + createIssueIfNotFound("3", housem, false, false); + createIssueIfNotFound("0", hunterkiller, false, false); + createIssueIfNotFound("1", hunterkiller, false, false); + createIssueIfNotFound("2", hunterkiller, false, false); + createIssueIfNotFound("3", hunterkiller, false, false); + createIssueIfNotFound("4", hunterkiller, false, false); + createIssueIfNotFound("1", hunterkillerdossier, false, false); + createIssueIfNotFound("1", ironghost, false, false); + createIssueIfNotFound("1", magdalenavampirella2, false, false); + createIssueIfNotFound("20", marvelknightspider, false, false); + createIssueIfNotFound("39", meridian, false, false); + createIssueIfNotFound("40", meridian, false, false); + createIssueIfNotFound("41", meridian, false, false); + createIssueIfNotFound("42", meridian, false, false); + createIssueIfNotFound("43", meridian, false, false); + createIssueIfNotFound("44", meridian, false, false); + createIssueIfNotFound("1", necromancer, false, false); + createIssueIfNotFound("2", necromancer, false, false); + createIssueIfNotFound("3", necromancer, false, false); + createIssueIfNotFound("1", negationwar, false, false); + createIssueIfNotFound("2", negationwar, false, false); + createIssueIfNotFound("1", mystique, false, false); + createIssueIfNotFound("2", mystique, false, false); + createIssueIfNotFound("3", mystique, false, false); + createIssueIfNotFound("4", mystique, false, false); + createIssueIfNotFound("5", mystique, false, false); + createIssueIfNotFound("6", mystique, false, false); + createIssueIfNotFound("7", mystique, false, false); + createIssueIfNotFound("8", mystique, false, false); + createIssueIfNotFound("9", mystique, false, false); + createIssueIfNotFound("10", mystique, false, false); + createIssueIfNotFound("11", mystique, false, false); + createIssueIfNotFound("12", mystique, false, false); + createIssueIfNotFound("13", mystique, false, false); + createIssueIfNotFound("14", mystique, false, false); + createIssueIfNotFound("15", mystique, false, false); + createIssueIfNotFound("16", mystique, false, false); + createIssueIfNotFound("17", mystique, false, false); + createIssueIfNotFound("18", mystique, false, false); + createIssueIfNotFound("19", mystique, false, false); + createIssueIfNotFound("20", mystique, false, false); + createIssueIfNotFound("21", mystique, false, false); + createIssueIfNotFound("22", mystique, false, false); + createIssueIfNotFound("23", mystique, false, false); + createIssueIfNotFound("24", mystique, false, false); + createIssueIfNotFound("1", abadazad, false, false); + createIssueIfNotFound("2", abadazad, false, false); + createIssueIfNotFound("3", abadazad, false, false); + createIssueIfNotFound("503", amazingspiderman, false, false); + createIssueIfNotFound("504", amazingspiderman, false, false); + createIssueIfNotFound("505", amazingspiderman, false, false); + createIssueIfNotFound("506", amazingspiderman, false, false); + createIssueIfNotFound("507", amazingspiderman, false, false); + createIssueIfNotFound("508", amazingspiderman, false, false); + createIssueIfNotFound("509", amazingspiderman, false, false); + createIssueIfNotFound("510", amazingspiderman, false, false); + createIssueIfNotFound("511", amazingspiderman, false, false); + createIssueIfNotFound("512", amazingspiderman, false, false); + createIssueIfNotFound("513", amazingspiderman, false, false); + createIssueIfNotFound("514", amazingspiderman, false, false); + createIssueIfNotFound("515", amazingspiderman, false, false); + createIssueIfNotFound("516", amazingspiderman, false, false); + createIssueIfNotFound("517", amazingspiderman, false, false); + createIssueIfNotFound("518", amazingspiderman, false, false); + createIssueIfNotFound("519", amazingspiderman, false, false); + createIssueIfNotFound("520", amazingspiderman, false, false); + createIssueIfNotFound("521", amazingspiderman, false, false); + createIssueIfNotFound("522", amazingspiderman, false, false); + createIssueIfNotFound("523", amazingspiderman, false, false); + createIssueIfNotFound("524", amazingspiderman, false, false); + createIssueIfNotFound("525", amazingspiderman, false, false); + createIssueIfNotFound("526", amazingspiderman, false, false); + createIssueIfNotFound("1", dangergirlbackinblack, false, false); + createIssueIfNotFound("2", dangergirlbackinblack, false, false); + createIssueIfNotFound("1", darknesssuperman, false, false); + createIssueIfNotFound("2", darknesssuperman, false, false); + createIssueIfNotFound("1", darknesstombraider, false, false); + createIssueIfNotFound("1", darknessvampirella, false, false); + createIssueIfNotFound("1", darknessblacksails, false, false); + createIssueIfNotFound("17", darknessvol2, false, false); + createIssueIfNotFound("18", darknessvol2, false, false); + createIssueIfNotFound("19", darknessvol2, false, false); + createIssueIfNotFound("20", darknessvol2, false, false); + createIssueIfNotFound("21", darknessvol2, false, false); + createIssueIfNotFound("22", darknessvol2, false, false); + createIssueIfNotFound("23", darknessvol2, false, false); + createIssueIfNotFound("1", aria, false, false); + createIssueIfNotFound("2", aria, false, false); + createIssueIfNotFound("3", aria, false, false); + createIssueIfNotFound("4", aria, false, false); + createIssueIfNotFound("1", ariasoulmarket, false, false); + createIssueIfNotFound("2", ariasoulmarket, false, false); + createIssueIfNotFound("3", ariasoulmarket, false, false); + createIssueIfNotFound("4", ariasoulmarket, false, false); + createIssueIfNotFound("5", ariasoulmarket, false, false); + createIssueIfNotFound("6", ariasoulmarket, false, false); + createIssueIfNotFound("1", ariasummerspell, false, false); + createIssueIfNotFound("2", ariasummerspell, false, false); + createIssueIfNotFound("1", ariaenchantment, false, false); + createIssueIfNotFound("2", ariaenchantment, false, false); + createIssueIfNotFound("3", ariaenchantment, false, false); + createIssueIfNotFound("4", ariaenchantment, false, false); + createIssueIfNotFound("1", armydarknessashes, false, false); + createIssueIfNotFound("2", armydarknessashes, false, false); + createIssueIfNotFound("3", armydarknessashes, false, false); + createIssueIfNotFound("4", armydarknessashes, false, false); + createIssueIfNotFound("1", armydarknessshop, false, false); + createIssueIfNotFound("2", armydarknessshop, false, false); + createIssueIfNotFound("1", hackslashtoys, false, false); + createIssueIfNotFound("2", hackslashtoys, false, false); + createIssueIfNotFound("1", harryjohnson, false, false); + createIssueIfNotFound("4", battlepope, false, false); + createIssueIfNotFound("5", battlepope, false, false); + createIssueIfNotFound("6", battlepope, false, false); + createIssueIfNotFound("7", battlepope, false, false); + createIssueIfNotFound("8", battlepope, false, false); + createIssueIfNotFound("9", battlepope, false, false); + createIssueIfNotFound("10", battlepope, false, false); + createIssueIfNotFound("11", battlepope, false, false); + createIssueIfNotFound("12", battlepope, false, false); + List volumes = volumeRepository.findAll(); + volumes.forEach(volume -> volumeRepository.delete(volume)); + moduleService.setDataImported(ComicConstants.COMICS); + } + + private Artist createArtistIfNotFound(String artistName) { + log.info("createArtistIfNotFound {}", artistName); + Artist artist = artistRepository.findByName(artistName); + if (artist == null) { + log.info("Artist {} not found, will create it", artistName); + artist = new Artist(); + artist.setName(artistName); + artistRepository.save(artist); + } + return artist; + } + + private Publisher createPublisherIfNotFound(String publisherName) { + log.info("createPublisherIfNotFound {}", publisherName); + Publisher publisher = publisherRepository.findByName(publisherName); + if (publisher == null) { + log.info("Publisher {} not found, will create it", publisherName); + publisher = new Publisher(); + publisher.setName(publisherName); + publisherRepository.save(publisher); + } + return publisher; + } + + private Worktype createWorktypeIfNotFound(String workTypeName) { + log.info("createWorktypeIfNotFound {}", workTypeName); + Worktype worktype = worktypeRepository.findByName(workTypeName); + if (worktype == null) { + log.info("Worktype {} not found, will create it", workTypeName); + worktype = new Worktype(); + worktype.setName(workTypeName); + worktypeRepository.save(worktype); + } + return worktype; + } + + private Comic createComicIfNotFound(Publisher publisher, String title, boolean currentOrder, boolean completed) { + log.info("createComicIfNotFound {} {} {} {}", publisher, title, currentOrder, completed); + Comic comic = comicRepository.findByTitleAndPublisher(title, publisher); + if (comic == null) { + log.info("Comic {} from {} not found, will create it", title, publisher.getName()); + comic = new Comic(); + comic.setTitle(title); + comic.setPublisher(publisher); + comic.setCurrentOrder(currentOrder); + comic.setCompleted(completed); + comicRepository.save(comic); + } + return comic; + } + + private ComicWork createComicWorkIfNotFound(Comic comic, Artist artist, Worktype worktype) { + log.info("createComicWorkIfNotFound {} {} {}", comic, artist, worktype); + ComicWork comicWork = comicWorkRepository.findbyComicAndArtistAndWorktype(comic, artist, worktype); + if (comicWork == null) { + log.info("ComicWork {} from {} for {} not found, will create it", worktype, artist, comic); + comicWork = new ComicWork(); + comicWork.setComic(comic); + comicWork.setArtist(artist); + comicWork.setWorkType(worktype); + comicWorkRepository.save(comicWork); + } + return comicWork; + } + + private void createStoryArcIfNotFound(String name, Comic comic) { + log.info("createStoryArcIfNotFound {} {}", comic, name); + StoryArc storyArc = storyArcRepository.findByNameAndComic(name, comic); + if (storyArc == null) { + log.info("StoryArc {} for {} not found, will create it", name, comic); + storyArc = new StoryArc(); + storyArc.setName(name); + storyArc.setComic(comic); + storyArcRepository.save(storyArc); + } + } + + private void createTradePaperbackIfNotFound(String name, Comic comic, int start, int end) { + log.info("createTradePaperbackIfNotFound {} {} {}-{}", comic, name, start, end); + TradePaperback tradePaperback = tradePaperbackRepository.findByFields(name, comic, start, end); + if (tradePaperback == null) { + log.info("TradePaperback {} for {} with issues {}-{} not found, will create it", name, comic, start, end); + tradePaperback = new TradePaperback(); + tradePaperback.setName(name); + tradePaperback.setComic(comic); + tradePaperback.setIssueStart(start); + tradePaperback.setIssueEnd(end); + tradePaperbackRepository.save(tradePaperback); + } + } + + private void createIssueIfNotFound(String issueNumber, Comic comic, boolean isRead, boolean inStock) { + log.info("createIssueIfNotFound {} {} {} {}", comic, issueNumber, isRead, inStock); + Issue issue = issueRepository.findByComicAndIssueNumber(comic, issueNumber); + if (issue == null) { + log.info("Issue {} for {} not found, will create it", issueNumber, comic); + issue = new Issue(); + issue.setIssueNumber(issueNumber); + issue.setComic(comic); + issue.setIsRead(isRead); + issue.setInStock(inStock); + issueRepository.save(issue); + } + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/data/Artist.java b/springboot/src/main/java/de/thpeetz/kontor/comics/data/Artist.java new file mode 100644 index 0000000..6a3159d --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/data/Artist.java @@ -0,0 +1,44 @@ +package de.thpeetz.kontor.comics.data; + +import java.util.List; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import io.micrometer.common.lang.Nullable; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.OneToMany; +import jakarta.validation.constraints.NotEmpty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +/** + * Represents an artist in the system. + */ +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +@Slf4j +@Entity +public class Artist extends AbstractEntity { + + @NotEmpty + @Column(unique = true) + private String name; + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "artist", cascade = CascadeType.REFRESH, orphanRemoval = true) + @Nullable + List comicWorks; + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("Artist{"); + sb.append("name='").append(name).append('\''); + sb.append('}'); + return sb.toString(); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/data/ArtistRepository.java b/springboot/src/main/java/de/thpeetz/kontor/comics/data/ArtistRepository.java new file mode 100644 index 0000000..a5b94f2 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/data/ArtistRepository.java @@ -0,0 +1,17 @@ +package de.thpeetz.kontor.comics.data; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface ArtistRepository extends JpaRepository { + @Query("select a from Artist a " + + "where lower(a.name) like lower(concat('%', :searchTerm, '%')) ") + List search(@Param("searchTerm") String searchTerm); + + List findByNameIgnoreCase(String name); + + Artist findByName(String name); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/data/Comic.java b/springboot/src/main/java/de/thpeetz/kontor/comics/data/Comic.java new file mode 100644 index 0000000..bad2914 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/data/Comic.java @@ -0,0 +1,77 @@ +package de.thpeetz.kontor.comics.data; + +import java.util.LinkedList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import io.micrometer.common.lang.Nullable; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + * Represents a comic entity. + */ +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +@Entity +public class Comic extends AbstractEntity { + + @NotEmpty + @Column(unique = true) + private String title; + + @ManyToOne + @JoinColumn(name = "publisher_id") + @NotNull + @JsonIgnoreProperties({ "comics" }) + private Publisher publisher; + + private Boolean currentOrder = false; + + private Boolean completed = false; + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "comic", cascade = CascadeType.REFRESH, orphanRemoval = true) + @Nullable + List comicWorks; + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "comic", cascade = CascadeType.REFRESH, orphanRemoval = true) + @Nullable + private List issues = new LinkedList<>(); + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "comic", cascade = CascadeType.REFRESH, orphanRemoval = true) + @Nullable + private List storyArcs = new LinkedList<>(); + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "comic", cascade = CascadeType.REFRESH, orphanRemoval = true) + @Nullable + private List tradePaperbacks = new LinkedList<>(); + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "comic", cascade = CascadeType.REFRESH, orphanRemoval = true) + @Nullable + private List volumes = new LinkedList<>(); + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("Comic{"); + sb.append("title='").append(title).append('\''); + sb.append(", publisher=").append(publisher); + sb.append(", currentOrder=").append(currentOrder); + sb.append(", completed=").append(completed); + sb.append('}'); + return sb.toString(); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/data/ComicRepository.java b/springboot/src/main/java/de/thpeetz/kontor/comics/data/ComicRepository.java new file mode 100644 index 0000000..3a3e61f --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/data/ComicRepository.java @@ -0,0 +1,19 @@ +package de.thpeetz.kontor.comics.data; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface ComicRepository extends JpaRepository { + @Query("select c from Comic c " + + "where lower(c.title) like lower(concat('%', :searchTerm, '%')) ") + List search(@Param("searchTerm") String searchTerm); + + Comic findByTitleAndPublisher(String title, Publisher publisher); + + List findByTitle(String title); + + List findByTitleIgnoreCase(String title); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/data/ComicWork.java b/springboot/src/main/java/de/thpeetz/kontor/comics/data/ComicWork.java new file mode 100644 index 0000000..840f93c --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/data/ComicWork.java @@ -0,0 +1,48 @@ +package de.thpeetz.kontor.comics.data; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.persistence.Entity; +import jakarta.persistence.Index; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import jakarta.validation.constraints.NotNull; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +@Entity +@Table(indexes = {@Index(columnList = "comic_id, artist_id, workType_id") }, + uniqueConstraints = @UniqueConstraint(columnNames = {"comic_id", "artist_id", "workType_id" }) +) +public class ComicWork extends AbstractEntity { + + @ManyToOne + @JoinColumn(name = "comic_id") + @NotNull + private Comic comic; + + @ManyToOne + @JoinColumn(name = "artist_id") + @NotNull + private Artist artist; + + @ManyToOne + @JoinColumn(name = "workType_id") + @NotNull + private Worktype workType; + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("ComicWork{"); + sb.append("comic=").append(comic); + sb.append(", artist=").append(artist); + sb.append(", workType=").append(workType); + sb.append('}'); + return sb.toString(); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/data/ComicWorkRepository.java b/springboot/src/main/java/de/thpeetz/kontor/comics/data/ComicWorkRepository.java new file mode 100644 index 0000000..6b3540a --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/data/ComicWorkRepository.java @@ -0,0 +1,15 @@ +package de.thpeetz.kontor.comics.data; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface ComicWorkRepository extends JpaRepository { + + @Query("SELECT c from ComicWork c where c.comic = ?1 and c.artist = ?2 and c.workType = ?3") + ComicWork findbyComicAndArtistAndWorktype(Comic comic, Artist artist, Worktype worktype); + + @Query("select c from ComicWork c where c.comic = ?1") + List findByComic(Comic comic); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/data/Issue.java b/springboot/src/main/java/de/thpeetz/kontor/comics/data/Issue.java new file mode 100644 index 0000000..5a36f7d --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/data/Issue.java @@ -0,0 +1,42 @@ +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 jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@EqualsAndHashCode(callSuper = false) +@Entity +public class Issue extends AbstractEntity { + + @ManyToOne + @JoinColumn(name = "comic_id") + @NotNull + @JsonIgnoreProperties({ "issues" }) + private Comic comic; + + @ManyToOne + @JoinColumn(name = "volume_id") + @JsonIgnoreProperties({ "issues" }) + @Nullable + private Volume volume; + + @NotEmpty + private String issueNumber; + + private Boolean isRead; + + private Boolean inStock; +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/data/IssueRepository.java b/springboot/src/main/java/de/thpeetz/kontor/comics/data/IssueRepository.java new file mode 100644 index 0000000..af10705 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/data/IssueRepository.java @@ -0,0 +1,17 @@ +package de.thpeetz.kontor.comics.data; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface IssueRepository extends JpaRepository { + @Query("select i from Issue i " + + "where lower(i.issueNumber) like lower(concat('%', :searchTerm, '%')) ") + List search(@Param("searchTerm") String searchTerm); + + List findByComic(Comic comic); + + Issue findByComicAndIssueNumber(Comic comic, String issueNumber); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/data/Publisher.java b/springboot/src/main/java/de/thpeetz/kontor/comics/data/Publisher.java new file mode 100644 index 0000000..233d405 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/data/Publisher.java @@ -0,0 +1,40 @@ +package de.thpeetz.kontor.comics.data; + +import java.util.LinkedList; +import java.util.List; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.annotation.Nullable; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.OneToMany; +import jakarta.validation.constraints.NotEmpty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +@Entity +public class Publisher extends AbstractEntity { + + @NotEmpty + @Column(unique = true) + private String name; + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "publisher", cascade = CascadeType.ALL, orphanRemoval = true) + @Nullable + private List comics = new LinkedList<>(); + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("Publisher{"); + sb.append("name='").append(name).append('\''); + sb.append('}'); + return sb.toString(); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/data/PublisherRepository.java b/springboot/src/main/java/de/thpeetz/kontor/comics/data/PublisherRepository.java new file mode 100644 index 0000000..5b5587d --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/data/PublisherRepository.java @@ -0,0 +1,17 @@ +package de.thpeetz.kontor.comics.data; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface PublisherRepository extends JpaRepository { + @Query("select p from Publisher p " + + "where lower(p.name) like lower(concat('%', :searchTerm, '%')) ") + List search(@Param("searchTerm") String searchTerm); + + Publisher findByName(String name); + + List findByNameIgnoreCase(String name); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/data/StoryArc.java b/springboot/src/main/java/de/thpeetz/kontor/comics/data/StoryArc.java new file mode 100644 index 0000000..34f2188 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/data/StoryArc.java @@ -0,0 +1,31 @@ +package de.thpeetz.kontor.comics.data; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@EqualsAndHashCode(callSuper = false) +@Entity +public class StoryArc extends AbstractEntity { + + @NotEmpty + private String name; + + @ManyToOne + @JoinColumn(name = "comic_id") + @NotNull + @JsonIgnoreProperties({ "storyArcs" }) + private Comic comic; +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/data/StoryArcRepository.java b/springboot/src/main/java/de/thpeetz/kontor/comics/data/StoryArcRepository.java new file mode 100644 index 0000000..a74619f --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/data/StoryArcRepository.java @@ -0,0 +1,17 @@ +package de.thpeetz.kontor.comics.data; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface StoryArcRepository extends JpaRepository { + @Query("select s from StoryArc s " + + "where lower(s.name) like lower(concat('%', :searchTerm, '%')) ") + List search(@Param("searchTerm") String searchTerm); + + StoryArc findByNameAndComic(String name, Comic comic); + + List findByComic(Comic comic); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/data/TradePaperback.java b/springboot/src/main/java/de/thpeetz/kontor/comics/data/TradePaperback.java new file mode 100644 index 0000000..07ee8d3 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/data/TradePaperback.java @@ -0,0 +1,32 @@ +package de.thpeetz.kontor.comics.data; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@EqualsAndHashCode(callSuper = false) +@Entity +public class TradePaperback extends AbstractEntity { + + @NotEmpty + private String name; + + @ManyToOne + @JoinColumn(name = "comic_id") + @NotNull + private Comic comic; + + private Integer issueStart; + + private Integer issueEnd; +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/data/TradePaperbackRepository.java b/springboot/src/main/java/de/thpeetz/kontor/comics/data/TradePaperbackRepository.java new file mode 100644 index 0000000..fba26ad --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/data/TradePaperbackRepository.java @@ -0,0 +1,20 @@ +package de.thpeetz.kontor.comics.data; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface TradePaperbackRepository extends JpaRepository { + @Query("select t from TradePaperback t " + + "where lower(t.name) like lower(concat('%', :searchTerm, '%')) ") + List search(@Param("searchTerm") String searchTerm); + + List findByComic(Comic comic); + + List findByNameAndComic(String name, Comic comic); + + @Query("select t from TradePaperback t where t.name = ?1 and t.comic = ?2 and t.issueStart = ?3 and t.issueEnd = ?4") + TradePaperback findByFields(String name, Comic comic, int start, int end); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/data/Volume.java b/springboot/src/main/java/de/thpeetz/kontor/comics/data/Volume.java new file mode 100644 index 0000000..c8e1eb0 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/data/Volume.java @@ -0,0 +1,42 @@ +package de.thpeetz.kontor.comics.data; + +import java.util.LinkedList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import io.micrometer.common.lang.Nullable; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@EqualsAndHashCode(callSuper = false) +@Entity +public class Volume extends AbstractEntity { + + @NotEmpty + private String name; + + @ManyToOne + @JoinColumn(name = "comic_id") + @NotNull + @JsonIgnoreProperties({ "volumes" }) + private Comic comic; + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "volume", cascade = CascadeType.REMOVE, orphanRemoval = true) + @Nullable + private List issues = new LinkedList<>(); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/data/VolumeRepository.java b/springboot/src/main/java/de/thpeetz/kontor/comics/data/VolumeRepository.java new file mode 100644 index 0000000..3b70670 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/data/VolumeRepository.java @@ -0,0 +1,13 @@ +package de.thpeetz.kontor.comics.data; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface VolumeRepository extends JpaRepository { + + List findByName(String name); + + List findByComic(Comic comic); + +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/data/Worktype.java b/springboot/src/main/java/de/thpeetz/kontor/comics/data/Worktype.java new file mode 100644 index 0000000..9c83133 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/data/Worktype.java @@ -0,0 +1,44 @@ +package de.thpeetz.kontor.comics.data; + +import java.util.LinkedList; +import java.util.List; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import io.micrometer.common.lang.Nullable; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.OneToMany; +import jakarta.validation.constraints.NotEmpty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + * Represents a work type in the application. + * This class extends the AbstractEntity class. + */ +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +@Entity +public class Worktype extends AbstractEntity { + + @NotEmpty + @Column(unique = true) + private String name; + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "workType", cascade = CascadeType.REFRESH, orphanRemoval = true) + @Nullable + List comicWorks = new LinkedList<>(); + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("Worktype{"); + sb.append("name='").append(name).append('\''); + sb.append('}'); + return sb.toString(); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/data/WorktypeRepository.java b/springboot/src/main/java/de/thpeetz/kontor/comics/data/WorktypeRepository.java new file mode 100644 index 0000000..2afebaf --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/data/WorktypeRepository.java @@ -0,0 +1,18 @@ +package de.thpeetz.kontor.comics.data; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface WorktypeRepository extends JpaRepository { + @Query("select w from Worktype w " + + "where lower(w.name) like lower(concat('%', :searchTerm, '%')) ") + List search(@Param("searchTerm") String searchTerm); + + + Worktype findByName(String name); + + List findByNameIgnoreCase(String name); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/services/ComicService.java b/springboot/src/main/java/de/thpeetz/kontor/comics/services/ComicService.java new file mode 100644 index 0000000..ae4637f --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/services/ComicService.java @@ -0,0 +1,299 @@ +package de.thpeetz.kontor.comics.services; + +import java.util.List; + +import org.springframework.stereotype.Service; + +import de.thpeetz.kontor.comics.data.Artist; +import de.thpeetz.kontor.comics.data.ArtistRepository; +import de.thpeetz.kontor.comics.data.Comic; +import de.thpeetz.kontor.comics.data.ComicRepository; +import de.thpeetz.kontor.comics.data.ComicWork; +import de.thpeetz.kontor.comics.data.ComicWorkRepository; +import de.thpeetz.kontor.comics.data.Issue; +import de.thpeetz.kontor.comics.data.IssueRepository; +import de.thpeetz.kontor.comics.data.Publisher; +import de.thpeetz.kontor.comics.data.PublisherRepository; +import de.thpeetz.kontor.comics.data.StoryArc; +import de.thpeetz.kontor.comics.data.StoryArcRepository; +import de.thpeetz.kontor.comics.data.TradePaperback; +import de.thpeetz.kontor.comics.data.TradePaperbackRepository; +import de.thpeetz.kontor.comics.data.Volume; +import de.thpeetz.kontor.comics.data.VolumeRepository; +import de.thpeetz.kontor.comics.data.Worktype; +import de.thpeetz.kontor.comics.data.WorktypeRepository; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +public class ComicService { + + private final PublisherRepository publisherRepository; + private final ComicRepository comicRepository; + private final ArtistRepository artistRepository; + private final IssueRepository issueRepository; + private final StoryArcRepository storyArcRepository; + private final TradePaperbackRepository tradePaperbackRepository; + private final ComicWorkRepository comicWorkRepository; + private final VolumeRepository volumeRepository; + private final WorktypeRepository worktypeRepository; + + public ComicService(PublisherRepository publisherRepository, ComicRepository comicRepository, + ArtistRepository artistRepository, IssueRepository issueRepository, StoryArcRepository storyArcRepository, + TradePaperbackRepository tradePaperbackRepository, ComicWorkRepository comicWorkRepository, + VolumeRepository volumeRepository, WorktypeRepository worktypeRepository) { + + this.publisherRepository = publisherRepository; + this.comicRepository = comicRepository; + this.artistRepository = artistRepository; + this.issueRepository = issueRepository; + this.storyArcRepository = storyArcRepository; + this.tradePaperbackRepository = tradePaperbackRepository; + this.comicWorkRepository = comicWorkRepository; + this.volumeRepository = volumeRepository; + this.worktypeRepository = worktypeRepository; + } + + public List findAllPublishers(String stringFilter) { + if (stringFilter == null || stringFilter.isEmpty()) { + return publisherRepository.findAll(); + } else { + return publisherRepository.search(stringFilter); + } + } + + public Publisher findPublisherByName(String publisherName) { + return publisherRepository.findByName(publisherName); + } + + public void deletePublisher(Publisher publisher) { + publisherRepository.delete(publisher); + } + + public void savePublisher(Publisher publisher) { + if (publisher == null) { + log.warn("Publisher is null. Are you sure you have connected your form to the application?"); + return; + } + publisherRepository.save(publisher); + } + + public List findAllComics(String stringFilter) { + if (stringFilter == null || stringFilter.isEmpty()) { + return comicRepository.findAll(); + } else { + return comicRepository.search(stringFilter); + } + } + + public Comic findComicByTitle(String title) { + List comics = comicRepository.findByTitle(title); + if (comics.size() == 1) { + return comics.get(0); + } + return null; + } + + public void deleteComic(Comic comic) { + Publisher publisher = comic.getPublisher(); + publisher.getComics().remove(comic); + publisherRepository.save(publisher); + comicRepository.delete(comic); + } + + public void saveComic(Comic comic) { + if (comic == null) { + log.warn("Comic is null. Are you sure you have connected your form to the application?"); + return; + } + comicRepository.save(comic); + } + + public List findAllArtists(String stringFilter) { + if (stringFilter == null || stringFilter.isEmpty()) { + return artistRepository.findAll(); + } else { + return artistRepository.search(stringFilter); + } + } + + public Artist findArtistByName(String artistName) { + if (artistName == null || artistName.isEmpty()) { + return null; + } else { + return artistRepository.findByName(artistName); + } + } + + public void deleteArtist(Artist artist) { + artistRepository.delete(artist); + } + + public void saveArtist(Artist artist) { + if (artist == null) { + log.warn("Artist is null. Are you sure you have connected your form to the application?"); + return; + } + artistRepository.save(artist); + } + + public List findAllIssues() { + return issueRepository.findAll(); + } + + public List findAllIssuesForComic(Comic comic) { + if (comic == null) { + return issueRepository.findAll(); + } else { + log.info("Find issues for Comic: {}", comic); + return issueRepository.findByComic(comic); + } + } + + public void saveIssue(Issue issue) { + if (issue == null) { + log.warn("Issue is null. Are you sure you have connected your form to the application?"); + return; + } + issueRepository.save(issue); + } + + public void deleteIssue(Issue issue) { + issueRepository.delete(issue); + } + + public List findAllStoryArcs() { + return storyArcRepository.findAll(); + } + + public List findAllStoryArcsForComic(Comic comic) { + if (comic == null) { + return storyArcRepository.findAll(); + } else { + log.info("Find Story Arc for Comic: {}", comic); + return storyArcRepository.findByComic(comic); + } + } + + public void saveStoryArc(StoryArc storyArc) { + storyArcRepository.save(storyArc); + } + + public void deleteStoryArc(StoryArc storyArc) { + Comic comic = storyArc.getComic(); + comic.getStoryArcs().remove(storyArc); + comicRepository.save(comic); + storyArcRepository.delete(storyArc); + } + + public List findAllTradePaperbacks(String stringFilter) { + if (stringFilter == null || stringFilter.isEmpty()) { + return tradePaperbackRepository.findAll(); + } else { + return tradePaperbackRepository.search(stringFilter); + } + } + + public void deleteTradePaperBack(TradePaperback tradepaperback) { + tradePaperbackRepository.delete(tradepaperback); + } + + public void saveTradePaperBack(TradePaperback tradepaperback) { + if (tradepaperback == null) { + log.warn("TradePaperBack is null. Are you sure you have connected your form to the application?"); + return; + } + tradePaperbackRepository.save(tradepaperback); + } + + public List findAllComicWorks() { + return comicWorkRepository.findAll(); + } + + public void saveComicWork(ComicWork comicWork) { + if (comicWork == null) { + log.warn("ComicWork is null. Are you sure you have connected your form to the application?"); + return; + } + comicWorkRepository.save(comicWork); + } + + public void deleteComicWork(ComicWork comicWork) { + Comic comic = comicWork.getComic(); + comic.getComicWorks().remove(comicWork); + comicRepository.save(comic); + Artist artist = comicWork.getArtist(); + artist.getComicWorks().remove(comicWork); + artistRepository.save(artist); + Worktype worktype = comicWork.getWorkType(); + worktype.getComicWorks().remove(comicWork); + worktypeRepository.save(worktype); + comicWorkRepository.delete(comicWork); + } + + public List findAllVolumes() { + return volumeRepository.findAll(); + } + + public List findAllVolumesForComic(Comic comic) { + if (comic == null) { + return volumeRepository.findAll(); + } else { + log.info("Find Volume for Comic: {}", comic); + return volumeRepository.findByComic(comic); + } + } + + public void saveVolume(Volume volume) { + if (volume == null) { + log.warn("Volume is null. Are you sure you have connected your form to the application?"); + return; + } + volumeRepository.save(volume); + } + + public void deleteVolume(Volume volume) { + Comic comic = volume.getComic(); + comic.getVolumes().remove(volume); + comicRepository.save(comic); + volumeRepository.delete(volume); + } + + public List findAllWorktypes(String stringFilter) { + if (stringFilter == null || stringFilter.isEmpty()) { + return worktypeRepository.findAll(); + } else { + return worktypeRepository.search(stringFilter); + } + } + + public Worktype findWorktypeByName(String worktypeName) { + if (worktypeName == null || worktypeName.isEmpty()) { + return null; + } else { + return worktypeRepository.findByName(worktypeName); + } + } + + public void saveWorktype(Worktype worktype) { + if (worktype == null) { + log.warn("Worktype is null. Are you sure you have connected your form to the application?"); + return; + } + worktypeRepository.save(worktype); + } + + public void deleteWorktype(Worktype worktype) { + List comicWorks = worktype.getComicWorks(); + if (comicWorks == null) { + log.warn("reference to ComicWork is null"); + return; + } + log.info("found {} references to ComicWork", comicWorks.size()); + comicWorks.forEach(comicWork -> { + comicWork.setWorkType(null); + }); + log.info("delete Worktype: {}", worktype); + worktypeRepository.delete(worktype); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/views/ArtistForm.java b/springboot/src/main/java/de/thpeetz/kontor/comics/views/ArtistForm.java new file mode 100644 index 0000000..43a93d4 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/views/ArtistForm.java @@ -0,0 +1,117 @@ +package de.thpeetz.kontor.comics.views; + +import java.util.List; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.grid.Grid; +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 de.thpeetz.kontor.comics.data.Artist; +import de.thpeetz.kontor.comics.data.ComicWork; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ArtistForm extends FormLayout { + + TextField name = new TextField("Name"); + Grid comicWorks = new Grid<>(ComicWork.class); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(Artist.class); + + public ArtistForm() { + addClassName("artist-form"); + binder.bindInstanceFields(this); + + comicWorks.setColumns("workType.name", "comic.title"); + comicWorks.getColumnByKey("workType.name").setHeader("Work type"); + comicWorks.getColumnByKey("comic.title").setHeader("Comic"); + comicWorks.getColumns().forEach(col -> col.setAutoWidth(true)); + add(name, comicWorks, createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new SaveEvent(this, binder.getBean())); + } + } + + public void setArtist(Artist artist) { + binder.setBean(artist); + } + + public void setComicWorks(List works) { + log.info("Setting comic works: {}", works); + this.comicWorks.setItems(works); + } + + public abstract static class ArtistFormEvent extends ComponentEvent { + private Artist artist; + + protected ArtistFormEvent(ArtistForm source, Artist artist) { + super(source, false); + this.artist = artist; + } + + public Artist getArtist() { + return artist; + } + } + + public static class SaveEvent extends ArtistFormEvent { + SaveEvent(ArtistForm source, Artist artist) { + super(source, artist); + } + } + + public static class DeleteEvent extends ArtistFormEvent { + DeleteEvent(ArtistForm source, Artist artist) { + super(source, artist); + } + } + + public static class CloseEvent extends ArtistFormEvent { + CloseEvent(ArtistForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/views/ArtistView.java b/springboot/src/main/java/de/thpeetz/kontor/comics/views/ArtistView.java new file mode 100644 index 0000000..f0d294d --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/views/ArtistView.java @@ -0,0 +1,129 @@ +package de.thpeetz.kontor.comics.views; + +import org.springframework.context.annotation.Scope; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; + +import de.thpeetz.kontor.comics.data.Artist; +import de.thpeetz.kontor.comics.services.ComicService; +import de.thpeetz.kontor.common.views.MainLayout; +import jakarta.annotation.security.PermitAll; + +@SpringComponent +@Scope("prototype") +@PermitAll +@Route(value = "artist", layout = MainLayout.class) +@PageTitle("Artist | Comics | Kontor") +public class ArtistView extends VerticalLayout { + + Grid grid = new Grid<>(Artist.class); + TextField filterText = new TextField(); + ArtistForm form; + ComicService service; + + public ArtistView(ComicService service) { + this.service = service; + addClassName("artist-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + public Grid getGrid() { + return grid; + } + + public ArtistForm getForm() { + return form; + } + + private void configureGrid() { + grid.addClassName("artist-grid"); + grid.setSizeFull(); + grid.setColumns("name"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editArtist(event.getValue())); + } + + private void configureForm() { + form = new ArtistForm(); + form.setWidth("25em"); + form.setVisible(false); + form.addSaveListener(this::saveArtist); + form.addDeleteListener(this::deleteArtist); + form.addCloseListener(e -> closeEditor()); + } + + private void saveArtist(ArtistForm.SaveEvent event) { + service.saveArtist(event.getArtist()); + updateList(); + closeEditor(); + } + + private void deleteArtist(ArtistForm.DeleteEvent event) { + service.deleteArtist(event.getArtist()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + filterText.setPlaceholder("Filter by name..."); + filterText.setClearButtonVisible(true); + filterText.setValueChangeMode(ValueChangeMode.LAZY); + filterText.addValueChangeListener(e -> updateList()); + + Button addArtistButton = new Button("Add artist"); + addArtistButton.addClickListener(click -> addArtist()); + + HorizontalLayout toolbar = new HorizontalLayout(filterText, addArtistButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editArtist(Artist artist) { + if (artist == null) { + closeEditor(); + } else { + form.setArtist(artist); + form.setComicWorks(artist.getComicWorks()); + form.setVisible(true); + addClassName("editing"); + } + } + + private void closeEditor() { + form.setArtist(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addArtist() { + grid.asSingleSelect().clear(); + editArtist(new Artist()); + } + + public void updateList() { + grid.setItems(service.findAllArtists(filterText.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/views/ComicForm.java b/springboot/src/main/java/de/thpeetz/kontor/comics/views/ComicForm.java new file mode 100644 index 0000000..55fc557 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/views/ComicForm.java @@ -0,0 +1,130 @@ +package de.thpeetz.kontor.comics.views; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +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.formlayout.FormLayout; +import com.vaadin.flow.component.grid.Grid; +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 de.thpeetz.kontor.comics.data.Comic; +import de.thpeetz.kontor.comics.data.ComicWork; +import de.thpeetz.kontor.comics.data.Publisher; + +public class ComicForm extends FormLayout { + + private static final Logger log = LoggerFactory.getLogger(ComicForm.class); + + TextField title = new TextField("Title"); + ComboBox publisher = new ComboBox<>("Publisher"); + Checkbox currentOrder = new Checkbox("Current order"); + Checkbox completed = new Checkbox("Completed"); + Grid comicWorks = new Grid<>(ComicWork.class); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(Comic.class); + + public ComicForm(List publishers) { + addClassName("comic-form"); + binder.bindInstanceFields(this); + + publisher.setItems(publishers); + publisher.setItemLabelGenerator(Publisher::getName); + // comicWorks.addClassName("comic-works-grid"); + // comicWorks.setSizeFull(); + comicWorks.setColumns("workType.name", "artist.name"); + comicWorks.getColumnByKey("workType.name").setHeader("Work type"); + comicWorks.getColumnByKey("artist.name").setHeader("Artist"); + comicWorks.getColumns().forEach(col -> col.setAutoWidth(true)); + add(title, publisher, currentOrder, completed, comicWorks, createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new SaveEvent(this, binder.getBean())); + } + } + + public void setComic(Comic comic) { + binder.setBean(comic); + } + + public void setComicWorks(List works) { + log.info("Setting comic works: {}", works); + comicWorks.setItems(works); + } + + public abstract static class ComicFormEvent extends ComponentEvent { + private Comic comic; + + protected ComicFormEvent(ComicForm source, Comic comic) { + super(source, false); + this.comic = comic; + } + + public Comic getComic() { + return comic; + } + } + + public static class SaveEvent extends ComicFormEvent { + SaveEvent(ComicForm source, Comic comic) { + super(source, comic); + } + } + + public static class DeleteEvent extends ComicFormEvent { + DeleteEvent(ComicForm source, Comic comic) { + super(source, comic); + } + } + + public static class CloseEvent extends ComicFormEvent { + CloseEvent(ComicForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/views/ComicLayout.java b/springboot/src/main/java/de/thpeetz/kontor/comics/views/ComicLayout.java new file mode 100644 index 0000000..cffdbdb --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/views/ComicLayout.java @@ -0,0 +1,43 @@ +package de.thpeetz.kontor.comics.views; + +import com.vaadin.flow.component.applayout.AppLayout; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.theme.lumo.LumoUtility; + +import de.thpeetz.kontor.admin.services.AdminService; +import de.thpeetz.kontor.comics.ComicConstants; +import de.thpeetz.kontor.common.views.KontorLayoutUtil; +import de.thpeetz.kontor.security.SecurityService; +import lombok.extern.slf4j.Slf4j; + +/** + * Represents a custom layout for the comic view in the application. + * This layout extends the AppLayout class. + */ +@Slf4j +public class ComicLayout extends AppLayout { + + private final AdminService adminService; + + private final SecurityService securityService; + + public ComicLayout(AdminService adminService, SecurityService securityService) { + this.adminService = adminService; + this.securityService = securityService; + + KontorLayoutUtil layout = new KontorLayoutUtil(this, adminService, securityService); + layout.setSecondaryNavigation(getSecondaryNavigation()); + layout.createHeader(ComicConstants.COMICS); + } + + private HorizontalLayout getSecondaryNavigation() { + HorizontalLayout navigation = new HorizontalLayout(); + navigation.addClassNames(LumoUtility.JustifyContent.CENTER, LumoUtility.Gap.SMALL, LumoUtility.Height.MEDIUM); + navigation.add(ComicConstants.getComicLink(), ComicConstants.getPublisherLink(), ComicConstants.getIssueLink(), + ComicConstants.getTradePaperbackLink(), ComicConstants.getStoryArcLink(), + ComicConstants.getVolumeLink(), ComicConstants.getArtistLink(), ComicConstants.getComicWorkLink(), + ComicConstants.getWorktypeLink()); + return navigation; + } +} + diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/views/ComicView.java b/springboot/src/main/java/de/thpeetz/kontor/comics/views/ComicView.java new file mode 100644 index 0000000..ec09544 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/views/ComicView.java @@ -0,0 +1,139 @@ +package de.thpeetz.kontor.comics.views; + +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Scope; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; + +import de.thpeetz.kontor.comics.ComicConstants; +import de.thpeetz.kontor.comics.data.Comic; +import de.thpeetz.kontor.comics.services.ComicService; +import de.thpeetz.kontor.common.views.MainLayout; +import jakarta.annotation.security.PermitAll; + +@Slf4j +@SpringComponent +@Scope("prototype") +@PermitAll +@Route(value = ComicConstants.COMICS_ROUTE, layout = MainLayout.class) +@PageTitle("Comic | Comics | Kontor") +public class ComicView extends VerticalLayout { + + Grid grid = new Grid<>(Comic.class); + TextField filterText = new TextField(); + ComicForm form; + ComicService service; + + public ComicView(ComicService service) { + this.service = service; + addClassName("comic-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + public Grid getGrid() { + return grid; + } + + private void configureGrid() { + grid.addClassName("comic-grid"); + grid.setSizeFull(); + grid.setColumns("title", "publisher.name", "currentOrder", "completed"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editComic(event.getValue())); + } + + public ComicForm getForm() { + return form; + } + + private void configureForm() { + form = new ComicForm(service.findAllPublishers(null)); + form.setWidth("25em"); + form.setVisible(false); + form.addSaveListener(this::saveComic); + form.addDeleteListener(this::deleteComic); + form.addCloseListener(e -> closeEditor()); + } + + private void saveComic(ComicForm.SaveEvent event) { + service.saveComic(event.getComic()); + updateList(); + closeEditor(); + } + + private void deleteComic(ComicForm.DeleteEvent event) { + service.deleteComic(event.getComic()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + filterText.setPlaceholder("Filter by name..."); + filterText.setClearButtonVisible(true); + filterText.setValueChangeMode(ValueChangeMode.LAZY); + filterText.addValueChangeListener(e -> updateList()); + + Button addComicButton = new Button("Add comic"); + addComicButton.addClickListener(click -> addComic()); + + HorizontalLayout toolbar = new HorizontalLayout(filterText, addComicButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editComic(Comic comic) { + if (comic == null) { + closeEditor(); + } else { + form.setComic(comic); + if (comic.getComicWorks() == null) { + log.info("No comic works"); + } else { + log.info("Comic works sze: {}", comic.getComicWorks().size()); + } + form.setComicWorks(comic.getComicWorks()); + form.setVisible(true); + addClassName("editing"); + } + } + + private void closeEditor() { + form.setComic(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addComic() { + grid.asSingleSelect().clear(); + editComic(new Comic()); + } + + public void updateList() { + grid.setItems(service.findAllComics(filterText.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/views/ComicWorkForm.java b/springboot/src/main/java/de/thpeetz/kontor/comics/views/ComicWorkForm.java new file mode 100644 index 0000000..698a37e --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/views/ComicWorkForm.java @@ -0,0 +1,113 @@ +package de.thpeetz.kontor.comics.views; + +import java.util.List; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.data.binder.BeanValidationBinder; +import com.vaadin.flow.data.binder.Binder; + +import de.thpeetz.kontor.comics.data.Artist; +import de.thpeetz.kontor.comics.data.Comic; +import de.thpeetz.kontor.comics.data.ComicWork; +import de.thpeetz.kontor.comics.data.Worktype; + +public class ComicWorkForm extends FormLayout { + ComboBox comic = new ComboBox<>("Comic"); + ComboBox artist = new ComboBox<>("Artist"); + ComboBox workType = new ComboBox<>("Worktype"); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(ComicWork.class); + + public ComicWorkForm(List comics, List artists, List workTypes) { + addClassName("comicwork-form"); + binder.bindInstanceFields(this); + + comic.setItems(comics); + comic.setItemLabelGenerator(Comic::getTitle); + artist.setItems(artists); + artist.setItemLabelGenerator(Artist::getName); + workType.setItems(workTypes); + workType.setItemLabelGenerator(Worktype::getName); + add(comic, artist, workType, createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new SaveEvent(this, binder.getBean())); + } + } + + public void setComicWork(ComicWork comicWork) { + binder.setBean(comicWork); + } + + public abstract static class ComicWorkFormEvent extends ComponentEvent { + private ComicWork comicWork; + + protected ComicWorkFormEvent(ComicWorkForm source, ComicWork comicWork) { + super(source, false); + this.comicWork = comicWork; + } + + public ComicWork getComicWork() { + return comicWork; + } + } + + public static class SaveEvent extends ComicWorkFormEvent { + SaveEvent(ComicWorkForm source, ComicWork comicWork) { + super(source, comicWork); + } + } + + public static class DeleteEvent extends ComicWorkFormEvent { + DeleteEvent(ComicWorkForm source, ComicWork comicWork) { + super(source, comicWork); + } + } + + public static class CloseEvent extends ComicWorkFormEvent { + CloseEvent(ComicWorkForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/views/ComicWorkView.java b/springboot/src/main/java/de/thpeetz/kontor/comics/views/ComicWorkView.java new file mode 100644 index 0000000..449a76c --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/views/ComicWorkView.java @@ -0,0 +1,122 @@ +package de.thpeetz.kontor.comics.views; + +import org.springframework.context.annotation.Scope; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; + +import de.thpeetz.kontor.comics.ComicConstants; +import de.thpeetz.kontor.comics.data.ComicWork; +import de.thpeetz.kontor.comics.services.ComicService; +import de.thpeetz.kontor.common.views.MainLayout; +import jakarta.annotation.security.PermitAll; + +@SpringComponent +@Scope("prototype") +@PermitAll +@Route(value = ComicConstants.COMICWORK_ROUTE, layout = MainLayout.class) +@PageTitle("ComicWork | Comics | Kontor") +public class ComicWorkView extends VerticalLayout { + + Grid grid = new Grid<>(ComicWork.class); + ComicWorkForm form; + ComicService service; + + public ComicWorkView(ComicService service) { + this.service = service; + addClassName("comicWork-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + public Grid getGrid() { + return grid; + } + + private void configureGrid() { + grid.addClassName("comic-grid"); + grid.setSizeFull(); + grid.setColumns("comic.title", "artist.name", "workType.name"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editComicWork(event.getValue())); + } + + public ComicWorkForm getForm() { + return form; + } + + private void configureForm() { + form = new ComicWorkForm(service.findAllComics(null), service.findAllArtists(null), + service.findAllWorktypes(null)); + form.setWidth("25em"); + form.setVisible(false); + form.addSaveListener(this::saveComicWork); + form.addDeleteListener(this::deleteComicWork); + form.addCloseListener(e -> closeEditor()); + } + + private void saveComicWork(ComicWorkForm.SaveEvent event) { + service.saveComicWork(event.getComicWork()); + updateList(); + closeEditor(); + } + + private void deleteComicWork(ComicWorkForm.DeleteEvent event) { + service.deleteComicWork(event.getComicWork()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + Button addComicButton = new Button("Add ComicWork"); + addComicButton.addClickListener(click -> addComicWork()); + + HorizontalLayout toolbar = new HorizontalLayout(addComicButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editComicWork(ComicWork comicWork) { + if (comicWork == null) { + closeEditor(); + } else { + form.setComicWork(comicWork); + form.setVisible(true); + addClassName("editing"); + } + } + + private void closeEditor() { + form.setComicWork(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addComicWork() { + grid.asSingleSelect().clear(); + editComicWork(new ComicWork()); + } + + public void updateList() { + grid.setItems(service.findAllComicWorks()); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/views/IssueForm.java b/springboot/src/main/java/de/thpeetz/kontor/comics/views/IssueForm.java new file mode 100644 index 0000000..d537210 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/views/IssueForm.java @@ -0,0 +1,113 @@ +package de.thpeetz.kontor.comics.views; + +import java.util.List; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +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.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 de.thpeetz.kontor.comics.data.Comic; +import de.thpeetz.kontor.comics.data.Issue; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class IssueForm extends FormLayout { + + ComboBox comic = new ComboBox<>("Comic"); + TextField issueNumber = new TextField("Issue number"); + Checkbox isRead = new Checkbox("Read"); + Checkbox inStock = new Checkbox("In stock"); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(Issue.class); + + public IssueForm(List comics) { + addClassName("issue-form"); + binder.bindInstanceFields(this); + + comic.setItems(comics); + comic.setItemLabelGenerator(Comic::getTitle); + add(comic, issueNumber, isRead, inStock, createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new SaveEvent(this, binder.getBean())); + } + } + + public void setIssue(Issue issue) { + binder.setBean(issue); + } + + public abstract static class IssueFormEvent extends ComponentEvent { + private Issue issue; + + protected IssueFormEvent(IssueForm source, Issue issue) { + super(source, false); + this.issue = issue; + } + + public Issue getIssue() { + return issue; + } + } + + public static class SaveEvent extends IssueFormEvent { + SaveEvent(IssueForm source, Issue issue) { + super(source, issue); + } + } + + public static class DeleteEvent extends IssueFormEvent { + DeleteEvent(IssueForm source, Issue issue) { + super(source, issue); + } + } + + public static class CloseEvent extends IssueFormEvent { + CloseEvent(IssueForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/views/IssueView.java b/springboot/src/main/java/de/thpeetz/kontor/comics/views/IssueView.java new file mode 100644 index 0000000..4b9d58c --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/views/IssueView.java @@ -0,0 +1,130 @@ +package de.thpeetz.kontor.comics.views; + +import org.springframework.context.annotation.Scope; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; + +import de.thpeetz.kontor.comics.ComicConstants; +import de.thpeetz.kontor.comics.data.Comic; +import de.thpeetz.kontor.comics.data.Issue; +import de.thpeetz.kontor.comics.services.ComicService; +import de.thpeetz.kontor.common.views.MainLayout; +import jakarta.annotation.security.PermitAll; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@SpringComponent +@Scope("prototype") +@PermitAll +@Route(value = ComicConstants.ISSUE_ROUTE, layout = MainLayout.class) +@PageTitle("Issue | Comics | Kontor") +public class IssueView extends VerticalLayout { + + Grid grid = new Grid<>(Issue.class); + ComboBox comicFilter = new ComboBox<>("Comic"); + IssueForm form; + ComicService service; + + public IssueView(ComicService service) { + this.service = service; + addClassName("issue-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + public Grid getGrid() { + return grid; + } + + private void configureGrid() { + grid.addClassName("issue-grid"); + grid.setSizeFull(); + grid.setColumns("comic.title", "issueNumber", "isRead", "inStock"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editIssue(event.getValue())); + } + + public IssueForm getForm() { + return form; + } + + private void configureForm() { + form = new IssueForm(service.findAllComics(null)); + form.setWidth("25em"); + form.setVisible(false); + form.addSaveListener(this::saveIssue); + form.addDeleteListener(this::deleteIssue); + form.addCloseListener(e -> closeEditor()); + } + + private void saveIssue(IssueForm.SaveEvent event) { + service.saveIssue(event.getIssue()); + updateList(); + closeEditor(); + } + + private void deleteIssue(IssueForm.DeleteEvent event) { + service.deleteIssue(event.getIssue()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + comicFilter.setItems(service.findAllComics(null)); + comicFilter.setItemLabelGenerator(Comic::getTitle); + comicFilter.addValueChangeListener(e -> updateList()); + comicFilter.setClearButtonVisible(true); + + Button addIssueButton = new Button("Add issue", click -> addIssue()); + + HorizontalLayout toolbar = new HorizontalLayout(comicFilter, addIssueButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editIssue(Issue issue) { + if (issue == null) { + closeEditor(); + } else { + form.setIssue(issue); + form.setVisible(true); + addClassName("editing"); + } + } + + private void closeEditor() { + form.setIssue(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addIssue() { + grid.asSingleSelect().clear(); + editIssue(new Issue()); + } + + private void updateList() { + grid.setItems(service.findAllIssuesForComic(comicFilter.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/views/PublisherForm.java b/springboot/src/main/java/de/thpeetz/kontor/comics/views/PublisherForm.java new file mode 100644 index 0000000..dbd2f62 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/views/PublisherForm.java @@ -0,0 +1,100 @@ +package de.thpeetz.kontor.comics.views; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +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 de.thpeetz.kontor.comics.data.Publisher; + +public class PublisherForm extends FormLayout { + public TextField name = new TextField("Name"); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(Publisher.class); + + public PublisherForm() { + addClassName("publisher-form"); + binder.bindInstanceFields(this); + + add(name, createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new SaveEvent(this, binder.getBean())); + } + } + + public void setPublisher(Publisher publisher) { + binder.setBean(publisher); + } + + public abstract static class PublisherFormEvent extends ComponentEvent { + private Publisher publisher; + + protected PublisherFormEvent(PublisherForm source, Publisher publisher) { + super(source, false); + this.publisher = publisher; + } + + public Publisher getPublisher() { + return publisher; + } + } + + public static class SaveEvent extends PublisherFormEvent { + SaveEvent(PublisherForm source, Publisher publisher) { + super(source, publisher); + } + } + + public static class DeleteEvent extends PublisherFormEvent { + DeleteEvent(PublisherForm source, Publisher publisher) { + super(source, publisher); + } + } + + public static class CloseEvent extends PublisherFormEvent { + CloseEvent(PublisherForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/views/PublisherView.java b/springboot/src/main/java/de/thpeetz/kontor/comics/views/PublisherView.java new file mode 100644 index 0000000..b716889 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/views/PublisherView.java @@ -0,0 +1,130 @@ +package de.thpeetz.kontor.comics.views; + +import org.springframework.context.annotation.Scope; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; + +import de.thpeetz.kontor.comics.ComicConstants; +import de.thpeetz.kontor.comics.data.Publisher; +import de.thpeetz.kontor.comics.services.ComicService; +import de.thpeetz.kontor.common.views.MainLayout; +import jakarta.annotation.security.PermitAll; + +@SpringComponent +@Scope("prototype") +@PermitAll +@Route(value = ComicConstants.PUBLISHER_ROUTE, layout = MainLayout.class) +@PageTitle("Publisher | Comics | Kontor") +public class PublisherView extends VerticalLayout { + + Grid grid = new Grid<>(Publisher.class); + TextField filterText = new TextField(); + PublisherForm form; + + ComicService service; + + public PublisherView(ComicService service) { + this.service = service; + addClassName("publisher-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + private void configureGrid() { + grid.addClassName("publisher-grid"); + grid.setSizeFull(); + grid.setColumns("name"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editPublisher(event.getValue())); + } + + private void configureForm() { + form = new PublisherForm(); + form.setWidth("25em"); + form.setVisible(false); + form.addSaveListener(this::savePublisher); + form.addDeleteListener(this::deletePublisher); + form.addCloseListener(e -> closeEditor()); + } + + private void savePublisher(PublisherForm.SaveEvent event) { + service.savePublisher(event.getPublisher()); + updateList(); + closeEditor(); + } + + private void deletePublisher(PublisherForm.DeleteEvent event) { + service.deletePublisher(event.getPublisher()); + updateList(); + closeEditor(); + } + + public Grid getGrid() { + return grid; + } + + public PublisherForm getForm() { + return form; + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + filterText.setPlaceholder("Filter by name..."); + filterText.setClearButtonVisible(true); + filterText.setValueChangeMode(ValueChangeMode.LAZY); + filterText.addValueChangeListener(e -> updateList()); + + Button addPublisherButton = new Button("Add publisher"); + addPublisherButton.addClickListener(click -> addPublisher()); + + HorizontalLayout toolbar = new HorizontalLayout(filterText, addPublisherButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editPublisher(Publisher publisher) { + if (publisher == null) { + closeEditor(); + } else { + form.setPublisher(publisher); + form.setVisible(true); + addClassName("editing"); + } + } + + private void closeEditor() { + form.setPublisher(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addPublisher() { + grid.asSingleSelect().clear(); + editPublisher(new Publisher()); + } + + public void updateList() { + grid.setItems(service.findAllPublishers(filterText.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/views/StoryArcForm.java b/springboot/src/main/java/de/thpeetz/kontor/comics/views/StoryArcForm.java new file mode 100644 index 0000000..786a792 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/views/StoryArcForm.java @@ -0,0 +1,108 @@ +package de.thpeetz.kontor.comics.views; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.combobox.ComboBox; +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 de.thpeetz.kontor.comics.data.Comic; +import de.thpeetz.kontor.comics.data.StoryArc; + +import java.util.List; + +public class StoryArcForm extends FormLayout { + + ComboBox comic = new ComboBox<>("Comic"); + TextField name = new TextField("Story Arc Name"); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(StoryArc.class); + + public StoryArcForm(List comics) { + addClassName("storyarc-form"); + binder.bindInstanceFields(this); + + comic.setItems(comics); + comic.setItemLabelGenerator(Comic::getTitle); + add(comic, name, createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new SaveEvent(this, binder.getBean())); + } + } + + public void setStoryArc(StoryArc storyArc) { + binder.setBean(storyArc); + } + + public abstract static class StoryArcFormEvent extends ComponentEvent { + private StoryArc storyArc; + + protected StoryArcFormEvent(StoryArcForm source, StoryArc storyArc) { + super(source, false); + this.storyArc = storyArc; + } + + public StoryArc getStoryArc() { + return storyArc; + } + } + + public static class SaveEvent extends StoryArcFormEvent { + SaveEvent(StoryArcForm source, StoryArc storyArc) { + super(source, storyArc); + } + } + + public static class DeleteEvent extends StoryArcFormEvent { + DeleteEvent(StoryArcForm source, StoryArc storyArc) { + super(source, storyArc); + } + } + + public static class CloseEvent extends StoryArcFormEvent { + CloseEvent(StoryArcForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/views/StoryArcView.java b/springboot/src/main/java/de/thpeetz/kontor/comics/views/StoryArcView.java new file mode 100644 index 0000000..6044383 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/views/StoryArcView.java @@ -0,0 +1,130 @@ +package de.thpeetz.kontor.comics.views; + +import org.springframework.context.annotation.Scope; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; + +import de.thpeetz.kontor.comics.ComicConstants; +import de.thpeetz.kontor.comics.data.Comic; +import de.thpeetz.kontor.comics.data.StoryArc; +import de.thpeetz.kontor.comics.services.ComicService; +import de.thpeetz.kontor.common.views.MainLayout; +import jakarta.annotation.security.PermitAll; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@SpringComponent +@Scope("prototype") +@PermitAll +@Route(value = ComicConstants.STORYARC_ROTE, layout = MainLayout.class) +@PageTitle("StoryArc | Comics | Kontor") +public class StoryArcView extends VerticalLayout { + + Grid grid = new Grid<>(StoryArc.class); + ComboBox comicFilter = new ComboBox<>("Comic"); + StoryArcForm form; + ComicService service; + + public StoryArcView(ComicService service) { + this.service = service; + addClassName("storyarc-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + StoryArcForm getForm() { + return form; + } + + public Grid getGrid() { + return grid; + } + + private void configureGrid() { + grid.addClassName("storyarc-grid"); + grid.setSizeFull(); + grid.setColumns("comic.title", "name"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editStoryArc(event.getValue())); + } + + private void configureForm() { + form = new StoryArcForm(service.findAllComics(null)); + form.setWidth("25em"); + form.setVisible(false); + form.addSaveListener(this::saveStoryArc); + form.addDeleteListener(this::deleteStoryArc); + form.addCloseListener(e -> closeEditor()); + } + + private void saveStoryArc(StoryArcForm.SaveEvent event) { + service.saveStoryArc(event.getStoryArc()); + updateList(); + closeEditor(); + } + + private void deleteStoryArc(StoryArcForm.DeleteEvent event) { + service.deleteStoryArc(event.getStoryArc()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + comicFilter.setItems(service.findAllComics(null)); + comicFilter.setItemLabelGenerator(Comic::getTitle); + comicFilter.addValueChangeListener(e -> updateList()); + comicFilter.setClearButtonVisible(true); + + Button addStoryArcButton = new Button("Add StoryArc", click -> addStoryArc()); + + HorizontalLayout toolbar = new HorizontalLayout(comicFilter, addStoryArcButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editStoryArc(StoryArc storyArc) { + if (storyArc == null) { + closeEditor(); + } else { + form.setStoryArc(storyArc); + form.setVisible(true); + addClassName("editing"); + } + } + + public void closeEditor() { + form.setStoryArc(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addStoryArc() { + grid.asSingleSelect().clear(); + editStoryArc(new StoryArc()); + } + + private void updateList() { + grid.setItems(service.findAllStoryArcsForComic(comicFilter.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/views/TradePaperBackForm.java b/springboot/src/main/java/de/thpeetz/kontor/comics/views/TradePaperBackForm.java new file mode 100644 index 0000000..8a1b477 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/views/TradePaperBackForm.java @@ -0,0 +1,109 @@ +package de.thpeetz.kontor.comics.views; + +import java.util.List; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.combobox.ComboBox; +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 de.thpeetz.kontor.comics.data.Comic; +import de.thpeetz.kontor.comics.data.TradePaperback; + +public class TradePaperBackForm extends FormLayout { + TextField name = new TextField("Name"); + ComboBox comic = new ComboBox<>("Comic"); + TextField issueStart = new TextField("Issue Start"); + TextField issueEnd = new TextField("Issue End"); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(TradePaperback.class); + + public TradePaperBackForm(List comics) { + addClassName("tradepaperback-form"); + binder.bindInstanceFields(this); + + comic.setItems(comics); + comic.setItemLabelGenerator(Comic::getTitle); + add(name, comic, issueStart, issueEnd, createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new SaveEvent(this, binder.getBean())); + } + } + + public void setTradePaperBack(TradePaperback tradepaperback) { + binder.setBean(tradepaperback); + } + + public abstract static class TradePaperBackFormEvent extends ComponentEvent { + private TradePaperback tradepaperback; + + protected TradePaperBackFormEvent(TradePaperBackForm source, TradePaperback tradepaperback) { + super(source, false); + this.tradepaperback = tradepaperback; + } + + public TradePaperback getTradePaperBack() { + return tradepaperback; + } + } + + public static class SaveEvent extends TradePaperBackFormEvent { + SaveEvent(TradePaperBackForm source, TradePaperback tradepaperback) { + super(source, tradepaperback); + } + } + + public static class DeleteEvent extends TradePaperBackFormEvent { + DeleteEvent(TradePaperBackForm source, TradePaperback tradepaperback) { + super(source, tradepaperback); + } + } + + public static class CloseEvent extends TradePaperBackFormEvent { + CloseEvent(TradePaperBackForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/views/TradePaperbackView.java b/springboot/src/main/java/de/thpeetz/kontor/comics/views/TradePaperbackView.java new file mode 100644 index 0000000..0215234 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/views/TradePaperbackView.java @@ -0,0 +1,129 @@ +package de.thpeetz.kontor.comics.views; + +import org.springframework.context.annotation.Scope; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; + +import de.thpeetz.kontor.comics.ComicConstants; +import de.thpeetz.kontor.comics.data.TradePaperback; +import de.thpeetz.kontor.comics.services.ComicService; +import de.thpeetz.kontor.common.views.MainLayout; +import jakarta.annotation.security.PermitAll; + +@SpringComponent +@Scope("prototype") +@PermitAll +@Route(value = ComicConstants.TPB_ROUTE, layout = MainLayout.class) +@PageTitle("TradePaperBack | Comics | Kontor") +public class TradePaperbackView extends VerticalLayout { + + Grid grid = new Grid<>(TradePaperback.class); + TextField filterText = new TextField(); + TradePaperBackForm form; + ComicService service; + + public TradePaperbackView(ComicService service) { + this.service = service; + addClassName("tradepaperback-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + public Grid getGrid() { + return grid; + } + + private void configureGrid() { + grid.addClassName("tradepaperback-grid"); + grid.setSizeFull(); + grid.setColumns("name", "comic.title", "issueStart", "issueEnd"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editTradePaperBack(event.getValue())); + } + + public TradePaperBackForm getForm() { + return form; + } + + private void configureForm() { + form = new TradePaperBackForm(service.findAllComics(null)); + form.setWidth("25em"); + form.setVisible(false); + form.addSaveListener(this::saveTradePaperBack); + form.addDeleteListener(this::deleteTradePaperBack); + form.addCloseListener(e -> closeEditor()); + } + + private void saveTradePaperBack(TradePaperBackForm.SaveEvent event) { + service.saveTradePaperBack(event.getTradePaperBack()); + updateList(); + closeEditor(); + } + + private void deleteTradePaperBack(TradePaperBackForm.DeleteEvent event) { + service.deleteTradePaperBack(event.getTradePaperBack()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + filterText.setPlaceholder("Filter by name..."); + filterText.setClearButtonVisible(true); + filterText.setValueChangeMode(ValueChangeMode.LAZY); + filterText.addValueChangeListener(e -> updateList()); + + Button addTradePaperBackButton = new Button("Add TradePaperBack"); + addTradePaperBackButton.addClickListener(click -> addTradePaperBack()); + + HorizontalLayout toolbar = new HorizontalLayout(filterText, addTradePaperBackButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editTradePaperBack(TradePaperback tradepaperback) { + if (tradepaperback == null) { + closeEditor(); + } else { + form.setTradePaperBack(tradepaperback); + form.setVisible(true); + addClassName("editing"); + } + } + + private void closeEditor() { + form.setTradePaperBack(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addTradePaperBack() { + grid.asSingleSelect().clear(); + editTradePaperBack(new TradePaperback()); + } + + public void updateList() { + grid.setItems(service.findAllTradePaperbacks(filterText.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/views/VolumeForm.java b/springboot/src/main/java/de/thpeetz/kontor/comics/views/VolumeForm.java new file mode 100644 index 0000000..9b333a9 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/views/VolumeForm.java @@ -0,0 +1,109 @@ +package de.thpeetz.kontor.comics.views; + +import java.util.List; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.combobox.ComboBox; +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 de.thpeetz.kontor.comics.data.Comic; +import de.thpeetz.kontor.comics.data.StoryArc; +import de.thpeetz.kontor.comics.data.Volume; + +public class VolumeForm extends FormLayout { + + ComboBox comic = new ComboBox<>("Comic"); + public TextField name = new TextField("Name"); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(Volume.class); + + public VolumeForm(List comics) { + addClassName("volume-form"); + binder.bindInstanceFields(this); + + comic.setItems(comics); + comic.setItemLabelGenerator(Comic::getTitle); + add(comic, name, createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new SaveEvent(this, binder.getBean())); + } + } + + public void setVolume(Volume volume) { + binder.setBean(volume); + } + + public abstract static class VolumeFormEvent extends ComponentEvent { + private Volume volume; + + protected VolumeFormEvent(VolumeForm source, Volume volume) { + super(source, false); + this.volume = volume; + } + + public Volume getVolume() { + return volume; + } + } + + public static class SaveEvent extends VolumeFormEvent { + SaveEvent(VolumeForm source, Volume volume) { + super(source, volume); + } + } + + public static class DeleteEvent extends VolumeFormEvent { + DeleteEvent(VolumeForm source, Volume volume) { + super(source, volume); + } + } + + public static class CloseEvent extends VolumeFormEvent { + CloseEvent(VolumeForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/views/VolumeView.java b/springboot/src/main/java/de/thpeetz/kontor/comics/views/VolumeView.java new file mode 100644 index 0000000..47a2201 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/views/VolumeView.java @@ -0,0 +1,130 @@ +package de.thpeetz.kontor.comics.views; + +import org.springframework.context.annotation.Scope; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; + +import de.thpeetz.kontor.comics.ComicConstants; +import de.thpeetz.kontor.comics.data.Comic; +import de.thpeetz.kontor.comics.data.Volume; +import de.thpeetz.kontor.comics.services.ComicService; +import de.thpeetz.kontor.common.views.MainLayout; +import jakarta.annotation.security.PermitAll; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@SpringComponent +@Scope("prototype") +@PermitAll +@Route(value = ComicConstants.VOLUME_ROUTE, layout = MainLayout.class) +@PageTitle("Volume | Comics | Kontor") +public class VolumeView extends VerticalLayout { + + Grid grid = new Grid<>(Volume.class); + ComboBox comicFilter = new ComboBox<>("Comic"); + VolumeForm form; + ComicService service; + + public VolumeView(ComicService service) { + this.service = service; + addClassName("volume-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + VolumeForm getForm() { + return form; + } + + public Grid getGrid() { + return grid; + } + + private void configureGrid() { + grid.addClassName("volume-grid"); + grid.setSizeFull(); + grid.setColumns("comic.title", "name"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editVolume(event.getValue())); + } + + private void configureForm() { + form = new VolumeForm(service.findAllComics(null)); + form.setWidth("25em"); + form.setVisible(false); + form.addSaveListener(this::saveVolume); + form.addDeleteListener(this::deleteVolume); + form.addCloseListener(e -> closeEditor()); + } + + private void saveVolume(VolumeForm.SaveEvent event) { + service.saveVolume(event.getVolume()); + updateList(); + closeEditor(); + } + + private void deleteVolume(VolumeForm.DeleteEvent event) { + service.deleteVolume(event.getVolume()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + comicFilter.setItems(service.findAllComics(null)); + comicFilter.setItemLabelGenerator(Comic::getTitle); + comicFilter.addValueChangeListener(e -> updateList()); + comicFilter.setClearButtonVisible(true); + + Button addVolumeButton = new Button("Add Volume", click -> addVolume()); + + HorizontalLayout toolbar = new HorizontalLayout(comicFilter, addVolumeButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editVolume(Volume volume) { + if (volume == null) { + closeEditor(); + } else { + form.setVolume(volume); + form.setVisible(true); + addClassName("editing"); + } + } + + public void closeEditor() { + form.setVolume(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addVolume() { + grid.asSingleSelect().clear(); + editVolume(new Volume()); + } + + private void updateList() { + grid.setItems(service.findAllVolumesForComic(comicFilter.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/views/WorktypeForm.java b/springboot/src/main/java/de/thpeetz/kontor/comics/views/WorktypeForm.java new file mode 100644 index 0000000..0965229 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/views/WorktypeForm.java @@ -0,0 +1,121 @@ +package de.thpeetz.kontor.comics.views; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.grid.Grid; +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 de.thpeetz.kontor.comics.data.ComicWork; +import de.thpeetz.kontor.comics.data.Worktype; + +public class WorktypeForm extends FormLayout { + + private static final Logger log = LoggerFactory.getLogger(WorktypeForm.class); + + TextField name = new TextField("Name"); + Grid comicWorks = new Grid<>(ComicWork.class); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(Worktype.class); + + public WorktypeForm() { + addClassName("worktype-form"); + binder.bindInstanceFields(this); + + comicWorks.setColumns("comic.title", "artist.name"); + comicWorks.getColumnByKey("comic.title").setHeader("Comic"); + comicWorks.getColumnByKey("artist.name").setHeader("Artist"); + comicWorks.getColumns().forEach(col -> col.setAutoWidth(true)); + + add(name, comicWorks, createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new SaveEvent(this, binder.getBean())); + } + } + + public void setWorktype(Worktype worktype) { + binder.setBean(worktype); + } + + public void setComicWorks(List works) { + log.info("Setting comic works: {}", works); + this.comicWorks.setItems(works); + } + + public abstract static class WorktypeFormEvent extends ComponentEvent { + private Worktype worktype; + + protected WorktypeFormEvent(WorktypeForm source, Worktype worktype) { + super(source, false); + this.worktype = worktype; + } + + public Worktype getWorktype() { + return worktype; + } + } + + public static class SaveEvent extends WorktypeFormEvent { + SaveEvent(WorktypeForm source, Worktype worktype) { + super(source, worktype); + } + } + + public static class DeleteEvent extends WorktypeFormEvent { + DeleteEvent(WorktypeForm source, Worktype worktype) { + super(source, worktype); + } + } + + public static class CloseEvent extends WorktypeFormEvent { + CloseEvent(WorktypeForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/comics/views/WorktypeView.java b/springboot/src/main/java/de/thpeetz/kontor/comics/views/WorktypeView.java new file mode 100644 index 0000000..98f3adb --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/comics/views/WorktypeView.java @@ -0,0 +1,130 @@ +package de.thpeetz.kontor.comics.views; + +import org.springframework.context.annotation.Scope; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; + +import de.thpeetz.kontor.comics.ComicConstants; +import de.thpeetz.kontor.comics.data.Worktype; +import de.thpeetz.kontor.comics.services.ComicService; +import de.thpeetz.kontor.common.views.MainLayout; +import jakarta.annotation.security.PermitAll; + +@SpringComponent +@Scope("prototype") +@PermitAll +@Route(value = ComicConstants.WORKTYPE_ROUTE, layout = MainLayout.class) +@PageTitle("Worktype | Comics | Kontor") +public class WorktypeView extends VerticalLayout { + + Grid grid = new Grid<>(Worktype.class); + TextField filterText = new TextField(); + WorktypeForm form; + ComicService service; + + public WorktypeView(ComicService service) { + this.service = service; + addClassName("worktype-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + public Grid getGrid() { + return grid; + } + + private void configureGrid() { + grid.addClassName("worktype-grid"); + grid.setSizeFull(); + grid.setColumns("name"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editWorktype(event.getValue())); + } + + public WorktypeForm getForm() { + return form; + } + + private void configureForm() { + form = new WorktypeForm(); + form.setWidth("25em"); + form.setVisible(false); + form.addSaveListener(this::saveWorktype); + form.addDeleteListener(this::deleteWorktype); + form.addCloseListener(e -> closeEditor()); + } + + private void saveWorktype(WorktypeForm.SaveEvent event) { + service.saveWorktype(event.getWorktype()); + updateList(); + closeEditor(); + } + + private void deleteWorktype(WorktypeForm.DeleteEvent event) { + service.deleteWorktype(event.getWorktype()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + filterText.setPlaceholder("Filter by name..."); + filterText.setClearButtonVisible(true); + filterText.setValueChangeMode(ValueChangeMode.LAZY); + filterText.addValueChangeListener(e -> updateList()); + + Button addWorktypeButton = new Button("Add worktype"); + addWorktypeButton.addClickListener(click -> addWorktype()); + + HorizontalLayout toolbar = new HorizontalLayout(filterText, addWorktypeButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editWorktype(Worktype worktype) { + if (worktype == null) { + closeEditor(); + } else { + form.setWorktype(worktype); + form.setComicWorks(worktype.getComicWorks()); + form.setVisible(true); + addClassName("editing"); + } + } + + private void closeEditor() { + form.setWorktype(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addWorktype() { + grid.asSingleSelect().clear(); + editWorktype(new Worktype()); + } + + public void updateList() { + grid.setItems(service.findAllWorktypes(filterText.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/common/data/AbstractEntity.java b/springboot/src/main/java/de/thpeetz/kontor/common/data/AbstractEntity.java new file mode 100644 index 0000000..f9cfdaf --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/common/data/AbstractEntity.java @@ -0,0 +1,34 @@ +package de.thpeetz.kontor.common.data; + +import jakarta.persistence.*; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.util.Date; + +@Slf4j +@Getter +@Setter +@EqualsAndHashCode +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public abstract class AbstractEntity { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private String id; + + @Version + private int version; + + @CreatedDate + private Date createdDate; + + @LastModifiedDate + private Date lastModifiedDate; +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/common/data/AbstractLinkEntity.java b/springboot/src/main/java/de/thpeetz/kontor/common/data/AbstractLinkEntity.java new file mode 100644 index 0000000..7dfdf50 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/common/data/AbstractLinkEntity.java @@ -0,0 +1,55 @@ +package de.thpeetz.kontor.common.data; + +import jakarta.annotation.Nullable; +import jakarta.persistence.*; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.util.Date; + +@Slf4j +@Getter +@Setter +@EqualsAndHashCode +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public abstract class AbstractLinkEntity { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private String id; + + @Version + private int version; + + @CreatedDate + private Date createdDate; + + @LastModifiedDate + private Date lastModifiedDate; + + @Nullable + private String url; + + private boolean review; + + private boolean shouldDownload; + + @Nullable + private String title; + + @Nullable + private String cloudLink; + + @Nullable + private String fileName; + + @Nullable + private String path; + +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/common/views/AvatarMenuBar.java b/springboot/src/main/java/de/thpeetz/kontor/common/views/AvatarMenuBar.java new file mode 100644 index 0000000..1678e58 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/common/views/AvatarMenuBar.java @@ -0,0 +1,52 @@ +package de.thpeetz.kontor.common.views; + +import com.vaadin.flow.component.avatar.Avatar; +import com.vaadin.flow.component.contextmenu.MenuItem; +import com.vaadin.flow.component.contextmenu.SubMenu; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.menubar.MenuBar; +import com.vaadin.flow.component.menubar.MenuBarVariant; +import com.vaadin.flow.router.Route; + +import com.vaadin.flow.router.Router; +import de.thpeetz.kontor.admin.services.AdminService; +import de.thpeetz.kontor.admin.views.UserProfileView; +import de.thpeetz.kontor.security.SecurityService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; + +@Slf4j +@Route("avatar-menu-bar") +public class AvatarMenuBar extends Div { + + private final SecurityService securityService; + + final AdminService adminService; + + public AvatarMenuBar(SecurityService securityService, AdminService adminService) { + this.adminService = adminService; + this.securityService = securityService; + + Avatar avatar = new Avatar(); + securityService.getAuthenticatedUser().ifPresent(user -> { + log.info("AdminService: {}", adminService); + if (user != null) { + String userName = user.getUsername(); + String fullName = adminService.getUserFullName(userName); + avatar.setName(fullName); + } + }); + // avatar.setImage(pictureUrl); + MenuBar menuBar = new MenuBar(); + menuBar.addThemeVariants(MenuBarVariant.LUMO_TERTIARY_INLINE); + MenuItem menuItem = menuBar.addItem(avatar); + SubMenu subMenu = menuItem.getSubMenu(); + MenuItem logoutMenuItem = subMenu.addItem("Log out"); + logoutMenuItem.addClickListener(e -> securityService.logout()); + MenuItem profileItem = subMenu.addItem("Profile"); + profileItem.addClickListener(e -> profileItem.getUI().ifPresent(ui -> ui.navigate(UserProfileView.class))); + subMenu.addItem("Settings"); + subMenu.addItem("Help"); + add(menuBar); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/common/views/KontorLayoutUtil.java b/springboot/src/main/java/de/thpeetz/kontor/common/views/KontorLayoutUtil.java new file mode 100644 index 0000000..4c2112b --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/common/views/KontorLayoutUtil.java @@ -0,0 +1,100 @@ +package de.thpeetz.kontor.common.views; + +import com.vaadin.flow.component.accordion.Accordion; +import com.vaadin.flow.component.applayout.AppLayout; +import com.vaadin.flow.component.applayout.AppLayout.Section; +import com.vaadin.flow.component.applayout.DrawerToggle; +import com.vaadin.flow.component.html.H1; +import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.orderedlayout.FlexComponent; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.Scroller; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.sidenav.SideNav; +import com.vaadin.flow.router.RouterLink; +import com.vaadin.flow.theme.lumo.LumoUtility; + +import de.thpeetz.kontor.admin.AdminConstants; +import de.thpeetz.kontor.admin.services.AdminService; +import de.thpeetz.kontor.bookshelf.BookshelfConstants; +import de.thpeetz.kontor.comics.ComicConstants; +import de.thpeetz.kontor.security.SecurityService; +import de.thpeetz.kontor.tysc.TyscConstants; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class KontorLayoutUtil { + + private final AppLayout appLayout; + private HorizontalLayout secondaryNavigation; + + private AdminService adminService; + + private SecurityService securityService; + + public KontorLayoutUtil(AppLayout layout, AdminService adminService, SecurityService securityService) { + this.adminService = adminService; + this.securityService = securityService; + this.appLayout = layout; + } + + public void setSecondaryNavigation(HorizontalLayout secondaryNavigation) { + this.secondaryNavigation = secondaryNavigation; + } + + public void createHeader(String titleName) { + appLayout.addToDrawer(createTitle(), getScroller()); + appLayout.addToNavbar(getHeader(titleName)); + appLayout.setPrimarySection(Section.DRAWER); + } + + private H1 createTitle() { + H1 appTitle = new H1(new RouterLink("Kontor", MainView.class)); + appTitle.getStyle().set("font-size", "var(--lumo-font-size-l)") + .set("line-height", "var(--lumo-size-l)") + .set("margin", "0 var(--lumo-space-m)"); + return appTitle; + } + + private Scroller getScroller() { + Scroller scroller = new Scroller(getPrimaryNavigation()); + scroller.setClassName(LumoUtility.Padding.SMALL); + return scroller; + } + + private VerticalLayout getHeader(String header) { + DrawerToggle toggle = new DrawerToggle(); + H2 viewTitle = new H2(header); + viewTitle.addClassNames(LumoUtility.FontSize.LARGE, LumoUtility.Margin.NONE); + HorizontalLayout subViews = this.secondaryNavigation; + + AvatarMenuBar avatar = new AvatarMenuBar(securityService, adminService); + HorizontalLayout wrapper = new HorizontalLayout(toggle, viewTitle, avatar); + wrapper.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER); + wrapper.expand(viewTitle); + wrapper.setWidthFull(); + wrapper.setSpacing(false); + + VerticalLayout viewHeader = new VerticalLayout(wrapper, subViews); + viewHeader.setPadding(false); + viewHeader.setSpacing(false); + return viewHeader; + } + + private SideNav getPrimaryNavigation() { + SideNav sideNav = new SideNav(); + sideNav.addItem(ComicConstants.getComicsNavigation()); + sideNav.addItem(TyscConstants.getTyscNavigation()); + sideNav.addItem(BookshelfConstants.getBookshelfNavigation()); + securityService.getAuthenticatedUser().ifPresent(user -> { + log.info("User {} found", user.getUsername()); + boolean isAdmin = user.getAuthorities().stream() + .anyMatch(grantedAuthority -> "ROLE_ADMIN".equals(grantedAuthority.getAuthority())); + log.info("User {} hat Admin-Rechte: {}", user, isAdmin); + if (isAdmin) { + sideNav.addItem(AdminConstants.getAdminNavigation()); + } + }); + return sideNav; + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/common/views/MainLayout.java b/springboot/src/main/java/de/thpeetz/kontor/common/views/MainLayout.java new file mode 100644 index 0000000..96f720f --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/common/views/MainLayout.java @@ -0,0 +1,101 @@ +package de.thpeetz.kontor.common.views; + +import com.vaadin.flow.component.applayout.AppLayout; +import com.vaadin.flow.component.applayout.DrawerToggle; +import com.vaadin.flow.component.html.H1; +import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.orderedlayout.FlexComponent; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.Scroller; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.sidenav.SideNav; +import com.vaadin.flow.component.sidenav.SideNavItem; +import com.vaadin.flow.router.RouterLink; +import com.vaadin.flow.theme.lumo.LumoUtility; + +import de.thpeetz.kontor.admin.AdminConstants; +import de.thpeetz.kontor.admin.services.AdminService; +import de.thpeetz.kontor.bookshelf.BookshelfConstants; +import de.thpeetz.kontor.comics.ComicConstants; +import de.thpeetz.kontor.mailclient.views.EmailView; +import de.thpeetz.kontor.media.MediaConstants; +import de.thpeetz.kontor.security.SecurityService; +import de.thpeetz.kontor.tysc.TyscConstants; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.GrantedAuthority; + +import java.util.ArrayList; +import java.util.Collection; + +@Slf4j +public class MainLayout extends AppLayout { + + private final AdminService adminService; + + private final SecurityService securityService; + + public MainLayout(AdminService adminService, SecurityService securityService) { + this.adminService = adminService; + this.securityService = securityService; + + createHeader("Kontor"); + createDrawer(); + } + + public void createDrawer() { + H1 appTitle = new H1(new RouterLink("Kontor", MainView.class)); + appTitle.getStyle().set("font-size", "var(--lumo-font-size-l)") + .set("line-height", "var(--lumo-size-l)") + .set("margin", "0 var(--lumo-space-m)"); + Scroller scroller = new Scroller(getPrimaryNavigation()); + scroller.setClassName(LumoUtility.Padding.SMALL); + addToDrawer(appTitle, scroller); + } + + public void createHeader(String titleName) { + DrawerToggle toggle = new DrawerToggle(); + H2 viewTitle = new H2(titleName); + viewTitle.addClassNames(LumoUtility.FontSize.LARGE, LumoUtility.Margin.NONE); + + AvatarMenuBar avatar = new AvatarMenuBar(securityService, adminService); + HorizontalLayout wrapper = new HorizontalLayout(toggle, viewTitle, avatar); + wrapper.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER); + wrapper.expand(viewTitle); + wrapper.setWidthFull(); + wrapper.setSpacing(false); + + VerticalLayout viewHeader = new VerticalLayout(wrapper); + viewHeader.setPadding(false); + viewHeader.setSpacing(false); + + addToNavbar(viewHeader); + setPrimarySection(Section.DRAWER); + } + + private SideNav getPrimaryNavigation() { + SideNav sideNav = new SideNav(); + ArrayList roles = new ArrayList<>(); + securityService.getAuthenticatedUser().ifPresent(user -> { + log.info("Get roles for user {}", user.getUsername()); + user.getAuthorities().forEach(grantedAuthority -> { + roles.add(grantedAuthority.getAuthority()); + }); + log.info("user {} has roles: {}", user, roles); + }); + sideNav.addItem(ComicConstants.getComicsNavigation()); + sideNav.addItem(TyscConstants.getTyscNavigation()); + sideNav.addItem(BookshelfConstants.getBookshelfNavigation()); + sideNav.addItem(MediaConstants.getMediaNavigation(roles)); + sideNav.addItem(new SideNavItem("Emails", EmailView.class)); + securityService.getAuthenticatedUser().ifPresent(user -> { + log.info("User {} found", user.getUsername()); + boolean isAdmin = user.getAuthorities().stream() + .anyMatch(grantedAuthority -> "ROLE_ADMIN".equals(grantedAuthority.getAuthority())); + log.info("User {} hat Admin-Rechte: {}", user, isAdmin); + if (isAdmin) { + sideNav.addItem(AdminConstants.getAdminNavigation()); + } + }); + return sideNav; + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/common/views/MainView.java b/springboot/src/main/java/de/thpeetz/kontor/common/views/MainView.java new file mode 100644 index 0000000..08edac9 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/common/views/MainView.java @@ -0,0 +1,18 @@ +package de.thpeetz.kontor.common.views; + +import org.springframework.context.annotation.Scope; + +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.auth.AnonymousAllowed; +import com.vaadin.flow.spring.annotation.SpringComponent; + +@SpringComponent +@Scope("prototype") +@AnonymousAllowed +@Route(value = "/", layout = MainLayout.class) +@PageTitle("Kontor") +public class MainView extends VerticalLayout { + +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/common/views/SeparateMainLayout.java b/springboot/src/main/java/de/thpeetz/kontor/common/views/SeparateMainLayout.java new file mode 100644 index 0000000..474d4a7 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/common/views/SeparateMainLayout.java @@ -0,0 +1,31 @@ +package de.thpeetz.kontor.common.views; + +import com.vaadin.flow.component.applayout.AppLayout; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.theme.lumo.LumoUtility; + +import de.thpeetz.kontor.admin.services.AdminService; +import de.thpeetz.kontor.security.SecurityService; + +public class SeparateMainLayout extends AppLayout { + + private final AdminService adminService; + + private final SecurityService securityService; + + public SeparateMainLayout(AdminService adminService, SecurityService securityService) { + this.adminService = adminService; + this.securityService = securityService; + + KontorLayoutUtil layout = new KontorLayoutUtil(this, adminService, securityService); + layout.setSecondaryNavigation(getSecondaryNavigation()); + layout.createHeader("Kontor"); + } + + private HorizontalLayout getSecondaryNavigation() { + HorizontalLayout navigation = new HorizontalLayout(); + navigation.addClassNames(LumoUtility.JustifyContent.CENTER, LumoUtility.Gap.SMALL, LumoUtility.Height.MEDIUM); + return navigation; + } + +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/mailclient/data/Mail.java b/springboot/src/main/java/de/thpeetz/kontor/mailclient/data/Mail.java new file mode 100644 index 0000000..f3b80a8 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/mailclient/data/Mail.java @@ -0,0 +1,20 @@ +package de.thpeetz.kontor.mailclient.data; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.persistence.Entity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper=false) +@Entity +public class Mail extends AbstractEntity { + + private String folder; + private String subject; + private String body; + private Date sentDate; + private Date receivedDate; +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/mailclient/data/MailAccount.java b/springboot/src/main/java/de/thpeetz/kontor/mailclient/data/MailAccount.java new file mode 100644 index 0000000..840d8da --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/mailclient/data/MailAccount.java @@ -0,0 +1,29 @@ +package de.thpeetz.kontor.mailclient.data; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.persistence.Entity; +import jakarta.validation.constraints.NotEmpty; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Getter +@Setter +@Entity +public class MailAccount extends AbstractEntity { + + @NotEmpty + private String host; + + private Integer port; + + @NotEmpty + private String protocol; + + private String userName; + + private String password; + + private Boolean startTls; +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/mailclient/views/EmailView.java b/springboot/src/main/java/de/thpeetz/kontor/mailclient/views/EmailView.java new file mode 100644 index 0000000..4235dd1 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/mailclient/views/EmailView.java @@ -0,0 +1,222 @@ +package de.thpeetz.kontor.mailclient.views; + +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.List; +import java.util.Properties; + +import javax.mail.Address; +import javax.mail.Folder; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.NoSuchProviderException; +import javax.mail.Session; +import javax.mail.Store; +import javax.mail.internet.InternetAddress; + +import com.sun.mail.imap.IMAPFolder; +import com.vaadin.flow.component.Composite; +import com.vaadin.flow.component.accordion.Accordion; +import com.vaadin.flow.component.html.Paragraph; +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.component.menubar.MenuBar; +import com.vaadin.flow.component.messages.MessageList; +import com.vaadin.flow.component.messages.MessageListItem; +import com.vaadin.flow.component.orderedlayout.FlexComponent.Alignment; +import com.vaadin.flow.component.orderedlayout.FlexComponent.JustifyContentMode; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.sidenav.SideNav; +import com.vaadin.flow.component.sidenav.SideNavItem; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.router.RouteAlias; +import com.vaadin.flow.server.auth.AnonymousAllowed; +import com.vaadin.flow.theme.lumo.LumoUtility.Gap; + +import de.thpeetz.kontor.mailclient.data.MailAccount; +import de.thpeetz.kontor.admin.services.MailService; +import de.thpeetz.kontor.common.views.MainLayout; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@PageTitle("Email") +@AnonymousAllowed +@Route(value = "mail", layout = MainLayout.class) +@RouteAlias(value = "kontor/mail", layout = MainLayout.class) +public class EmailView extends Composite { + + MenuBar menuBar = new MenuBar(); + Accordion accordion = new Accordion(); + SideNav folderList = new SideNav(); + MailService mailService; + + public EmailView(MailService mailService) { + this.mailService = mailService; + configureView(); + updateFolderList(); + } + + private void updateFolderList() { + List accounts = mailService.findAllMailAccounts(); + MailAccount account = accounts.get(0); + + Properties properties = new Properties(); + properties.put(String.format("mail.%s.host", account.getProtocol()), account.getHost()); + properties.put(String.format("mail.%s.port", account.getProtocol()), account.getPort().toString()); + properties.put("mail.imap.starttls.enable", "true"); + + Session session = Session.getDefaultInstance(properties); + Store store; + try { + store = session.getStore(account.getProtocol()); + store.connect(account.getUserName(), account.getPassword()); + Folder rootFolder = store.getDefaultFolder(); + Folder[] folders = rootFolder.list(); + for (Folder value : folders) { + SideNavItem folder = addSubFolder(value); + folderList.addItem(folder); + } + log.info("{} folders found", folders.length); + Folder folder = store.getFolder("INBOX"); + IMAPFolder imapFolder = (IMAPFolder) folder; + imapFolder.open(Folder.READ_ONLY); + Message[] messages = imapFolder.getMessages(); + log.info("Folder {} opened", folder.getFullName()); + int messageCount = folder.getMessageCount(); + log.info("{} messages found", messages.length); + for (Message message : messages) { + log.info("Message {}: {}, {}, {}", message.getMessageNumber(), message.getSubject(), message.getReceivedDate(), message.getSentDate()); + //log.info("Message: {}", message.getAllRecipients()); + Address[] addresses; + printAddress(message.getRecipients(Message.RecipientType.TO)); + printAddress(message.getRecipients(Message.RecipientType.CC)); + printAddress(message.getRecipients(Message.RecipientType.BCC)); + } + store.close(); + log.info("Connection closed"); + } catch (NoSuchProviderException e) { + throw new RuntimeException(e); + } catch (MessagingException e) { + throw new RuntimeException(e); + } + } + + private void printAddress(Address[] addresses) { + if (addresses != null) { + for (Address address: addresses) { + log.info("Address {}: {}", address.getType(), address.toString()); + InternetAddress internetAddress = (InternetAddress)address; + log.info("InternetAddress {}: {}", internetAddress.getPersonal(), internetAddress.getAddress()); + } + } + } + + private SideNavItem addSubFolder(Folder mailFolder) throws MessagingException { + log.debug("addSubFolder: {}", mailFolder.getName()); + Span counter = new Span(String.valueOf(mailFolder.getMessageCount())); + counter.getElement().getThemeList().add("badge contrast pill"); + counter.getElement().setAttribute("aria-label", String.format("%d unread messages", mailFolder.getMessageCount())); + SideNavItem folder = new SideNavItem(mailFolder.getName()); + folder.setSuffixComponent(counter); + Folder[] folders = mailFolder.list(); + log.info("{} subfolders found", folders.length); + for (Folder value : folders) { + SideNavItem subFolder = addSubFolder(value); + folder.addItem(subFolder); + } + return folder; + } + + private void configureView() { + HorizontalLayout layoutMenubar = new HorizontalLayout(); + HorizontalLayout layoutRow2 = new HorizontalLayout(); + VerticalLayout layoutColumn2 = new VerticalLayout(); + VerticalLayout layoutColumn3 = new VerticalLayout(); + MessageList messageList = new MessageList(); + VerticalLayout layoutColumn4 = new VerticalLayout(); + Paragraph textMedium = new Paragraph(); + getContent().setWidth("100%"); + getContent().getStyle().set("flex-grow", "1"); + layoutMenubar.addClassName(Gap.MEDIUM); + layoutMenubar.setWidth("100%"); + layoutMenubar.setHeight("min-content"); + menuBar.setWidth("min-content"); + setMenuBarSampleData(menuBar); + layoutRow2.addClassName(Gap.MEDIUM); + layoutRow2.setWidth("100%"); + layoutRow2.getStyle().set("flex-grow", "1"); + layoutColumn2.getStyle().set("flex-grow", "1"); + accordion.setWidth("100%"); + setAccordionSampleData(accordion); + layoutColumn3.setHeightFull(); + layoutRow2.setFlexGrow(1.0, layoutColumn3); + layoutColumn3.setWidth("100%"); + layoutColumn3.getStyle().set("flex-grow", "1"); + layoutColumn3.setJustifyContentMode(JustifyContentMode.START); + layoutColumn3.setAlignItems(Alignment.START); + messageList.setWidth("100%"); + setMessageListSampleData(messageList); + layoutColumn4.setWidthFull(); + layoutColumn3.setFlexGrow(1.0, layoutColumn4); + layoutColumn4.setWidth("100%"); + layoutColumn4.getStyle().set("flex-grow", "1"); + textMedium.setText( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."); + textMedium.setWidth("100%"); + textMedium.getStyle().set("font-size", "var(--lumo-font-size-m)"); + getContent().add(layoutMenubar); + layoutMenubar.add(menuBar); + getContent().add(layoutRow2); + layoutRow2.add(layoutColumn2); + layoutColumn2.add(folderList); + layoutRow2.add(layoutColumn3); + layoutColumn3.add(messageList); + layoutColumn3.add(layoutColumn4); + layoutColumn4.add(textMedium); + } + + private void setMenuBarSampleData(MenuBar menuBar) { + menuBar.addItem("View"); + menuBar.addItem("Edit"); + menuBar.addItem("Share"); + menuBar.addItem("Move"); + } + + private void setAccordionSampleData(Accordion accordion) { + Span name = new Span("Sophia Williams"); + Span email = new Span("sophia.williams@company.com"); + Span phone = new Span("(501) 555-9128"); + VerticalLayout personalInformationLayout = new VerticalLayout(name, email, phone); + personalInformationLayout.setSpacing(false); + personalInformationLayout.setPadding(false); + accordion.add("Personal information", personalInformationLayout); + Span street = new Span("4027 Amber Lake Canyon"); + Span zipCode = new Span("72333-5884 Cozy Nook"); + Span city = new Span("Arkansas"); + VerticalLayout billingAddressLayout = new VerticalLayout(); + billingAddressLayout.setSpacing(false); + billingAddressLayout.setPadding(false); + billingAddressLayout.add(street, zipCode, city); + accordion.add("Billing address", billingAddressLayout); + Span cardBrand = new Span("Mastercard"); + Span cardNumber = new Span("1234 5678 9012 3456"); + Span expiryDate = new Span("Expires 06/21"); + VerticalLayout paymentLayout = new VerticalLayout(); + paymentLayout.setSpacing(false); + paymentLayout.setPadding(false); + paymentLayout.add(cardBrand, cardNumber, expiryDate); + accordion.add("Payment", paymentLayout); + } + + private void setMessageListSampleData(MessageList messageList) { + MessageListItem message1 = new MessageListItem("Nature does not hurry, yet everything gets accomplished.", + LocalDateTime.now().minusDays(1).toInstant(ZoneOffset.UTC), "Matt Mambo"); + message1.setUserColorIndex(1); + MessageListItem message2 = new MessageListItem( + "Using your talent, hobby or profession in a way that makes you contribute with something good to this world is truly the way to go.", + LocalDateTime.now().minusMinutes(55).toInstant(ZoneOffset.UTC), "Linsey Listy"); + message2.setUserColorIndex(2); + messageList.setItems(message1, message2); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/media/MediaConstants.java b/springboot/src/main/java/de/thpeetz/kontor/media/MediaConstants.java new file mode 100644 index 0000000..866f7b3 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/media/MediaConstants.java @@ -0,0 +1,35 @@ +package de.thpeetz.kontor.media; + +import com.vaadin.flow.component.icon.VaadinIcon; +import com.vaadin.flow.component.sidenav.SideNavItem; +import de.thpeetz.kontor.media.views.MediaArticleView; +import de.thpeetz.kontor.media.views.MediaFileView; +import de.thpeetz.kontor.media.views.MediaVideoView; + +import java.util.ArrayList; + +public class MediaConstants { + + public static final String MEDIA = "Media"; + public static final String MEDIAFILE_ROUTE = "media/mediafile"; + public static final String MEDIAVIDEO_ROUTE = "media/mediavideo"; + public static final String MEDIAARTICLE_ROUTE = "media/mediaarticle"; + public static final String MEDIA_ROLE = "ROLE_MEDIA"; + private static final String MEDIAFILE = "Media Files"; + private static final String MEDIAVIDEO = "Media Videos"; + private static final String MEDIAARTICLE = "Media Article"; + + public static SideNavItem getMediaNavigation(ArrayList roles) { + SideNavItem media = new SideNavItem(MEDIA, MEDIAFILE_ROUTE, VaadinIcon.VIMEO.create()); + media.addItem(new SideNavItem(MEDIAVIDEO, MediaVideoView.class)); + media.addItem(new SideNavItem(MEDIAARTICLE, MediaArticleView.class)); + if (roles.contains(MEDIA_ROLE)) { + media.addItem(new SideNavItem(MEDIAFILE, MediaFileView.class)); + } + return media; + } + + private MediaConstants() { + // private constructor to hide the implicit public one + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/media/SetupModuleMedia.java b/springboot/src/main/java/de/thpeetz/kontor/media/SetupModuleMedia.java new file mode 100644 index 0000000..c9ce7a1 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/media/SetupModuleMedia.java @@ -0,0 +1,39 @@ +package de.thpeetz.kontor.media; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.stereotype.Component; + +import de.thpeetz.kontor.admin.services.AdminService; +import de.thpeetz.kontor.admin.services.ModuleService; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +public class SetupModuleMedia implements ApplicationListener { + + boolean alreadySetup = false; + + @Autowired + private AdminService adminService; + + @Autowired + private ModuleService moduleService; + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + adminService.addRole(MediaConstants.MEDIA_ROLE); + if (alreadySetup) { + log.info("SetupModuleMedia already executed, skipping"); + return; + } + if (!moduleService.importData(MediaConstants.MEDIA)) { + log.info("Module media should not setup data"); + return; + } + + log.info("Set up Media data"); + moduleService.setDataImported(MediaConstants.MEDIA); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/media/data/MediaArticle.java b/springboot/src/main/java/de/thpeetz/kontor/media/data/MediaArticle.java new file mode 100644 index 0000000..c664a3a --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/media/data/MediaArticle.java @@ -0,0 +1,29 @@ +package de.thpeetz.kontor.media.data; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.annotation.Nullable; +import jakarta.persistence.Entity; +import jakarta.persistence.Index; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import jakarta.validation.constraints.NotEmpty; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Getter +@Setter +@Entity +@Table(indexes = @Index(columnList = "url"), uniqueConstraints = @UniqueConstraint(columnNames = {"url"})) +public class MediaArticle extends AbstractEntity { + + @NotEmpty + private String url; + + private boolean review; + + @Nullable + private String title; +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/media/data/MediaArticleRepository.java b/springboot/src/main/java/de/thpeetz/kontor/media/data/MediaArticleRepository.java new file mode 100644 index 0000000..a25702e --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/media/data/MediaArticleRepository.java @@ -0,0 +1,13 @@ +package de.thpeetz.kontor.media.data; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface MediaArticleRepository extends JpaRepository { + @Query("select m from MediaArticle m " + + "where lower(m.url) like lower(concat('%', :searchTerm, '%')) or lower(m.title) like lower(concat('%', :searchTerm, '%'))") + List search(@Param("searchTerm") String searchTerm); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/media/data/MediaFile.java b/springboot/src/main/java/de/thpeetz/kontor/media/data/MediaFile.java new file mode 100644 index 0000000..fa7b934 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/media/data/MediaFile.java @@ -0,0 +1,40 @@ +package de.thpeetz.kontor.media.data; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.annotation.Nullable; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +@Entity +@Table(uniqueConstraints = { @UniqueConstraint(columnNames = { "url" }) }) +public class MediaFile extends AbstractEntity { + + @Nullable + private String url; + + private boolean review; + + private boolean shouldDownload; + + @Nullable + private String title; + + @Nullable + private String cloudLink; + + @Nullable + private String fileName; + + @Nullable + private String path; + +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/media/data/MediaFileRepository.java b/springboot/src/main/java/de/thpeetz/kontor/media/data/MediaFileRepository.java new file mode 100644 index 0000000..7db70e1 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/media/data/MediaFileRepository.java @@ -0,0 +1,14 @@ +package de.thpeetz.kontor.media.data; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface MediaFileRepository extends JpaRepository { + @Query("select m from MediaFile m " + + "where lower(m.url) like lower(concat('%', :searchTerm, '%')) or lower(m.title) like lower(concat('%', :searchTerm, '%'))") + List search(@Param("searchTerm") String searchTerm); + +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/media/data/MediaLink.java b/springboot/src/main/java/de/thpeetz/kontor/media/data/MediaLink.java new file mode 100644 index 0000000..c70db48 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/media/data/MediaLink.java @@ -0,0 +1,40 @@ +package de.thpeetz.kontor.media.data; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.annotation.Nullable; +import jakarta.persistence.Entity; +import jakarta.persistence.Index; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import jakarta.validation.constraints.NotEmpty; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + + +@Slf4j +@Getter +@Setter +@Entity +@Table(indexes = @Index(columnList = "url"), uniqueConstraints = @UniqueConstraint(columnNames = {"url"})) +public class MediaLink extends AbstractEntity { + + @NotEmpty + private String url; + + private String title; + + private boolean review; + + @OneToOne + @JoinColumn(name = "video_id", referencedColumnName = "id") + @Nullable + private MediaVideo mediaVideo; + + @OneToOne + @JoinColumn(name = "article_id", referencedColumnName = "id") + @Nullable + private MediaArticle mediaArticle; +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/media/data/MediaVideo.java b/springboot/src/main/java/de/thpeetz/kontor/media/data/MediaVideo.java new file mode 100644 index 0000000..d093eb1 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/media/data/MediaVideo.java @@ -0,0 +1,40 @@ +package de.thpeetz.kontor.media.data; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.annotation.Nullable; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +@Entity +@Table(uniqueConstraints = { @UniqueConstraint(columnNames = { "url" }) }) +public class MediaVideo extends AbstractEntity { + + @Nullable + private String url; + + private boolean review; + + private boolean shouldDownload; + + @Nullable + private String title; + + @Nullable + private String cloudLink; + + @Nullable + private String fileName; + + @Nullable + private String path; + +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/media/data/MediaVideoRepository.java b/springboot/src/main/java/de/thpeetz/kontor/media/data/MediaVideoRepository.java new file mode 100644 index 0000000..d898186 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/media/data/MediaVideoRepository.java @@ -0,0 +1,15 @@ +package de.thpeetz.kontor.media.data; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface MediaVideoRepository extends JpaRepository { + + @Query("select m from MediaVideo m " + + "where lower(m.url) like lower(concat('%', :searchTerm, '%')) or lower(m.title) like lower(concat('%', :searchTerm, '%'))") + List search(@Param("searchTerm") String searchTerm); + +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/media/services/MediaArticleService.java b/springboot/src/main/java/de/thpeetz/kontor/media/services/MediaArticleService.java new file mode 100644 index 0000000..fc78d5d --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/media/services/MediaArticleService.java @@ -0,0 +1,42 @@ +package de.thpeetz.kontor.media.services; + +import de.thpeetz.kontor.media.data.MediaArticle; +import de.thpeetz.kontor.media.data.MediaArticleRepository; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +public class MediaArticleService { + + private final MediaArticleRepository mediaArticleRepository; + + public MediaArticleService(MediaArticleRepository mediaArticleRepository) { + this.mediaArticleRepository = mediaArticleRepository; + } + + public List findAllMediaArticles(String stringFilter) { + if (stringFilter == null || stringFilter.isEmpty()) { + log.info("Found {} entries", mediaArticleRepository.count()); + return mediaArticleRepository.findAll(); + } else { + List results = mediaArticleRepository.search(stringFilter); + log.info("Found {} entries", results.size()); + return results; + } + } + + public void saveMediaArticle(MediaArticle mediaArticle) { + if (mediaArticle == null) { + log.warn("MediaArticle is null. Are you sure you have connected your form to the application?"); + return; + } + mediaArticleRepository.save(mediaArticle); + } + + public void deleteMediaArticle(MediaArticle mediaArticle) { + mediaArticleRepository.delete(mediaArticle); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/media/services/MediaFileService.java b/springboot/src/main/java/de/thpeetz/kontor/media/services/MediaFileService.java new file mode 100644 index 0000000..ffb0a24 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/media/services/MediaFileService.java @@ -0,0 +1,42 @@ +package de.thpeetz.kontor.media.services; + +import de.thpeetz.kontor.media.data.MediaFile; +import de.thpeetz.kontor.media.data.MediaFileRepository; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +public class MediaFileService { + + private final MediaFileRepository mediaFileRepository; + + public MediaFileService(MediaFileRepository mediaFileRepository) { + this.mediaFileRepository = mediaFileRepository; + } + + public List findAllMediaFiles(String stringFilter) { + if (stringFilter == null || stringFilter.isEmpty()) { + log.debug("Found " + mediaFileRepository.count()+ " entries"); + return mediaFileRepository.findAll(); + } else { + List results = mediaFileRepository.search(stringFilter); + log.debug("Found " + results.size() + " entries"); + return results; + } + } + + public void saveMediaFile(MediaFile mediaFile) { + if (mediaFile == null) { + log.warn("MediaFile is null. Are you sure you have connected your form to the application?"); + return; + } + mediaFileRepository.save(mediaFile); + } + + public void deleteMediaFile(MediaFile mediaFile) { + mediaFileRepository.delete(mediaFile); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/media/services/MediaVideoService.java b/springboot/src/main/java/de/thpeetz/kontor/media/services/MediaVideoService.java new file mode 100644 index 0000000..61299fd --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/media/services/MediaVideoService.java @@ -0,0 +1,39 @@ +package de.thpeetz.kontor.media.services; + +import de.thpeetz.kontor.media.data.MediaVideo; +import de.thpeetz.kontor.media.data.MediaVideoRepository; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +public class MediaVideoService { + + private final MediaVideoRepository mediaVideoRepository; + + public MediaVideoService(MediaVideoRepository mediaVideoRepository) { + this.mediaVideoRepository = mediaVideoRepository; + } + + public List findAllMediaVideos(String stringFilter) { + if (stringFilter == null || stringFilter.isEmpty()) { + return mediaVideoRepository.findAll(); + } else { + return mediaVideoRepository.search(stringFilter); + } + } + + public void saveMediaVideo(MediaVideo mediaVideo) { + if (mediaVideo == null) { + log.warn("MediaFile is null. Are you sure you have connected your form to the application?"); + return; + } + mediaVideoRepository.save(mediaVideo); + } + + public void deleteMediaVideo(MediaVideo mediaVideo) { + mediaVideoRepository.delete(mediaVideo); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/media/views/MediaArticleForm.java b/springboot/src/main/java/de/thpeetz/kontor/media/views/MediaArticleForm.java new file mode 100644 index 0000000..d658e4e --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/media/views/MediaArticleForm.java @@ -0,0 +1,108 @@ +package de.thpeetz.kontor.media.views; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +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.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 de.thpeetz.kontor.media.data.MediaArticle; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class MediaArticleForm extends FormLayout { + + TextField url = new TextField("URL"); + TextField title = new TextField("Title"); + Checkbox review = new Checkbox("Review"); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(MediaArticle.class); + + public MediaArticleForm() { + addClassName("mediaarticle-form"); + binder.bindInstanceFields(this); + add(url, 2); + add(title, 2); + add(review, createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new MediaArticleForm.DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new MediaArticleForm.CloseEvent(this))); + + binder.addStatusChangeListener(event -> { + save.setEnabled(event.getBinder().isValid()); + }); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new MediaArticleForm.SaveEvent(this, binder.getBean())); + } + } + + public void setMediaArticle(MediaArticle mediaArticle) { + binder.setBean(mediaArticle); + } + + public abstract static class MediaArticleFormEvent extends ComponentEvent { + private MediaArticle mediaArticle; + + protected MediaArticleFormEvent(MediaArticleForm source, MediaArticle mediaArticle) { + super(source, false); + this.mediaArticle = mediaArticle; + } + + public MediaArticle getMediaArticle() { + return mediaArticle; + } + } + + public static class SaveEvent extends MediaArticleForm.MediaArticleFormEvent { + SaveEvent(MediaArticleForm source, MediaArticle mediaArticle) { + super(source, mediaArticle); + } + } + + public static class DeleteEvent extends MediaArticleForm.MediaArticleFormEvent { + DeleteEvent(MediaArticleForm source, MediaArticle mediaArticle) { + super(source, mediaArticle); + } + } + + public static class CloseEvent extends MediaArticleForm.MediaArticleFormEvent { + CloseEvent(MediaArticleForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(MediaArticleForm.DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(MediaArticleForm.SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(MediaArticleForm.CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/media/views/MediaArticleView.java b/springboot/src/main/java/de/thpeetz/kontor/media/views/MediaArticleView.java new file mode 100644 index 0000000..67079d4 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/media/views/MediaArticleView.java @@ -0,0 +1,171 @@ +package de.thpeetz.kontor.media.views; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.contextmenu.ContextMenu; +import com.vaadin.flow.component.contextmenu.MenuItem; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.icon.Icon; +import com.vaadin.flow.component.icon.VaadinIcon; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; +import de.thpeetz.kontor.common.views.MainLayout; +import de.thpeetz.kontor.media.MediaConstants; +import de.thpeetz.kontor.media.data.MediaArticle; +import de.thpeetz.kontor.media.services.MediaArticleService; +import jakarta.annotation.security.PermitAll; +import lombok.Getter; +import org.springframework.context.annotation.Scope; + +@SpringComponent +@Scope("prototype") +@PermitAll +@Route(value = MediaConstants.MEDIAARTICLE_ROUTE, layout = MainLayout.class) +@PageTitle("MediaArticle | Media | Kontor") +public class MediaArticleView extends VerticalLayout { + + @Getter + Grid grid = new Grid<>(MediaArticle.class, false); + Grid.Column idColumn = grid.addColumn(MediaArticle::getId).setHeader("ID").setResizable(true); + Grid.Column urlColumn = grid.addColumn(MediaArticle::getUrl).setHeader("URL").setResizable(true).setSortable(true); + Grid.Column titleColumn = grid.addColumn(MediaArticle::getTitle).setHeader("Titel").setResizable(true).setSortable(true); + Grid.Column reviewColumn = grid.addComponentColumn(mediaArticle -> createStatusIcon(mediaArticle.isReview())) + .setHeader("Überprüfung") + .setWidth("6rem"); + TextField searchField = new TextField(); + @Getter + MediaArticleForm form; + MediaArticleService service; + + private static class ColumnToggleContextMenu extends ContextMenu { + public ColumnToggleContextMenu(Component target) { + super(target); + setOpenOnClick(true); + } + + void addColumnToggleItem(String label, Grid.Column column) { + MenuItem menuItem = this.addItem(label, e -> { + column.setVisible(e.getSource().isChecked()); + }); + menuItem.setCheckable(true); + menuItem.setChecked(column.isVisible()); + menuItem.setKeepOpen(true); + } + } + + public MediaArticleView(MediaArticleService service) { + this.service = service; + addClassName("mediaarticle-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + private void configureGrid() { + grid.addClassName("mediaarticle-grid"); + grid.setSizeFull(); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + idColumn.setVisible(false); + grid.asSingleSelect().addValueChangeListener(event -> editMediaArticle(event.getValue())); + } + + private void configureForm() { + form = new MediaArticleForm(); + form.setWidth("75em"); + form.setVisible(false); + form.addSaveListener(this::saveMediaArticle); + form.addDeleteListener(this::deleteMediaArticle); + form.addCloseListener(e -> closeEditor()); + } + + private void saveMediaArticle(MediaArticleForm.SaveEvent event) { + service.saveMediaArticle(event.getMediaArticle()); + updateList(); + closeEditor(); + } + + private Icon createStatusIcon(boolean status) { + Icon icon; + if (status) { + icon = VaadinIcon.CHECK.create(); + icon.getElement().getThemeList().add("badge success"); + } else { + icon = VaadinIcon.CLOSE_SMALL.create(); + icon.getElement().getThemeList().add("badge error"); + } + icon.getStyle().set("padding", "var(--lumo-space-xs"); + return icon; + } + + private void deleteMediaArticle(MediaArticleForm.DeleteEvent event) { + service.deleteMediaArticle(event.getMediaArticle()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + searchField.setPlaceholder("Search"); + searchField.setClearButtonVisible(true); + searchField.setPrefixComponent(new Icon(VaadinIcon.SEARCH)); + searchField.setValueChangeMode(ValueChangeMode.EAGER); + searchField.addValueChangeListener(e -> updateList()); + + Button addMediaArticleButton = new Button("Add MediaArticle"); + addMediaArticleButton.addClickListener(click -> addMediaArticle()); + + Button menuButton = new Button("Show/Hide Columns"); + menuButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + MediaArticleView.ColumnToggleContextMenu columnToggleContextMenu = new MediaArticleView.ColumnToggleContextMenu(menuButton); + columnToggleContextMenu.addColumnToggleItem("ID", idColumn); + columnToggleContextMenu.addColumnToggleItem("URL", urlColumn); + columnToggleContextMenu.addColumnToggleItem("Titel", titleColumn); + columnToggleContextMenu.addColumnToggleItem("Überprüfung", reviewColumn); + HorizontalLayout toolbar = new HorizontalLayout(searchField, addMediaArticleButton, menuButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editMediaArticle(MediaArticle mediaArticle) { + if (mediaArticle == null) { + closeEditor(); + } else { + form.setMediaArticle(mediaArticle); + form.setVisible(true); + addClassName("editing"); + } + } + + private void closeEditor() { + form.setMediaArticle(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addMediaArticle() { + grid.asSingleSelect().clear(); + editMediaArticle(new MediaArticle()); + } + + public void updateList() { + grid.setItems(service.findAllMediaArticles(searchField.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/media/views/MediaFileForm.java b/springboot/src/main/java/de/thpeetz/kontor/media/views/MediaFileForm.java new file mode 100644 index 0000000..b1042fa --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/media/views/MediaFileForm.java @@ -0,0 +1,113 @@ +package de.thpeetz.kontor.media.views; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +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.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 de.thpeetz.kontor.media.data.MediaFile; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class MediaFileForm extends FormLayout { + + TextField id = new TextField("ID"); + TextField url = new TextField("URL"); + TextField title = new TextField("Title"); + TextField fileName = new TextField("Dateiname"); + TextField cloudLink = new TextField("Cloud Link"); + Checkbox review = new Checkbox("Review"); + Checkbox shouldDownload = new Checkbox("Download"); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(MediaFile.class); + + public MediaFileForm() { + addClassName("mediafile-form"); + binder.bindInstanceFields(this); + id.setReadOnly(true); + add(id, 2); + add(url, 2); + add(title, 2); + add(fileName, 2); + add(cloudLink, 2); + add(review, shouldDownload, createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new CloseEvent(this))); + + binder.addStatusChangeListener(event -> save.setEnabled(event.getBinder().isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new SaveEvent(this, binder.getBean())); + } + } + + public void setMediaFile(MediaFile mediaFile) { + binder.setBean(mediaFile); + } + + @Getter + public abstract static class MediaFileFormEvent extends ComponentEvent { + private final MediaFile mediaFile; + + protected MediaFileFormEvent(MediaFileForm source, MediaFile mediaFile) { + super(source, false); + this.mediaFile = mediaFile; + } + + } + + public static class SaveEvent extends MediaFileFormEvent { + SaveEvent(MediaFileForm source, MediaFile mediaFile) { + super(source, mediaFile); + } + } + + public static class DeleteEvent extends MediaFileFormEvent { + DeleteEvent(MediaFileForm source, MediaFile mediaFile) { + super(source, mediaFile); + } + } + + public static class CloseEvent extends MediaFileFormEvent { + CloseEvent(MediaFileForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/media/views/MediaFileView.java b/springboot/src/main/java/de/thpeetz/kontor/media/views/MediaFileView.java new file mode 100644 index 0000000..0784b8d --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/media/views/MediaFileView.java @@ -0,0 +1,186 @@ +package de.thpeetz.kontor.media.views; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.contextmenu.ContextMenu; +import com.vaadin.flow.component.contextmenu.MenuItem; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.icon.Icon; +import com.vaadin.flow.component.icon.VaadinIcon; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; +import de.thpeetz.kontor.common.views.MainLayout; +import de.thpeetz.kontor.media.data.MediaFile; +import de.thpeetz.kontor.media.services.MediaFileService; +import jakarta.annotation.security.RolesAllowed; +import lombok.Getter; +import org.springframework.context.annotation.Scope; + +@SpringComponent +@Scope("prototype") +@RolesAllowed("MEDIA") +@Route(value = "media/mediafile", layout = MainLayout.class) +@PageTitle("MediaFile | Media | Kontor") +public class MediaFileView extends VerticalLayout { + + @Getter + Grid grid = new Grid<>(MediaFile.class, false); + Grid.Column idColumn = grid.addColumn(MediaFile::getId) + .setHeader("ID").setResizable(true).setSortable(true); + Grid.Column createdColumn = grid.addColumn(MediaFile::getCreatedDate) + .setHeader("Erstellt").setResizable(true).setSortable(true); + Grid.Column modifiedColumn = grid.addColumn(MediaFile::getLastModifiedDate) + .setHeader("Geändert").setResizable(true).setSortable(true); + Grid.Column urlColumn = grid.addColumn(MediaFile::getUrl) + .setHeader("URL").setResizable(true).setSortable(true); + Grid.Column titleColumn = grid.addColumn(MediaFile::getTitle) + .setHeader("Titel").setResizable(true).setSortable(true); + Grid.Column fileNameColumn = grid.addColumn(MediaFile::getFileName) + .setHeader("Dateiname").setResizable(true).setSortable(true); + Grid.Column cloudLinkColumn = grid.addColumn(MediaFile::getCloudLink) + .setHeader("Cloud Link").setResizable(true).setSortable(true); + Grid.Column reviewColumn = grid.addComponentColumn(mediafile -> createStatusIcon(mediafile.isReview())). + setHeader("Überprüfung").setWidth("6rem").setSortable(true); + Grid.Column shouldDownloadColumn = grid.addComponentColumn(mediafile -> createStatusIcon(mediafile.isShouldDownload())). + setHeader("Download?").setWidth("6rem").setSortable(true); + TextField searchField = new TextField(); + @Getter + MediaFileForm form; + MediaFileService service; + + private static class ColumnToggleContextMenu extends ContextMenu { + public ColumnToggleContextMenu(Component target) { + super(target); + setOpenOnClick(true); + } + + void addColumnToggleItem(String label, Grid.Column column) { + MenuItem menuItem = this.addItem(label, e -> { + column.setVisible(e.getSource().isChecked()); + }); + menuItem.setCheckable(true); + menuItem.setChecked(column.isVisible()); + menuItem.setKeepOpen(true); + } + } + + public MediaFileView(MediaFileService service) { + this.service = service; + addClassName("mediafile-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + private void configureGrid() { + grid.addClassName("mediafile-grid"); + grid.setSizeFull(); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + //idColumn.setVisible(false); + grid.asSingleSelect().addValueChangeListener(event -> editMediaFile(event.getValue())); + } + + private void configureForm() { + form = new MediaFileForm(); + form.setWidth("75em"); + form.setVisible(false); + form.addSaveListener(this::saveMediaFile); + form.addDeleteListener(this::deleteMediaFile); + form.addCloseListener(e -> closeEditor()); + } + + private void saveMediaFile(MediaFileForm.SaveEvent event) { + service.saveMediaFile(event.getMediaFile()); + updateList(); + closeEditor(); + } + + private Icon createStatusIcon(boolean status) { + Icon icon; + if (status) { + icon = VaadinIcon.CHECK.create(); + icon.getElement().getThemeList().add("badge success"); + } else { + icon = VaadinIcon.CLOSE_SMALL.create(); + icon.getElement().getThemeList().add("badge error"); + } + icon.getStyle().set("padding", "var(--lumo-space-xs"); + return icon; + } + + private void deleteMediaFile(MediaFileForm.DeleteEvent event) { + service.deleteMediaFile(event.getMediaFile()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + searchField.setPlaceholder("Search"); + searchField.setClearButtonVisible(true); + searchField.setPrefixComponent(new Icon(VaadinIcon.SEARCH)); + searchField.setValueChangeMode(ValueChangeMode.EAGER); + searchField.addValueChangeListener(e -> updateList()); + + Button addMediaFileButton = new Button("Add MediaFile"); + addMediaFileButton.addClickListener(click -> addMediaFile()); + + Button menuButton = new Button("Show/Hide Columns"); + menuButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + ColumnToggleContextMenu columnToggleContextMenu = new ColumnToggleContextMenu(menuButton); + columnToggleContextMenu.addColumnToggleItem("ID", idColumn); + columnToggleContextMenu.addColumnToggleItem("Erstellt", createdColumn); + columnToggleContextMenu.addColumnToggleItem("Geändert", modifiedColumn); + columnToggleContextMenu.addColumnToggleItem("URL", urlColumn); + columnToggleContextMenu.addColumnToggleItem("Titel", titleColumn); + columnToggleContextMenu.addColumnToggleItem("Dateiname", fileNameColumn); + columnToggleContextMenu.addColumnToggleItem("Cloud Link", cloudLinkColumn); + columnToggleContextMenu.addColumnToggleItem("Überprüfung", reviewColumn); + columnToggleContextMenu.addColumnToggleItem("Download?", shouldDownloadColumn); + HorizontalLayout toolbar = new HorizontalLayout(searchField, addMediaFileButton, menuButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editMediaFile(MediaFile mediaFile) { + if (mediaFile == null) { + closeEditor(); + } else { + form.setMediaFile(mediaFile); + form.setVisible(true); + addClassName("editing"); + } + } + + private void closeEditor() { + form.setMediaFile(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addMediaFile() { + grid.asSingleSelect().clear(); + editMediaFile(new MediaFile()); + } + + public void updateList() { + grid.setItems(service.findAllMediaFiles(searchField.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/media/views/MediaVideoForm.java b/springboot/src/main/java/de/thpeetz/kontor/media/views/MediaVideoForm.java new file mode 100644 index 0000000..dbc8d47 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/media/views/MediaVideoForm.java @@ -0,0 +1,113 @@ +package de.thpeetz.kontor.media.views; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +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.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 de.thpeetz.kontor.media.data.MediaVideo; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class MediaVideoForm extends FormLayout { + + TextField url = new TextField("URL"); + TextField title = new TextField("Title"); + TextField fileName = new TextField("Dateiname"); + TextField cloudLink = new TextField("Cloud Link"); + Checkbox review = new Checkbox("Review"); + Checkbox shouldDownload = new Checkbox("Download"); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(MediaVideo.class); + + public MediaVideoForm() { + addClassName("mediavideo-form"); + binder.bindInstanceFields(this); + add(url, 2); + add(title, 2); + add(fileName, 2); + add(cloudLink, 2); + add(review, shouldDownload, createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new CloseEvent(this))); + + binder.addStatusChangeListener(event -> { + save.setEnabled(event.getBinder().isValid()); + }); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new SaveEvent(this, binder.getBean())); + } + } + + public void setMediaVideo(MediaVideo mediaVideo) { + binder.setBean(mediaVideo); + } + + public abstract static class MediaVideoFormEvent extends ComponentEvent { + private MediaVideo mediaVideo; + + protected MediaVideoFormEvent(MediaVideoForm source, MediaVideo mediaVideo) { + super(source, false); + this.mediaVideo = mediaVideo; + } + + public MediaVideo getMediaVideo() { + return mediaVideo; + } + } + + public static class SaveEvent extends MediaVideoFormEvent { + SaveEvent(MediaVideoForm source, MediaVideo mediaVideo) { + super(source, mediaVideo); + } + } + + public static class DeleteEvent extends MediaVideoFormEvent { + DeleteEvent(MediaVideoForm source, MediaVideo mediaVideo) { + super(source, mediaVideo); + } + } + + public static class CloseEvent extends MediaVideoFormEvent { + CloseEvent(MediaVideoForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/media/views/MediaVideoView.java b/springboot/src/main/java/de/thpeetz/kontor/media/views/MediaVideoView.java new file mode 100644 index 0000000..8b0c131 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/media/views/MediaVideoView.java @@ -0,0 +1,178 @@ +package de.thpeetz.kontor.media.views; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.contextmenu.ContextMenu; +import com.vaadin.flow.component.contextmenu.MenuItem; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.icon.Icon; +import com.vaadin.flow.component.icon.VaadinIcon; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; +import de.thpeetz.kontor.common.views.MainLayout; +import de.thpeetz.kontor.media.MediaConstants; +import de.thpeetz.kontor.media.data.MediaVideo; +import de.thpeetz.kontor.media.services.MediaVideoService; +import jakarta.annotation.security.PermitAll; +import lombok.Getter; +import org.springframework.context.annotation.Scope; + +@SpringComponent +@Scope("prototype") +@PermitAll +@Route(value = MediaConstants.MEDIAVIDEO_ROUTE, layout = MainLayout.class) +@PageTitle("MediaVideo | Media | Kontor") +public class MediaVideoView extends VerticalLayout { + + @Getter + Grid grid = new Grid<>(MediaVideo.class, false); + Grid.Column idColumn = grid.addColumn(MediaVideo::getId).setHeader("ID").setResizable(true); + Grid.Column urlColumn = grid.addColumn(MediaVideo::getUrl).setHeader("URL").setResizable(true).setSortable(true); + Grid.Column titleColumn = grid.addColumn(MediaVideo::getTitle).setHeader("Titel").setResizable(true).setSortable(true); + Grid.Column fileNameColumn = grid.addColumn(MediaVideo::getFileName).setHeader("Dateiname").setResizable(true).setSortable(true); + Grid.Column cloudLinkColumn = grid.addColumn(MediaVideo::getCloudLink).setHeader("Cloud Link").setResizable(true).setSortable(true); + Grid.Column reviewColumn = grid.addComponentColumn(mediaVideo -> createStatusIcon(mediaVideo.isReview())) + .setHeader("Überprüfung") + .setWidth("6rem"); + Grid.Column shouldDownloadColumn = grid.addComponentColumn(mediaVideo -> createStatusIcon(mediaVideo.isShouldDownload())) + .setHeader("Download?") + .setWidth("6rem"); + TextField searchField = new TextField(); + @Getter + MediaVideoForm form; + MediaVideoService service; + + private static class ColumnToggleContextMenu extends ContextMenu { + public ColumnToggleContextMenu(Component target) { + super(target); + setOpenOnClick(true); + } + + void addColumnToggleItem(String label, Grid.Column column) { + MenuItem menuItem = this.addItem(label, e -> { + column.setVisible(e.getSource().isChecked()); + }); + menuItem.setCheckable(true); + menuItem.setChecked(column.isVisible()); + menuItem.setKeepOpen(true); + } + } + + public MediaVideoView(MediaVideoService service) { + this.service = service; + addClassName("mediavideo-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + private void configureGrid() { + grid.addClassName("mediavideo-grid"); + grid.setSizeFull(); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + idColumn.setVisible(false); + grid.asSingleSelect().addValueChangeListener(event -> editMediaVideo(event.getValue())); + } + + private void configureForm() { + form = new MediaVideoForm(); + form.setWidth("75em"); + form.setVisible(false); + form.addSaveListener(this::saveMediaVideo); + form.addDeleteListener(this::deleteMediaVideo); + form.addCloseListener(e -> closeEditor()); + } + + private void saveMediaVideo(MediaVideoForm.SaveEvent event) { + service.saveMediaVideo(event.getMediaVideo()); + updateList(); + closeEditor(); + } + + private Icon createStatusIcon(boolean status) { + Icon icon; + if (status) { + icon = VaadinIcon.CHECK.create(); + icon.getElement().getThemeList().add("badge success"); + } else { + icon = VaadinIcon.CLOSE_SMALL.create(); + icon.getElement().getThemeList().add("badge error"); + } + icon.getStyle().set("padding", "var(--lumo-space-xs"); + return icon; + } + + private void deleteMediaVideo(MediaVideoForm.DeleteEvent event) { + service.deleteMediaVideo(event.getMediaVideo()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + searchField.setPlaceholder("Search"); + searchField.setClearButtonVisible(true); + searchField.setPrefixComponent(new Icon(VaadinIcon.SEARCH)); + searchField.setValueChangeMode(ValueChangeMode.EAGER); + searchField.addValueChangeListener(e -> updateList()); + + Button addMediaVideoButton = new Button("Add MediaVideo"); + addMediaVideoButton.addClickListener(click -> addMediaVideo()); + + Button menuButton = new Button("Show/Hide Columns"); + menuButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + MediaVideoView.ColumnToggleContextMenu columnToggleContextMenu = new MediaVideoView.ColumnToggleContextMenu(menuButton); + columnToggleContextMenu.addColumnToggleItem("ID", idColumn); + columnToggleContextMenu.addColumnToggleItem("URL", urlColumn); + columnToggleContextMenu.addColumnToggleItem("Titel", titleColumn); + columnToggleContextMenu.addColumnToggleItem("Dateiname", fileNameColumn); + columnToggleContextMenu.addColumnToggleItem("Cloud Link", cloudLinkColumn); + columnToggleContextMenu.addColumnToggleItem("Überprüfung", reviewColumn); + columnToggleContextMenu.addColumnToggleItem("Download?", shouldDownloadColumn); + HorizontalLayout toolbar = new HorizontalLayout(searchField, addMediaVideoButton, menuButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editMediaVideo(MediaVideo mediaVideo) { + if (mediaVideo == null) { + closeEditor(); + } else { + form.setMediaVideo(mediaVideo); + form.setVisible(true); + addClassName("editing"); + } + } + + private void closeEditor() { + form.setMediaVideo(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addMediaVideo() { + grid.asSingleSelect().clear(); + editMediaVideo(new MediaVideo()); + } + + public void updateList() { + grid.setItems(service.findAllMediaVideos(searchField.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/security/SecurityConfig.java b/springboot/src/main/java/de/thpeetz/kontor/security/SecurityConfig.java new file mode 100644 index 0000000..ae77838 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/security/SecurityConfig.java @@ -0,0 +1,59 @@ +package de.thpeetz.kontor.security; + +import com.vaadin.flow.spring.security.VaadinWebSecurity; + +import de.thpeetz.kontor.admin.services.KontorUserDetailsService; +import de.thpeetz.kontor.admin.views.LoginView; + +import java.util.Base64; + +import javax.crypto.spec.SecretKeySpec; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +@EnableWebSecurity +@Configuration +public class SecurityConfig extends VaadinWebSecurity { + + public static final String LOGOUT_URL = "/"; + + @Value("${jwt.auth.secret}") + private String authSecret; + + @Autowired + private KontorUserDetailsService userDetailsService; + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.authorizeHttpRequests(auth -> auth.requestMatchers( + AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/images/*.png")).permitAll()); + super.configure(http); + setLoginView(http, LoginView.class); + setStatelessAuthentication(http, new SecretKeySpec(Base64.getDecoder().decode(authSecret), JwsAlgorithms.HS256), + "de.thpeetz.kontor"); + } + + @Bean + public DaoAuthenticationProvider authenticationProvider() { + final DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); + authProvider.setUserDetailsService(userDetailsService); + authProvider.setPasswordEncoder(encoder()); + return authProvider; + } + + @Bean + public PasswordEncoder encoder() { + return new BCryptPasswordEncoder(11); + } +} \ No newline at end of file diff --git a/springboot/src/main/java/de/thpeetz/kontor/security/SecurityService.java b/springboot/src/main/java/de/thpeetz/kontor/security/SecurityService.java new file mode 100644 index 0000000..4d17d31 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/security/SecurityService.java @@ -0,0 +1,105 @@ +package de.thpeetz.kontor.security; + +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; +import org.springframework.stereotype.Component; + +import com.vaadin.flow.component.UI; +import com.vaadin.flow.server.VaadinServletRequest; +import com.vaadin.flow.server.VaadinServletResponse; +import com.vaadin.flow.spring.security.AuthenticationContext; + +import de.thpeetz.kontor.admin.services.KontorUserDetailsService; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Component +@Service +public class SecurityService { + + private static final String LOGOUT_SUCCESS_URL = "/"; + + private final AuthenticationContext authenticationContext; + + @Autowired + private KontorUserDetailsService userService; + + public SecurityService(AuthenticationContext authenticationContext) { + this.authenticationContext = authenticationContext; + } + + public Optional getAuthenticatedUser() { + String name = SecurityContextHolder.getContext().getAuthentication().getName(); + log.info("getAuthenticatedUser: {}", name); + + return Optional.ofNullable(userService.loadUserByUsername(name)); + } + + /*public static UserDetails getAuthenticatedUser() { + SecurityContext context = SecurityContextHolder.getContext(); + final Authentication authentication = context.getAuthentication(); + log.info("Authentication: {}", authentication); + if (authentication == null) { + return null; + } + Object principal = authentication.getPrincipal(); + if (principal instanceof UserDetails) { + return (UserDetails) authentication.getPrincipal(); + } + return null; + }*/ + + public void logout() { + log.info("logout"); + authenticationContext.logout(); + clearCookies(); + + UI.getCurrent().getPage().setLocation(LOGOUT_SUCCESS_URL); + SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler(); + logoutHandler.logout(VaadinServletRequest.getCurrent().getHttpServletRequest(), null, null); + clearCookies(); + } + + public boolean isLoggedIn() { + log.info("User {}", getAuthenticatedUser()); + return getAuthenticatedUser().isPresent(); + } + + private static final String JWT_HEADER_AND_PAYLOAD_COOKIE_NAME = "jwt.headerAndPayload"; + private static final String JWT_SIGNATURE_COOKIE_NAME = "jwt.signature"; + + private void clearCookies() { + log.info("clearCookies"); + clearCookie(JWT_HEADER_AND_PAYLOAD_COOKIE_NAME); + clearCookie(JWT_SIGNATURE_COOKIE_NAME); + } + + private void clearCookie(String cookieName) { + log.info("clearCookie: {}", cookieName); + HttpServletRequest request = VaadinServletRequest.getCurrent() + .getHttpServletRequest(); + HttpServletResponse response = VaadinServletResponse.getCurrent() + .getHttpServletResponse(); + + Cookie k = new Cookie(cookieName, null); + k.setPath(getRequestContextPath(request)); + k.setMaxAge(0); + k.setSecure(request.isSecure()); + k.setHttpOnly(false); + response.addCookie(k); + } + + private String getRequestContextPath(HttpServletRequest request) { + log.info("getRequestContextPath"); + final String contextPath = request.getContextPath(); + return "".equals(contextPath) ? "/" : contextPath; + } +} \ No newline at end of file diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/SetupModuleTysc.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/SetupModuleTysc.java new file mode 100644 index 0000000..bf6e1e4 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/SetupModuleTysc.java @@ -0,0 +1,519 @@ +package de.thpeetz.kontor.tysc; + +import de.thpeetz.kontor.tysc.data.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.stereotype.Component; + +import de.thpeetz.kontor.admin.services.ModuleService; +import de.thpeetz.kontor.tysc.data.FieldPosition; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +public class SetupModuleTysc implements ApplicationListener { + + boolean alreadySetup = false; + + @Autowired + private SportRepository sportRepository; + + @Autowired + private TeamRepository teamRepository; + + @Autowired + private VendorRepository vendorRepository; + + @Autowired + private FieldPositionRepository fieldPositionRepository; + + @Autowired + private PlayerRepository playerRepository; + + @Autowired + private CardSetRepository cardSetRepository; + + @Autowired + private RoosterRepository roosterRepository; + + @Autowired + private CardRepository cardRepository; + + @Autowired + private ModuleService moduleService; + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + if (alreadySetup) { + log.info("SetupDataLoader(TYSC) already executed, skipping"); + return; + } + if (!moduleService.importData(TyscConstants.TYSC)) { + log.info("Module TradeYourSportsCards should not setup data"); + return; + } + log.info("Setp up TYSC data"); + Sport football = createSportIfNotFound("Football"); + Sport baseball = createSportIfNotFound("Baseball"); + Sport basketball = createSportIfNotFound("Basketball"); + Sport hockey = createSportIfNotFound("Hockey"); + createTeamIfNotFound(football, "Buffalo Bills", "Bills"); + Team colts = createTeamIfNotFound(football, "Indianapolis Colts", "Colts"); + createTeamIfNotFound(football, "Miami Dolphins", "Dolphins"); + Team patriots = createTeamIfNotFound(football, "New England Patriots", "Patriots"); + createTeamIfNotFound(football, "New York Jets", "Jets"); + Team ravens = createTeamIfNotFound(football, "Baltimore Ravens", "Ravens"); + createTeamIfNotFound(football, "Cincinnati Bengals", "Bengals"); + Team browns = createTeamIfNotFound(football, "Cleveland Browns", "Browns"); + createTeamIfNotFound(football, "Jacksonville Jaguars", "Jaguars"); + Team steelers = createTeamIfNotFound(football, "Pittsburgh Steelers", "Steelers"); + createTeamIfNotFound(football, "Tennessee Titans", "Titans"); + createTeamIfNotFound(football, "Denver Broncos", "Broncos"); + createTeamIfNotFound(football, "Kansas City Chiefs", "Chiefs"); + createTeamIfNotFound(football, "Oakland Raiders", "Raiders"); + createTeamIfNotFound(football, "San Diego Chargers", "Chargers"); + createTeamIfNotFound(football, "Seattle Seahawks", "Seahawks"); + createTeamIfNotFound(football, "Arizona Cardinals", "Cardinals"); + createTeamIfNotFound(football, "Dallas Cowboys", "Cowboys"); + createTeamIfNotFound(football, "New York Giants", "Giants"); + createTeamIfNotFound(football, "Philadelphia Eagles", "Eagles"); + Team redskins = createTeamIfNotFound(football, "Washington Redskins", "Redskins"); + createTeamIfNotFound(football, "Chicago Bears", "Bears"); + createTeamIfNotFound(football, "Detroit Lions", "Lions"); + createTeamIfNotFound(football, "Green Bay Packers", "Packers"); + createTeamIfNotFound(football, "Minnesota Vikings", "Vikings"); + createTeamIfNotFound(football, "Tampa Bay Buccaneers", "Buccaneers"); + createTeamIfNotFound(football, "Atlanta Falcons", "Falcons"); + createTeamIfNotFound(football, "Carolina Panthers", "Panthers"); + createTeamIfNotFound(football, "New Orleans Saints", "Saints"); + createTeamIfNotFound(football, "St.Louis Rams", "Rams"); + createTeamIfNotFound(football, "San Francisco 49ers", "49ers"); + createTeamIfNotFound(football, "Houston Texans", "Texans"); + createTeamIfNotFound(football, "Houston Oilers", "Oilers"); + createTeamIfNotFound(baseball, "Baltimore Orioles", "Orioles"); + createTeamIfNotFound(baseball, "Boston Red Sox", "Red Sox"); + createTeamIfNotFound(baseball, "New York Yankees", "Yankees"); + createTeamIfNotFound(baseball, "Tampa Bay Devil Rays", "Devil Rays"); + createTeamIfNotFound(baseball, "Toronto Blue Jays", "Blue Jays"); + createTeamIfNotFound(baseball, "Chicago White Sox", "White Sox"); + createTeamIfNotFound(baseball, "Cleveland Indians", "Indians"); + createTeamIfNotFound(baseball, "Detroit Tigers", "Tigers"); + createTeamIfNotFound(baseball, "Kansas City Royals", "Royals"); + createTeamIfNotFound(baseball, "Minnesota Twins", "Twins"); + createTeamIfNotFound(baseball, "Anaheim Angels", "Angels"); + createTeamIfNotFound(baseball, "Oakland Athletics", "Athletics"); + createTeamIfNotFound(baseball, "Seattle Mariners", "Mariners"); + createTeamIfNotFound(baseball, "Texas Rangers", "Rangers"); + createTeamIfNotFound(baseball, "Atlanta Braves", "Braves"); + createTeamIfNotFound(baseball, "Florida Marlins", "Marlins"); + createTeamIfNotFound(baseball, "Montreal Expos", "Expos"); + createTeamIfNotFound(baseball, "New York Mets", "Mets"); + createTeamIfNotFound(baseball, "Philadelphia Phillies", "Phillies"); + createTeamIfNotFound(baseball, "Chicago Cubs", "Cubs"); + createTeamIfNotFound(baseball, "Cincinnati Reds", "Reds"); + createTeamIfNotFound(baseball, "Houston Astros", "Astros"); + createTeamIfNotFound(baseball, "Milwaukee Brewers", "Brewers"); + createTeamIfNotFound(baseball, "Pittsburgh Pirates", "Pirates"); + createTeamIfNotFound(baseball, "St.Louis Cardinals", "Cardinals"); + createTeamIfNotFound(baseball, "Arizona Diamondbacks", "Diamondbacks"); + createTeamIfNotFound(baseball, "Colorado Rockies", "Rockies"); + createTeamIfNotFound(baseball, "Los Angeles Dodgers", "Dodgers"); + createTeamIfNotFound(baseball, "San Diego Padres", "Padres"); + createTeamIfNotFound(baseball, "San Francisco Giants", "Giants"); + createTeamIfNotFound(basketball, "Boston Celtics", "Celtics"); + createTeamIfNotFound(basketball, "Miami Heat", "Heat"); + createTeamIfNotFound(basketball, "New Jersey Nets", "Mets"); + createTeamIfNotFound(basketball, "New York Knicks", "Knicks"); + createTeamIfNotFound(basketball, "Orlando Magic", "Magic"); + createTeamIfNotFound(basketball, "Philadelphia 76ers", "76ers"); + createTeamIfNotFound(basketball, "Washington Wizards", "Wizards"); + createTeamIfNotFound(basketball, "Atlanta Hawks", "Hawks"); + createTeamIfNotFound(basketball, "Charlotte Hornets", "Hornets"); + createTeamIfNotFound(basketball, "Chicago Bulls", "Bulls"); + createTeamIfNotFound(basketball, "Cleveland Cavaliers", "Cavaliers"); + createTeamIfNotFound(basketball, "Detroit Pistons", "Pistons"); + createTeamIfNotFound(basketball, "Indiana Pacers", "Pacers"); + createTeamIfNotFound(basketball, "Milwaukee Bucks", "Bucks"); + createTeamIfNotFound(basketball, "Toronto Raptors", "Raptors"); + createTeamIfNotFound(basketball, "Dallas Mavericks", "Mavericks"); + createTeamIfNotFound(basketball, "Denver Nuggets", "Nuggets"); + createTeamIfNotFound(basketball, "Houston Rockets", "Rockets"); + createTeamIfNotFound(basketball, "Minnesota Timberwolves", "Timberwolves"); + createTeamIfNotFound(basketball, "San Antonio Spurs", "Spurs"); + createTeamIfNotFound(basketball, "Utah Jazz", "Jazz"); + createTeamIfNotFound(basketball, "Vancouver Grizzlies", "Grizzlies"); + createTeamIfNotFound(basketball, "Golden State Warriors", "Warriors"); + createTeamIfNotFound(basketball, "Los Angeles Clippers", "Clippers"); + createTeamIfNotFound(basketball, "Los Angeles Lakers", "Lakers"); + createTeamIfNotFound(basketball, "Phoenix Suns", "Suns"); + createTeamIfNotFound(basketball, "Portland Trail Blazers", "Blazers"); + createTeamIfNotFound(basketball, "Sacramento Kings", "Kings"); + createTeamIfNotFound(basketball, "Seattle SuperSonics", "SuperSonics"); + createTeamIfNotFound(hockey, "Boston Bruins", "Bruins"); + createTeamIfNotFound(hockey, "Buffalo Sabres", "Sabres"); + createTeamIfNotFound(hockey, "Montreal Canadiens", "Canadiens"); + createTeamIfNotFound(hockey, "Ottawa Senators", "Senators"); + createTeamIfNotFound(hockey, "Toronto Maple Leafs", "Maple Leafs"); + createTeamIfNotFound(hockey, "New Jersey Devils", "Devils"); + createTeamIfNotFound(hockey, "New York Islanders", "Islanders"); + createTeamIfNotFound(hockey, "New York Rangers", "Rangers"); + createTeamIfNotFound(hockey, "Philadelphia Flyers", "Flyers"); + createTeamIfNotFound(hockey, "Pittsburgh Penguins", "Penguins"); + createTeamIfNotFound(hockey, "Atlanta Trashers", "Trashers"); + createTeamIfNotFound(hockey, "Carolina Hurricanes", "Hurricanes"); + createTeamIfNotFound(hockey, "Florida Panthers", "Panthers"); + createTeamIfNotFound(hockey, "Tampa Bay Lightnings", "Lightnings"); + createTeamIfNotFound(hockey, "Washington Capitals", "Capitals"); + createTeamIfNotFound(hockey, "Chicago Blackhawks", "Blackhawks"); + createTeamIfNotFound(hockey, "Columbus Blue Jackets", "Blue Jackets"); + createTeamIfNotFound(hockey, "Detroit Red Wings", "Red Wings"); + createTeamIfNotFound(hockey, "Nashville Predators", "Predators"); + createTeamIfNotFound(hockey, "St.Louis Blues", "Blues"); + createTeamIfNotFound(hockey, "Calgary Flames", "Flames"); + createTeamIfNotFound(hockey, "Colorado Avalanche", "Avalanche"); + createTeamIfNotFound(hockey, "Edmonton Oilers", "Oilers"); + createTeamIfNotFound(hockey, "Minnesota Wild", "Wild"); + createTeamIfNotFound(hockey, "Vancouver Canucks", "Canucks"); + createTeamIfNotFound(hockey, "Anaheim Mighty Ducks", "Mighty Ducks"); + createTeamIfNotFound(hockey, "Dallas Stars", "Stars"); + createTeamIfNotFound(hockey, "Los Angeles Kings", "Kings"); + createTeamIfNotFound(hockey, "Phoenix Coyotes", "Coyotes"); + createTeamIfNotFound(hockey, "San Jose Sharks", "Sharks"); + FieldPosition qb = createPosition(football, "QB", "Quarterback"); + FieldPosition rb = createPosition(football, "RB", "Running Back"); + FieldPosition wr = createPosition(football, "WR", "Wide Receiver"); + FieldPosition te = createPosition(football, "TE", "Tight End"); + FieldPosition fb = createPosition(football, "FB", "Fullback"); + createPosition(football, "OL", "Offensive Line"); + createPosition(football, "DL", "Defensive Line"); + FieldPosition lb = createPosition(football, "LB", "Linebacker"); + createPosition(football, "DB", "Defensive Back"); + createPosition(football, "DE", "Defensive End"); + createPosition(football, "K", "Kicker"); + createPosition(football, "P", "Punter"); + createPosition(football, "S", "Safety"); + createPosition(football, "KR", "Kick Returner"); + createPosition(football, "PR", "Punt Returner"); + createPosition(football, "LS", "Long Snapper"); + createPosition(football, "LG", "Left Guard"); + createPosition(football, "RG", "Right Guard"); + createPosition(football, "OF", "Offensive Tackle"); + createPosition(football, "DB", "Defensive Back"); + createPosition(football, "CB", "Cornerback"); + createPosition(football, "DT", "Defensive Tackle"); + createPosition(football, "NT", "Nose Tackle"); + createPosition(football, "OLB", "Outside Linebacker"); + createPosition(football, "ILB", "Inside Linebacker"); + createPosition(football, "SS", "Strong Safety"); + createPosition(baseball, "P", "Pitcher"); + createPosition(baseball, "C", "Catcher"); + createPosition(baseball, "1B", "First Base"); + createPosition(baseball, "2B", "Second Base"); + createPosition(baseball, "3B", "Third Base"); + createPosition(baseball, "SS", "Shortstop"); + createPosition(baseball, "LF", "Left Field"); + createPosition(baseball, "CF", "Center Field"); + createPosition(baseball, "RF", "Right Field"); + createPosition(basketball, "PG", "Point Guard"); + createPosition(basketball, "SG", "Shooting Guard"); + createPosition(basketball, "SF", "Small Forward"); + createPosition(basketball, "PF", "Power Forward"); + createPosition(basketball, "C", "Center"); + createPosition(hockey, "G", "Goalie"); + createPosition(hockey, "D", "Defense"); + createPosition(hockey, "LW", "Left Wing"); + createPosition(hockey, "RW", "Right Wing"); + createPosition(hockey, "C", "Center"); + Vendor pacific = createVendorIfNotFound("Pacific"); + Vendor fleer = createVendorIfNotFound("Fleer"); + Vendor bowman = createVendorIfNotFound("Bowman"); + Vendor leaf = createVendorIfNotFound("Leaf"); + Vendor upperdeck = createVendorIfNotFound("Upper Deck"); + createVendorIfNotFound("Topps"); + createVendorIfNotFound("Donruss"); + createVendorIfNotFound("Score"); + createVendorIfNotFound("Flair"); + createCardSetIfNotFound("Mystique Big Buzz", fleer, false, true); + createCardSetIfNotFound("Mystique Gold", fleer, true, false); + createCardSetIfNotFound("Pacific Copper", pacific, true, false); + createCardSetIfNotFound("Pacific Gold", pacific, true, false); + CardSet pacificbase = createCardSetIfNotFound(pacific.getName(), pacific, false, false); + createCardSetIfNotFound(fleer.getName(), fleer, false, false); + createCardSetIfNotFound(bowman.getName(), bowman, false, false); + createCardSetIfNotFound(leaf.getName(), leaf, false, false); + createCardSetIfNotFound("Ultra", fleer, false, false); + createCardSetIfNotFound("Mystique", fleer, false, false); + createCardSetIfNotFound("Finest Hour", pacific, false, false); + createCardSetIfNotFound("SP", upperdeck, false, false); + createCardSetIfNotFound("SPX", upperdeck, false, false); + createCardSetIfNotFound("SP Authentic", upperdeck, false, false); + createCardSetIfNotFound("Black Diamond", upperdeck, false, false); + Player jeromepathon = createPlayerIfNotFound("Jerome", "Pathon"); + Player bruschi = createPlayerIfNotFound("Tedy", "Bruschi"); + Player couch = createPlayerIfNotFound("Tim", "Couch"); + Player shea = createPlayerIfNotFound("Aaron", "Shea"); + Player jamallewis = createPlayerIfNotFound("Jamal", "Lewis"); + Player jermainelewis = createPlayerIfNotFound("Jermaine", "Lewis"); + Player tonybanks = createPlayerIfNotFound("Tony", "Banks"); + Player chrisfuamatu = createPlayerIfNotFound("Chris", "Fuamatu-Ma'afala"); + Player jeromebettis = createPlayerIfNotFound("Jerome", "Bettis"); + Player kordellstewart = createPlayerIfNotFound("Kordell", "Stewart"); + createPlayerIfNotFound("Warren", "Moon"); + createPlayerIfNotFound("Kevin", "Lockett"); + createPlayerIfNotFound("Rich", "Gannon"); + createPlayerIfNotFound("James", "Jett"); + createPlayerIfNotFound("Mack", "Strong"); + createPlayerIfNotFound("Brock", "Huard"); + createPlayerIfNotFound("Ricky", "Watters"); + createPlayerIfNotFound("Troy", "Aikman"); + createPlayerIfNotFound("David", "LaFleur"); + createPlayerIfNotFound("Chris", "Brazzell"); + createPlayerIfNotFound("Ron", "Dayne"); + createPlayerIfNotFound("Na", "Brown"); + createPlayerIfNotFound("Torrance", "Small"); + createPlayerIfNotFound("Chad", "Lewis"); + createPlayerIfNotFound("Adrian", "Murrell"); + createPlayerIfNotFound("Chris", "Chandler"); + createPlayerIfNotFound("Danny", "Kanell"); + createPlayerIfNotFound("Ricky", "Williams"); + createPlayerIfNotFound("Jeff", "Garcia"); + createPlayerIfNotFound("Tai", "Streets"); + createPlayerIfNotFound("Charlie", "Garner"); + createPlayerIfNotFound("Drew", "Bledsoe"); + createPlayerIfNotFound("Antowain", "Smith"); + createPlayerIfNotFound("Terry", "Glenn"); + createPlayerIfNotFound("Jerry", "Rice"); + createPlayerIfNotFound("Terrell", "Owens"); + createPlayerIfNotFound("Isaac", "Bruce"); + createPlayerIfNotFound("Trung", "Canidate"); + Rooster pathoncoltswr2001 = createRoosterIfNotFound(jeromepathon, colts, wr, 2001); + Rooster bruschipatriotslb2001 = createRoosterIfNotFound(bruschi, patriots, lb, 2001); + Rooster couchbrownsqb2001 = createRoosterIfNotFound(couch, browns, qb, 2001); + Rooster sheabrownste2001 = createRoosterIfNotFound(shea, browns, te, 2001); + Rooster jamallewisravensrb2001 = createRoosterIfNotFound(jamallewis, ravens, rb, 2001); + Rooster jermainelewisravenswr2001 = createRoosterIfNotFound(jermainelewis, ravens, wr, 2001); + Rooster tonybanksravensqb2001 = createRoosterIfNotFound(tonybanks, ravens, qb, 2001); + createRoosterIfNotFound(tonybanks, redskins, qb, 2002); + Rooster chrisfuamatusteelersfb2001 = createRoosterIfNotFound(chrisfuamatu, steelers, fb, 2001); + Rooster jeromebettissteelersrb2001 = createRoosterIfNotFound(jeromebettis, steelers, rb, 2001); + Rooster kordellstewartsteelersqb2001 = createRoosterIfNotFound(kordellstewart, steelers, qb, 2001); + createCardIfNotFound(185, 2001, pacific, pacificbase, pathoncoltswr2001); + createCardIfNotFound(250, 2001, pacific, pacificbase, bruschipatriotslb2001); + createCardIfNotFound(103, 2001, pacific, pacificbase, couchbrownsqb2001); + createCardIfNotFound(112, 2001, pacific, pacificbase, sheabrownste2001); + createCardIfNotFound(37, 2001, pacific, pacificbase, jamallewisravensrb2001); + createCardIfNotFound(38, 2001, pacific, pacificbase, jermainelewisravenswr2001); + createCardIfNotFound(31, 2001, pacific, pacificbase, tonybanksravensqb2001); + createCardIfNotFound(338, 2001, pacific, pacificbase, chrisfuamatusteelersfb2001); + createCardIfNotFound(335, 2001, pacific, pacificbase, jeromebettissteelersrb2001); + createCardIfNotFound(345, 2001, pacific, pacificbase, kordellstewartsteelersqb2001); + /* + * INSERT INTO `spieler` (`ID`,`name`) VALUES + * (11,'Moon, Warren'), + * (12,'Lockett, Kevin'), + * (13,'Gannon, Rich'), + * (14,'Jett, James'), + * (15,'Strong, Mack'), + * (16,'Huard, Brock'), + * (17,'Watters, Ricky'), + * (18,'Aikman, Troy'), + * (19,'LaFleur, David'), + * (20,'Brazzell, Chris'), + * (21,'Dayne, Ron'), + * (22,'Brown, Na'), + * (23,'Small, Torrance'), + * (24,'Lewis, Chad'), + * (25,'Murrell, Adrian'), + * (26,'Smith, Maurice'), + * (27,'Chandler, Chris'), + * (28,'Kanell, Danny') + * ,(29,'Williams, Ricky'), + * (30,'Garcia, Jeff'), + * (31,'Streets, Tai'), + * (32,'Garner, Charlie'), + * (33,'Rice, Jerry'), + * (34,'Owens, Terrell'), + * (35,'Bruce, Isaac'), + * (36,'Canidate, Trung'); + * + * INSERT INTO `team` (`ID`,`sportart_id`,`name`,`short`) VALUES + * (1,1,'Buffalo Bills','Bills'), + * (2,1,'Indianapolis Colts','Colts'), + * (3,1,'Miami Dolphins','Dolphins'), + * (4,1,'New England Patriots','Patriots'), + * (5,1,'New York Jets','Jets'), + * (6,1,'Baltimore Ravens','Ravens'), + * (7,1,'Cincinnati Bengals','Bengals'), + * (8,1,'Cleveland Browns','Browns'), + * (9,1,'Jacksonville Jaguars','Jaguars'), + * (10,1,'Pittsburgh Steelers','Steelers'), + * (11,1,'Tennessee Titans','Titans'), + * (12,1,'Denver Broncos','Broncos'), + * (13,1,'Kansas City Chiefs','Chiefs'), + * (14,1,'Oakland Raiders','Raiders'), + * (15,1,'San Diego Chargers','Chargers'), + * (16,1,'Seattle Seahawks','Seahawks'), + * (17,1,'Arizona Cardinals','Cardinals'), + * (18,1,'Dallas Cowboys','Cowboys'), + * (19,1,'New York Giants','Giants'), + * (20,1,'Philadelphia Eagles','Eagles'), + * (21,1,'Washington Redskins','Redskins'), + * (22,1,'Chicago Bears','Bears'), + * (23,1,'Detroit Lions','Lions'), + * (24,1,'Green Bay Packers','Packers'), + * (25,1,'Minnesota Vikings','Vikings'), + * (26,1,'Tampa Bay Buccaneers','Buccaneers'), + * (27,1,'Atlanta Falcons','Falcons'), + * (28,1,'Carolina Panthers','Panthers'); + * (29,1,'New Orleans Saints','Saints'), + * (30,1,'St.Louis Rams','Rams'), + * (31,1,'San Francisco 49ers','49ers'), + * + * INSERT INTO `karte` + * (`ID`,`spieler_id`,`team_id`,`hersteller_id`,`serie_id`,`parallel_id`,` + * inserts_id`,`rookie`,`jahr`,`nummer`) VALUES + * (11,11,13,1,1,0,0,'N',2001,213), + * (12,12,13,1,1,0,0,'N',2001,212), + * (13,13,14,1,1,0,0,'N',2001,311), + * (14,14,14,1,1,0,0,'N',2001,312), + * (15,15,16,1,1,0,0,'N',2001,403), + * (16,16,16,1,1,0,0,'N',2001,397), + * (17,17,16,1,1,0,0,'N',2001,404), + * (18,18,18,1,1,0,0,'N',2001,116), + * (19,19,18,1,1,0,0,'N',2001,122), + * (20,20,18,1,1,0,0,'N',2001,117), + * (21,21,19,1,1,0,0,'N',2001,281), + * (22,22,19,1,1,0,0,'N',2001,321), + * (23,23,20,1,1,0,0,'N',2001,331), + * (24,24,20,1,1,0,0,'N',2001,324), + * (25,25,21,1,1,0,0,'N',2001,445), + * (26,26,27,1,1,0,0,'N',2001,28), + * (27,27,27,1,1,0,0,'N',2001,17), + * (28,28,27,1,1,0,0,'N',2001,23), + * (29,29,29,1,1,0,0,'N',2001,273); + * (30,30,31,1,1,0,0,'N',2001,380), + * (31,31,31,1,1,0,0,'N',2001,390), + * (32,32,31,1,1,0,0,'N',2001,381), + * (33,33,31,1,1,0,0,'N',2001,387), + * (34,34,31,1,1,0,0,'N',2001,386), + * (35,35,30,1,1,0,0,'N',2001,349), + * (36,36,30,1,1,0,0,'N',2001,350), + * (37,37,44,5,8,0,0,'N',1994,106); + */ + + alreadySetup = true; + moduleService.setDataImported(TyscConstants.TYSC); + } + + private Sport createSportIfNotFound(String sport) { + log.info("createSportIfNotFound: {}", sport); + Sport sportEntity = sportRepository.findByName(sport); + if (sportEntity == null) { + log.info("Sport {} not found, will create it", sport); + sportEntity = new Sport(); + sportEntity.setName(sport); + sportRepository.save(sportEntity); + } + return sportEntity; + } + + private Team createTeamIfNotFound(Sport football, String name, String shortName) { + log.info("createTeamIfNotFound: {}", name); + Team team = teamRepository.findByName(name); + if (team == null) { + log.info("Team {} not found, will create it", name); + team = new Team(); + team.setName(name); + team.setShortName(shortName); + team.setSport(football); + teamRepository.save(team); + } + return team; + } + + private FieldPosition createPosition(Sport sport, String shortName, String name) { + log.info("createPosition: {} for Sport {}", name, sport.getName()); + FieldPosition fieldPosition = fieldPositionRepository.findByShortNameAndSport(shortName, sport); + if (fieldPosition == null) { + log.info("Position {} not found, will create it", name); + fieldPosition = new FieldPosition(); + fieldPosition.setShortName(shortName); + fieldPosition.setName(name); + fieldPosition.setSport(sport); + fieldPositionRepository.save(fieldPosition); + } + return fieldPosition; + } + + private Vendor createVendorIfNotFound(String name) { + log.info("createVendorIfNotFound: {}", name); + Vendor vendor = vendorRepository.findByName(name); + if (vendor == null) { + log.info("Vendor {} not found, will create it", name); + vendor = new Vendor(); + vendor.setName(name); + vendorRepository.save(vendor); + } + return vendor; + } + + private Player createPlayerIfNotFound(String firstName, String lastName) { + log.info("createPlayerIfNotFound: {} {}", firstName, lastName); + Player player = playerRepository.findByFirstNameAndLastName(firstName, lastName); + if (player == null) { + log.info("Player {} {} not found, will create it", firstName, lastName); + player = new Player(); + player.setFirstName(firstName); + player.setLastName(lastName); + playerRepository.save(player); + } + return player; + } + + private CardSet createCardSetIfNotFound(String setname, Vendor vendor, boolean parallelSet, boolean insertSet) { + log.info("createCardSetIfNotFound: {} {}", setname, vendor.getName()); + CardSet cardSet = cardSetRepository.findByNameAndVendor(setname, vendor); + if (cardSet == null) { + log.info("CardType {} not found, will create it", cardSet); + cardSet = new CardSet(); + cardSet.setName(setname); + cardSet.setVendor(vendor); + cardSet.setParallelSet(parallelSet); + cardSet.setInsertSet(insertSet); + cardSetRepository.save(cardSet); + } + return cardSet; + } + + private Rooster createRoosterIfNotFound(Player player, Team team, FieldPosition fieldPosition, int year) { + log.info("createRoosterIfNotFound; {} {} {} {}", player.getFirstName(), player.getLastName(), team.getName(), + year); + Rooster rooster = roosterRepository.findByReferences(player, team, fieldPosition, year); + if (rooster == null) { + log.info("Rooster {} not found, will create it", player); + rooster = new Rooster(); + rooster.setPlayer(player); + rooster.setTeam(team); + rooster.setPosition(fieldPosition); + rooster.setYear(year); + roosterRepository.save(rooster); + } + return rooster; + } + + private void createCardIfNotFound(int cardNumber, int year, Vendor vendor, CardSet cardset, Rooster rooster) { + log.info("createCardIfNotFound: vendor={} cardset={} rooster={} cardNumber={} year={}", vendor, cardset, + rooster, cardNumber, year); + Card card = cardRepository.search(vendor, cardset, rooster, cardNumber, year); + if (card == null) { + card = new Card(); + card.setVendor(vendor); + card.setCardSet(cardset); + card.setRooster(rooster); + card.setCardNumber(cardNumber); + card.setYear(year); + cardRepository.save(card); + } + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/TyscConstants.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/TyscConstants.java new file mode 100644 index 0000000..cc41c93 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/TyscConstants.java @@ -0,0 +1,88 @@ +package de.thpeetz.kontor.tysc; + +import com.vaadin.flow.component.icon.VaadinIcon; +import com.vaadin.flow.component.sidenav.SideNavItem; +import com.vaadin.flow.router.RouterLink; + +import de.thpeetz.kontor.tysc.views.CardSetView; +import de.thpeetz.kontor.tysc.views.CardView; +import de.thpeetz.kontor.tysc.views.PlayerView; +import de.thpeetz.kontor.tysc.views.PositionView; +import de.thpeetz.kontor.tysc.views.RoosterView; +import de.thpeetz.kontor.tysc.views.SportView; +import de.thpeetz.kontor.tysc.views.TeamView; +import de.thpeetz.kontor.tysc.views.VendorView; + +/** + * The {@code TyscConstants} class contains constant values related to tysc. + */ +public class TyscConstants { + + public static final String TYSC = "TradeYourSportsCards"; + public static final String TYSC_ROLE = "ROLE_TYSC"; + public static final String VENDOR = "Vendor"; + public static final String VENDOR_ROUTE = "tysc/vendor"; + public static final String CARDSET = "Card Set"; + public static final String CARDSET_ROUTE = "tysc/cardset"; + public static final String CARD = "Card"; + public static final String CARD_ROUTE = "tysc/card"; + public static final String SPORT = "Sport"; + public static final String SPORT_ROUTE = "tysc/sport"; + public static final String TEAM = "Team"; + public static final String TEAM_ROUTE = "tysc/team"; + public static final String POSITION = "Position"; + public static final String POSITION_ROUTE = "tysc/position"; + public static final String PLAYER = "Player"; + public static final String PLAYER_ROUTE = "tysc/player"; + public static final String ROOSTER = "Rooster"; + public static final String ROOSTER_ROUTE = "tysc/rooster"; + + public static RouterLink getSportLink() { + return new RouterLink(SPORT, SportView.class); + } + + public static RouterLink getTeamLink() { + return new RouterLink(TEAM, TeamView.class); + } + + public static RouterLink getPlayerLink() { + return new RouterLink(PLAYER, PlayerView.class); + } + + public static RouterLink getPositionLink() { + return new RouterLink(POSITION, PositionView.class); + } + + public static RouterLink getRoosterLink() { + return new RouterLink(ROOSTER, RoosterView.class); + } + + public static RouterLink getVendorLink() { + return new RouterLink(VENDOR, VendorView.class); + } + + public static RouterLink getCardSetLink() { + return new RouterLink(CARDSET, CardSetView.class); + } + + public static RouterLink getCardLink() { + return new RouterLink(CARD, CardView.class); + } + + public static SideNavItem getTyscNavigation() { + SideNavItem tysc = new SideNavItem(TYSC, VENDOR_ROUTE, VaadinIcon.ARCHIVE.create()); + tysc.addItem(new SideNavItem(SPORT, SportView.class)); + tysc.addItem(new SideNavItem(TEAM, TeamView.class)); + tysc.addItem(new SideNavItem(PLAYER, PlayerView.class)); + tysc.addItem(new SideNavItem(POSITION, PositionView.class)); + tysc.addItem(new SideNavItem(ROOSTER, RoosterView.class)); + tysc.addItem(new SideNavItem(CARDSET, CardSetView.class)); + tysc.addItem(new SideNavItem(CARD, CardView.class)); + tysc.addItem(new SideNavItem(VENDOR, VendorView.class)); + return tysc; + } + + private TyscConstants() { + // private constructor to hide the implicit public one + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/data/Card.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/Card.java new file mode 100644 index 0000000..90ec739 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/Card.java @@ -0,0 +1,49 @@ +package de.thpeetz.kontor.tysc.data; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.persistence.Entity; +import jakarta.persistence.Index; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import jakarta.validation.constraints.NotNull; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@EqualsAndHashCode(callSuper=false) +@Entity +@Table(indexes = {@Index(columnList = "cardNumber, year")}, + uniqueConstraints = {@UniqueConstraint(columnNames = { "cardNumber", "year", "vendor_id", "cardSet_id" })} +) +public class Card extends AbstractEntity { + + private int cardNumber; + + private int year; + + @ManyToOne + @JoinColumn(name = "vendor_id") + @NotNull + @JsonIgnoreProperties({ "cards" }) + private Vendor vendor; + + @ManyToOne + @JoinColumn(name = "cardSet_id") + @NotNull + @JsonIgnoreProperties({ "cards" }) + private CardSet cardSet; + + @ManyToOne + @JoinColumn(name = "rooster_id") + @NotNull + @JsonIgnoreProperties({ "cards" }) + private Rooster rooster; +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/data/CardRepository.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/CardRepository.java new file mode 100644 index 0000000..c894b28 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/CardRepository.java @@ -0,0 +1,17 @@ +package de.thpeetz.kontor.tysc.data; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface CardRepository extends JpaRepository { + + @Query("SELECT c FROM Card c WHERE c.vendor = ?1 AND c.cardSet = ?2 and c.rooster = ?3 and c.cardNumber = ?4 and c.year = ?5") + Card search(Vendor vendor, CardSet cardset, Rooster rooster, int cardNumber, int year); + + @Query("select c from Card c " + + "where str(c.cardNumber) like lower(concat('%', :searchTerm, '%')) ") + List search(@Param("searchTerm") String searchTerm); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/data/CardSet.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/CardSet.java new file mode 100644 index 0000000..c72188c --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/CardSet.java @@ -0,0 +1,41 @@ +package de.thpeetz.kontor.tysc.data; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.persistence.Entity; +import jakarta.persistence.Index; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@EqualsAndHashCode(callSuper=false) +@Entity +@Table(indexes = {@Index(columnList = "name, vendor_id")}, + uniqueConstraints = {@UniqueConstraint(columnNames = { "name", "vendor_id" })} +) +public class CardSet extends AbstractEntity { + + @NotEmpty + private String name; + + @ManyToOne() + @JoinColumn(name = "vendor_id") + @NotNull + @JsonIgnoreProperties({ "cardSets" }) + private Vendor vendor; + + private boolean parallelSet = false; + + private boolean insertSet = false; +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/data/CardSetRepository.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/CardSetRepository.java new file mode 100644 index 0000000..237953e --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/CardSetRepository.java @@ -0,0 +1,18 @@ +package de.thpeetz.kontor.tysc.data; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface CardSetRepository extends JpaRepository { + + List findByName(String name); + + CardSet findByNameAndVendor(String name, Vendor vendor); + + @Query("select c from CardSet c " + + "where lower(c.name) like lower(concat('%', :searchTerm, '%')) ") + List search(@Param("searchTerm") String searchTerm); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/data/FieldPosition.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/FieldPosition.java new file mode 100644 index 0000000..a806e70 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/FieldPosition.java @@ -0,0 +1,52 @@ +package de.thpeetz.kontor.tysc.data; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.annotation.Nullable; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Index; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@EqualsAndHashCode(callSuper=false) +@Entity +@Table(indexes = {@Index(columnList = "name, sport_id")}, + uniqueConstraints = { + @UniqueConstraint(columnNames = { "name", "sport_id" }), + @UniqueConstraint(columnNames = { "shortName", "sport_id" }) + } +) +public class FieldPosition extends AbstractEntity { + + @NotEmpty + private String name; + + @NotEmpty + private String shortName; + + @ManyToOne + @JoinColumn(name = "sport_id") + @NotNull + @JsonIgnoreProperties({ "positions" }) + private Sport sport; + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "position") + @Nullable + private List roosters; +} \ No newline at end of file diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/data/FieldPositionRepository.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/FieldPositionRepository.java new file mode 100644 index 0000000..0f649cc --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/FieldPositionRepository.java @@ -0,0 +1,22 @@ +package de.thpeetz.kontor.tysc.data; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface FieldPositionRepository extends JpaRepository { + + @Query("select p from FieldPosition p " + + "where lower(p.name) like lower(concat('%', :searchTerm, '%')) ") + List search(@Param("searchTerm") String searchTerm); + + List findBySport(Sport sport); + + FieldPosition findByShortName(String searchTerm); + + List findByShortNameIgnoreCase(String shortName); + + FieldPosition findByShortNameAndSport(String shortName, Sport sport); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/data/Player.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/Player.java new file mode 100644 index 0000000..6517959 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/Player.java @@ -0,0 +1,49 @@ +package de.thpeetz.kontor.tysc.data; + +import java.util.List; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.annotation.Nullable; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Index; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import jakarta.validation.constraints.NotNull; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@EqualsAndHashCode(callSuper=false) +@Entity +@Table(indexes = { + @Index(columnList = "firstName, lastName"), + @Index(columnList = "lastName, firstName") +}, uniqueConstraints = { + @UniqueConstraint(columnNames = { "firstName", "lastName" }) +}) +public class Player extends AbstractEntity { + + @NotNull + private String firstName; + + @NotNull + private String lastName; + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "player") + @Nullable + private List roosters; + + public String getFullName() { + StringBuilder fullName = new StringBuilder(); + fullName.append(this.lastName); + fullName.append(", "); + fullName.append(this.firstName); + return fullName.toString(); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/data/PlayerRepository.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/PlayerRepository.java new file mode 100644 index 0000000..8431abd --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/PlayerRepository.java @@ -0,0 +1,17 @@ +package de.thpeetz.kontor.tysc.data; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface PlayerRepository extends JpaRepository { + + @Query("select p from Player p " + + "where lower(p.firstName) like lower(concat('%', :searchTerm, '%')) " + + "or lower(p.lastName) like lower(concat('%', :searchTerm, '%'))") + List search(@Param("searchTerm") String searchTerm); + + Player findByFirstNameAndLastName(String firstName, String lastName); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/data/Rooster.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/Rooster.java new file mode 100644 index 0000000..6410b07 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/Rooster.java @@ -0,0 +1,51 @@ +package de.thpeetz.kontor.tysc.data; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.persistence.Entity; +import jakarta.persistence.Index; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import jakarta.validation.constraints.NotNull; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@EqualsAndHashCode(callSuper=false) +@Entity +@Table(indexes = {@Index(columnList = "team_id, player_id, position_id")}, + uniqueConstraints = {@UniqueConstraint(name = "uniqueRooster", columnNames = {"year", "team_id", "player_id", "position_id"})} +) +public class Rooster extends AbstractEntity { + + private int year; + + @ManyToOne + @JoinColumn(name = "team_id") + @NotNull + private Team team; + + @ManyToOne + @JoinColumn(name = "player_id") + @NotNull + private Player player; + + @ManyToOne + @JoinColumn(name = "position_id") + @NotNull + private FieldPosition position; + + @Override + public String toString() { + return "Rooster{" + + "year=" + year + + ", team=" + team.getName() + + ", player=" + player.getFullName() + + ", position=" + position.getName() + + '}'; + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/data/RoosterRepository.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/RoosterRepository.java new file mode 100644 index 0000000..a24dda9 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/RoosterRepository.java @@ -0,0 +1,11 @@ +package de.thpeetz.kontor.tysc.data; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface RoosterRepository extends JpaRepository{ + + @Query("SELECT r FROM Rooster r WHERE r.player = ?1 AND r.team = ?2 AND r.position = ?3 AND r.year = ?4") + Rooster findByReferences(Player player, Team team, FieldPosition position, int year); + +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/data/Sport.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/Sport.java new file mode 100644 index 0000000..39333fd --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/Sport.java @@ -0,0 +1,42 @@ +package de.thpeetz.kontor.tysc.data; + +import java.util.LinkedList; +import java.util.List; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.annotation.Nullable; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Index; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import jakarta.validation.constraints.NotEmpty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@EqualsAndHashCode(callSuper=false) +@Entity +@Table(indexes = {@Index(columnList = "name")}, + uniqueConstraints = {@UniqueConstraint(columnNames = { "name" })} +) +public class Sport extends AbstractEntity { + + @NotEmpty + @Column(unique = true) + private String name; + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "sport") + @Nullable + private List teams = new LinkedList<>(); + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "sport") + @Nullable + private List positions = new LinkedList<>(); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/data/SportRepository.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/SportRepository.java new file mode 100644 index 0000000..35efe85 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/SportRepository.java @@ -0,0 +1,17 @@ +package de.thpeetz.kontor.tysc.data; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface SportRepository extends JpaRepository { + @Query("select s from Sport s " + + "where lower(s.name) like lower(concat('%', :searchTerm, '%')) ") + List search(@Param("searchTerm") String searchTerm); + + Sport findByName(String name); + + List findByNameIgnoreCase(String name); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/data/Team.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/Team.java new file mode 100644 index 0000000..b61fdd2 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/Team.java @@ -0,0 +1,50 @@ +package de.thpeetz.kontor.tysc.data; + +import java.util.List; +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.annotation.Nullable; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Index; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@EqualsAndHashCode(callSuper=false) +@Entity +@Table(indexes = {@Index(columnList = "name")}, + uniqueConstraints = {@UniqueConstraint(columnNames = { "name" })} +) +public class Team extends AbstractEntity { + + @NotEmpty + private String name; + + @NotEmpty + private String shortName; + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "team") + @Nullable + private List roosters; + + @ManyToOne + @JoinColumn(name = "sport_id") + @NotNull + @JsonIgnoreProperties({ "teams" }) + private Sport sport; +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/data/TeamRepository.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/TeamRepository.java new file mode 100644 index 0000000..9f518d9 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/TeamRepository.java @@ -0,0 +1,27 @@ +package de.thpeetz.kontor.tysc.data; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +/** + * The repository interface for managing teams. + */ +public interface TeamRepository extends JpaRepository { + + /** + * Searches for teams based on a search term. + * + * @param searchTerm the search term to match against team names + * @return a list of teams matching the search term + */ + @Query("select t from Team t " + + "where lower(t.name) like lower(concat('%', :searchTerm, '%')) ") + List search(@Param("searchTerm") String searchTerm); + + Team findByName(String name); + + List findByNameIgnoreCase(String name); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/data/Vendor.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/Vendor.java new file mode 100644 index 0000000..f405565 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/Vendor.java @@ -0,0 +1,38 @@ +package de.thpeetz.kontor.tysc.data; + +import java.util.List; + +import de.thpeetz.kontor.common.data.AbstractEntity; +import jakarta.annotation.Nullable; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Index; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import jakarta.validation.constraints.NotEmpty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + * Represents a vendor entity. + */ +@Getter +@Setter +@ToString +@EqualsAndHashCode(callSuper=false) +@Entity +@Table(indexes = {@Index(columnList = "name")}, + uniqueConstraints = {@UniqueConstraint(columnNames = "name")} +) +public class Vendor extends AbstractEntity { + + @NotEmpty + private String name; + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "vendor") + @Nullable + private List cardSets; +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/data/VendorRepository.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/VendorRepository.java new file mode 100644 index 0000000..4c7a304 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/data/VendorRepository.java @@ -0,0 +1,17 @@ +package de.thpeetz.kontor.tysc.data; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface VendorRepository extends JpaRepository { + + @Query("select v from Vendor v where lower(v.name) like lower(concat('%', :searchTerm, '%')) ") + List search(@Param("searchTerm") String searchTerm); + + Vendor findByName(String name); + + List findByNameIgnoreCase(String name); +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/services/CardService.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/services/CardService.java new file mode 100644 index 0000000..1cfedbd --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/services/CardService.java @@ -0,0 +1,93 @@ +package de.thpeetz.kontor.tysc.services; + +import java.util.List; + +import org.springframework.stereotype.Service; + +import de.thpeetz.kontor.tysc.data.Card; +import de.thpeetz.kontor.tysc.data.CardRepository; +import de.thpeetz.kontor.tysc.data.CardSet; +import de.thpeetz.kontor.tysc.data.CardSetRepository; +import de.thpeetz.kontor.tysc.data.Vendor; +import de.thpeetz.kontor.tysc.data.VendorRepository; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +public class CardService { + + private VendorRepository vendorRepository; + + private CardSetRepository cardSetRepository; + + private CardRepository cardRepository; + + public CardService(VendorRepository vendorRepository, CardSetRepository cardSetRepository, CardRepository cardRepository) { + this.vendorRepository = vendorRepository; + this.cardSetRepository = cardSetRepository; + this.cardRepository = cardRepository; + } + + public List findAllVendors(String stringFilter) { + if (stringFilter == null || stringFilter.isEmpty()) { + return vendorRepository.findAll(); + } else { + return vendorRepository.search(stringFilter); + } + } + + public void deleteVendor(Vendor vendor) { + vendorRepository.delete(vendor); + } + + public Vendor saveVendor(Vendor vendor) { + if (vendor == null) { + log.warn("Vendor is null. Are you sure you have connected your form to the application?"); + return null; + } + return vendorRepository.save(vendor); + } + + public List findAllCards(String stringFilter) { + log.info("find cards with filter: {}", stringFilter); + if (stringFilter == null || stringFilter.isEmpty()) { + List cards = cardRepository.findAll(); + log.debug("found {} cards", cards.size()); + return cards; + } else { + return cardRepository.search(stringFilter); + } + } + + public void deleteCard(Card card) { + cardRepository.delete(card); + } + + public Card saveCard(Card card) { + if (card == null) { + log.warn("Card is null. Are you sure you have connected your form to the application?"); + return null; + } + return cardRepository.save(card); + } + + public List findAllCardSets(String stringFilter) { + if (stringFilter == null || stringFilter.isEmpty()) { + return cardSetRepository.findAll(); + } else { + return cardSetRepository.search(stringFilter); + } + } + + public CardSet saveCardSet(CardSet cardSet) { + if (cardSet == null) { + log.warn("CardSet is null. Are you sure you have connected your form to the application?"); + return null; + } + return cardSetRepository.save(cardSet); + } + + public void deleteCardSet(CardSet cardSet) { + cardSetRepository.delete(cardSet); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/services/SportService.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/services/SportService.java new file mode 100644 index 0000000..05482bf --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/services/SportService.java @@ -0,0 +1,147 @@ +package de.thpeetz.kontor.tysc.services; + +import java.util.List; + +import de.thpeetz.kontor.tysc.data.FieldPosition; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import de.thpeetz.kontor.tysc.data.Player; +import de.thpeetz.kontor.tysc.data.PlayerRepository; +import de.thpeetz.kontor.tysc.data.FieldPositionRepository; +import de.thpeetz.kontor.tysc.data.Rooster; +import de.thpeetz.kontor.tysc.data.RoosterRepository; +import de.thpeetz.kontor.tysc.data.Sport; +import de.thpeetz.kontor.tysc.data.SportRepository; +import de.thpeetz.kontor.tysc.data.Team; +import de.thpeetz.kontor.tysc.data.TeamRepository; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +public class SportService { + + @Autowired + private SportRepository sportRepository; + + @Autowired + private TeamRepository teamRepository; + + @Autowired + private FieldPositionRepository fieldPositionRepository; + + @Autowired + private PlayerRepository playerRepository; + + @Autowired + private RoosterRepository roosterRepository; + + public List findAllSports(String stringFilter) { + if (stringFilter == null || stringFilter.isEmpty()) { + return sportRepository.findAll(); + } else { + return sportRepository.search(stringFilter); + } + } + + public void deleteSport(Sport sport) { + sportRepository.delete(sport); + } + + public Sport saveSport(Sport sport) { + if (sport == null) { + log.warn("Sport is null. Are you sure you have connected your form to the application?"); + return null; + } + return sportRepository.save(sport); + } + + public List findAllPlayers(String stringFilter) { + if (stringFilter == null || stringFilter.isEmpty()) { + return playerRepository.findAll(); + } else { + return playerRepository.search(stringFilter); + } + } + + public Player savePlayer(Player player) { + if (player == null) { + log.warn("Player is null. Are you sure you have connected yout form to the application?"); + return null; + } + return playerRepository.save(player); + } + + public void deletePlayer(Player player) { + playerRepository.delete(player); + } + + public List findAllTeams(String stringFilter) { + if (stringFilter == null || stringFilter.isEmpty()) { + return teamRepository.findAll(); + } else { + return teamRepository.search(stringFilter); + } + } + + public void deleteTeam(Team team) { + teamRepository.delete(team); + } + + public void saveTeam(Team team) { + if (team == null) { + log.warn("Team is null. Are you sure you have connected your form to the application?"); + return; + } + teamRepository.save(team); + } + + public List findAllPositions(String stringFilter) { + if (stringFilter == null || stringFilter.isEmpty()) { + return fieldPositionRepository.findAll(); + } else { + return fieldPositionRepository.search(stringFilter); + } + } + + public List findAllPositionsForSport(Sport sport) { + if (sport == null) { + return fieldPositionRepository.findAll(); + } else { + log.info("Find positions for Sport: {}", sport); + return fieldPositionRepository.findBySport(sport); + } + } + + public void savePosition(FieldPosition position) { + if (position == null) { + log.warn("Position is null. Are you sure you have connected your form to the application?"); + return; + } + fieldPositionRepository.save(position); + } + + public void deletePosition(FieldPosition position) { + fieldPositionRepository.delete(position); + } + + public List findAllRoosters() { + return roosterRepository.findAll(); + } + + public Rooster findRoosterByFields(Team team, Player player, FieldPosition position, Integer year) { + return roosterRepository.findByReferences(player, team, position, year); + } + + public void saveRooster(Rooster rooster) { + if (rooster == null) { + log.warn(""); + return; + } + roosterRepository.save(rooster); + } + + public void deleteRooster(Rooster rooster) { + roosterRepository.delete(rooster); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/views/CardForm.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/CardForm.java new file mode 100644 index 0000000..174afdc --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/CardForm.java @@ -0,0 +1,110 @@ +package de.thpeetz.kontor.tysc.views; + +import java.util.List; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +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 de.thpeetz.kontor.tysc.data.Card; +import de.thpeetz.kontor.tysc.data.Rooster; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class CardForm extends FormLayout { + + TextField cardNumber = new TextField("CardNumber"); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(Card.class); + + public CardForm() { + addClassName("card-form"); + binder.bindInstanceFields(this); + + add(cardNumber, createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new SaveEvent(this, binder.getBean())); + } + } + + public void setCard(Card card) { + binder.setBean(card); + } + + public void setRoosters(List roosters) { + log.info("Setting roosters: {}", roosters); + } + + public abstract static class CardFormEvent extends ComponentEvent { + private Card card; + + protected CardFormEvent(CardForm source, Card card) { + super(source, false); + this.card = card; + } + + public Card getCard() { + return card; + } + } + + public static class SaveEvent extends CardFormEvent { + SaveEvent(CardForm source, Card card) { + super(source, card); + } + } + + public static class DeleteEvent extends CardFormEvent { + DeleteEvent(CardForm source, Card card) { + super(source, card); + } + } + + public static class CloseEvent extends CardFormEvent { + CloseEvent(CardForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/views/CardSetForm.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/CardSetForm.java new file mode 100644 index 0000000..d0e7e89 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/CardSetForm.java @@ -0,0 +1,103 @@ +package de.thpeetz.kontor.tysc.views; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +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 de.thpeetz.kontor.tysc.data.CardSet; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class CardSetForm extends FormLayout { + + TextField name = new TextField("Name"); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(CardSet.class); + + public CardSetForm() { + addClassName("cardSet-form"); + binder.bindInstanceFields(this); + + add(name, createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new SaveEvent(this, binder.getBean())); + } + } + + public void setCardSet(CardSet cardSet) { + binder.setBean(cardSet); + } + + public abstract static class CardSetFormEvent extends ComponentEvent { + private CardSet cardSet; + + protected CardSetFormEvent(CardSetForm source, CardSet cardSet) { + super(source, false); + this.cardSet = cardSet; + } + + public CardSet getCardSet() { + return cardSet; + } + } + + public static class SaveEvent extends CardSetFormEvent { + SaveEvent(CardSetForm source, CardSet cardSet) { + super(source, cardSet); + } + } + + public static class DeleteEvent extends CardSetFormEvent { + DeleteEvent(CardSetForm source, CardSet cardSet) { + super(source, cardSet); + } + } + + public static class CloseEvent extends CardSetFormEvent { + CloseEvent(CardSetForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/views/CardSetView.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/CardSetView.java new file mode 100644 index 0000000..1a49896 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/CardSetView.java @@ -0,0 +1,129 @@ +package de.thpeetz.kontor.tysc.views; + +import org.springframework.context.annotation.Scope; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; + +import de.thpeetz.kontor.common.views.MainLayout; +import de.thpeetz.kontor.tysc.TyscConstants; +import de.thpeetz.kontor.tysc.data.CardSet; +import de.thpeetz.kontor.tysc.services.CardService; +import jakarta.annotation.security.PermitAll; + +@SpringComponent +@Scope("prototype") +@PermitAll +@Route(value = TyscConstants.CARDSET_ROUTE, layout = MainLayout.class) +@PageTitle("CardSet | Tysc | Kontor") +public class CardSetView extends VerticalLayout { + + Grid grid = new Grid<>(CardSet.class); + TextField filterText = new TextField(); + CardSetForm form; + CardService service; + + public CardSetView(CardService service) { + this.service = service; + addClassName("cardSet-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + public Grid getGrid() { + return grid; + } + + private void configureGrid() { + grid.addClassName("cardSet-grid"); + grid.setSizeFull(); + grid.setColumns("name", "vendor.name", "parallelSet", "insertSet"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editCardSet(event.getValue())); + } + + public CardSetForm getForm() { + return form; + } + + private void configureForm() { + form = new CardSetForm(); + form.setWidth("25em"); + form.setVisible(false); + form.addSaveListener(this::saveCardSet); + form.addDeleteListener(this::deleteCardSet); + form.addCloseListener(e -> closeEditor()); + } + + private void saveCardSet(CardSetForm.SaveEvent event) { + service.saveCardSet(event.getCardSet()); + updateList(); + closeEditor(); + } + + private void deleteCardSet(CardSetForm.DeleteEvent event) { + service.deleteCardSet(event.getCardSet()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + filterText.setPlaceholder("Filter by name..."); + filterText.setClearButtonVisible(true); + filterText.setValueChangeMode(ValueChangeMode.LAZY); + filterText.addValueChangeListener(e -> updateList()); + + Button addCardSetButton = new Button("Add cardSet"); + addCardSetButton.addClickListener(click -> addCardSet()); + + HorizontalLayout toolbar = new HorizontalLayout(filterText, addCardSetButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editCardSet(CardSet cardSet) { + if (cardSet == null) { + closeEditor(); + } else { + form.setCardSet(cardSet); + form.setVisible(true); + addClassName("editing"); + } + } + + private void closeEditor() { + form.setCardSet(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addCardSet() { + grid.asSingleSelect().clear(); + editCardSet(new CardSet()); + } + + public void updateList() { + grid.setItems(service.findAllCardSets(filterText.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/views/CardView.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/CardView.java new file mode 100644 index 0000000..89cc1ce --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/CardView.java @@ -0,0 +1,129 @@ +package de.thpeetz.kontor.tysc.views; + +import org.springframework.context.annotation.Scope; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; + +import de.thpeetz.kontor.common.views.MainLayout; +import de.thpeetz.kontor.tysc.TyscConstants; +import de.thpeetz.kontor.tysc.data.Card; +import de.thpeetz.kontor.tysc.services.CardService; +import jakarta.annotation.security.PermitAll; + +@SpringComponent +@Scope("prototype") +@PermitAll +@Route(value = TyscConstants.CARD_ROUTE, layout = MainLayout.class) +@PageTitle("Card | Tysc | Kontor") +public class CardView extends VerticalLayout { + + Grid grid = new Grid<>(Card.class); + TextField filterText = new TextField(); + CardForm form; + CardService service; + + public CardView(CardService service) { + this.service = service; + addClassName("card-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + public Grid getGrid() { + return grid; + } + + private void configureGrid() { + grid.addClassName("card-grid"); + grid.setSizeFull(); + grid.setColumns("cardNumber", "year", "vendor.name", "cardSet.name", "rooster.player.fullName"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editCard(event.getValue())); + } + + public CardForm getForm() { + return form; + } + + private void configureForm() { + form = new CardForm(); + form.setWidth("25em"); + form.setVisible(false); + form.addSaveListener(this::saveCard); + form.addDeleteListener(this::deleteCard); + form.addCloseListener(e -> closeEditor()); + } + + private void saveCard(CardForm.SaveEvent event) { + service.saveCard(event.getCard()); + updateList(); + closeEditor(); + } + + private void deleteCard(CardForm.DeleteEvent event) { + service.deleteCard(event.getCard()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + filterText.setPlaceholder("Filter by name..."); + filterText.setClearButtonVisible(true); + filterText.setValueChangeMode(ValueChangeMode.LAZY); + filterText.addValueChangeListener(e -> updateList()); + + Button addCardButton = new Button("Add card"); + addCardButton.addClickListener(click -> addCard()); + + HorizontalLayout toolbar = new HorizontalLayout(filterText, addCardButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editCard(Card card) { + if (card == null) { + closeEditor(); + } else { + form.setCard(card); + form.setVisible(true); + addClassName("editing"); + } + } + + private void closeEditor() { + form.setCard(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addCard() { + grid.asSingleSelect().clear(); + editCard(new Card()); + } + + public void updateList() { + grid.setItems(service.findAllCards(filterText.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/views/PlayerForm.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/PlayerForm.java new file mode 100644 index 0000000..9730103 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/PlayerForm.java @@ -0,0 +1,116 @@ +package de.thpeetz.kontor.tysc.views; + +import java.util.List; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.grid.Grid; +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 de.thpeetz.kontor.tysc.data.Player; +import de.thpeetz.kontor.tysc.data.Rooster; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class PlayerForm extends FormLayout { + + TextField firstName = new TextField("First Name"); + TextField lastName = new TextField("Last Name"); + Grid roosters = new Grid<>(Rooster.class); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(Player.class); + + public PlayerForm() { + addClassName("player-form"); + binder.bindInstanceFields(this); + + roosters.setColumns("team.name", "position.name", "year"); + roosters.getColumns().forEach(col -> col.setAutoWidth(true)); + add(firstName, lastName, roosters, createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new SaveEvent(this, binder.getBean())); + } + } + + public void setPlayer(Player player) { + binder.setBean(player); + } + + public void setRoosters(List roosters) { + log.info("Setting roosters: {}", roosters); + this.roosters.setItems(roosters); + } + + public abstract static class PlayerFormEvent extends ComponentEvent { + private Player player; + + protected PlayerFormEvent(PlayerForm source, Player player) { + super(source, false); + this.player = player; + } + + public Player getPlayer() { + return player; + } + } + + public static class SaveEvent extends PlayerFormEvent { + SaveEvent(PlayerForm source, Player player) { + super(source, player); + } + } + + public static class DeleteEvent extends PlayerFormEvent { + DeleteEvent(PlayerForm source, Player player) { + super(source, player); + } + } + + public static class CloseEvent extends PlayerFormEvent { + CloseEvent(PlayerForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/views/PlayerView.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/PlayerView.java new file mode 100644 index 0000000..56d9588 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/PlayerView.java @@ -0,0 +1,130 @@ +package de.thpeetz.kontor.tysc.views; + +import org.springframework.context.annotation.Scope; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; + +import de.thpeetz.kontor.common.views.MainLayout; +import de.thpeetz.kontor.tysc.TyscConstants; +import de.thpeetz.kontor.tysc.data.Player; +import de.thpeetz.kontor.tysc.services.SportService; +import jakarta.annotation.security.PermitAll; + +@SpringComponent +@Scope("prototype") +@PermitAll +@Route(value = TyscConstants.PLAYER_ROUTE, layout = MainLayout.class) +@PageTitle("Player | Tysc | Kontor") +public class PlayerView extends VerticalLayout { + + Grid grid = new Grid<>(Player.class); + TextField filterText = new TextField(); + PlayerForm form; + SportService service; + + public PlayerView(SportService service) { + this.service = service; + addClassName("player-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + public Grid getGrid() { + return grid; + } + + private void configureGrid() { + grid.addClassName("player-grid"); + grid.setSizeFull(); + grid.setColumns("firstName", "lastName"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editPlayer(event.getValue())); + } + + public PlayerForm getForm() { + return form; + } + + private void configureForm() { + form = new PlayerForm(); + form.setWidth("25em"); + form.setVisible(false); + form.addSaveListener(this::savePlayer); + form.addDeleteListener(this::deletePlayer); + form.addCloseListener(e -> closeEditor()); + } + + private void savePlayer(PlayerForm.SaveEvent event) { + service.savePlayer(event.getPlayer()); + updateList(); + closeEditor(); + } + + private void deletePlayer(PlayerForm.DeleteEvent event) { + service.deletePlayer(event.getPlayer()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + filterText.setPlaceholder("Filter by name..."); + filterText.setClearButtonVisible(true); + filterText.setValueChangeMode(ValueChangeMode.LAZY); + filterText.addValueChangeListener(e -> updateList()); + + Button addPlayerButton = new Button("Add player"); + addPlayerButton.addClickListener(click -> addPlayer()); + + HorizontalLayout toolbar = new HorizontalLayout(filterText, addPlayerButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editPlayer(Player player) { + if (player == null) { + closeEditor(); + } else { + form.setPlayer(player); + form.setRoosters(player.getRoosters()); + form.setVisible(true); + addClassName("editing"); + } + } + + private void closeEditor() { + form.setPlayer(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addPlayer() { + grid.asSingleSelect().clear(); + editPlayer(new Player()); + } + + public void updateList() { + grid.setItems(service.findAllPlayers(filterText.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/views/PositionForm.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/PositionForm.java new file mode 100644 index 0000000..f5b49bb --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/PositionForm.java @@ -0,0 +1,115 @@ +package de.thpeetz.kontor.tysc.views; + +import java.util.List; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.grid.Grid; +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 de.thpeetz.kontor.tysc.data.FieldPosition; +import de.thpeetz.kontor.tysc.data.Rooster; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class PositionForm extends FormLayout { + + TextField name = new TextField("Name"); + Grid roosters = new Grid<>(Rooster.class); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(FieldPosition.class); + + public PositionForm() { + addClassName("position-form"); + binder.bindInstanceFields(this); + + roosters.setColumns("player.fullName", "team.name", "year"); + roosters.getColumns().forEach(col -> col.setAutoWidth(true)); + add(name, roosters, createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new SaveEvent(this, binder.getBean())); + } + } + + public void setPosition(FieldPosition position) { + binder.setBean(position); + } + + public void setRoosters(List roosters) { + log.info("Setting roosters: {}", roosters); + this.roosters.setItems(roosters); + } + + public abstract static class PositionFormEvent extends ComponentEvent { + private FieldPosition position; + + protected PositionFormEvent(PositionForm source, FieldPosition position) { + super(source, false); + this.position = position; + } + + public FieldPosition getPosition() { + return position; + } + } + + public static class SaveEvent extends PositionFormEvent { + SaveEvent(PositionForm source, FieldPosition position) { + super(source, position); + } + } + + public static class DeleteEvent extends PositionFormEvent { + DeleteEvent(PositionForm source, FieldPosition position) { + super(source, position); + } + } + + public static class CloseEvent extends PositionFormEvent { + CloseEvent(PositionForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/views/PositionView.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/PositionView.java new file mode 100644 index 0000000..ad71bf0 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/PositionView.java @@ -0,0 +1,130 @@ +package de.thpeetz.kontor.tysc.views; + +import de.thpeetz.kontor.tysc.data.FieldPosition; +import org.springframework.context.annotation.Scope; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; + +import de.thpeetz.kontor.common.views.MainLayout; +import de.thpeetz.kontor.tysc.TyscConstants; +import de.thpeetz.kontor.tysc.services.SportService; +import jakarta.annotation.security.PermitAll; + +@SpringComponent +@Scope("prototype") +@PermitAll +@Route(value = TyscConstants.POSITION_ROUTE, layout = MainLayout.class) +@PageTitle("Position | Tysc | Kontor") +public class PositionView extends VerticalLayout { + + Grid grid = new Grid<>(FieldPosition.class); + TextField filterText = new TextField(); + PositionForm form; + SportService service; + + public PositionView(SportService service) { + this.service = service; + addClassName("position-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + public Grid getGrid() { + return grid; + } + + private void configureGrid() { + grid.addClassName("position-grid"); + grid.setSizeFull(); + grid.setColumns("shortName", "name", "sport.name"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editPosition(event.getValue())); + } + + public PositionForm getForm() { + return form; + } + + private void configureForm() { + form = new PositionForm(); + form.setWidth("25em"); + form.setVisible(false); + form.addSaveListener(this::savePosition); + form.addDeleteListener(this::deletePosition); + form.addCloseListener(e -> closeEditor()); + } + + private void savePosition(PositionForm.SaveEvent event) { + service.savePosition(event.getPosition()); + updateList(); + closeEditor(); + } + + private void deletePosition(PositionForm.DeleteEvent event) { + service.deletePosition(event.getPosition()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + filterText.setPlaceholder("Filter by name..."); + filterText.setClearButtonVisible(true); + filterText.setValueChangeMode(ValueChangeMode.LAZY); + filterText.addValueChangeListener(e -> updateList()); + + Button addPositionButton = new Button("Add position"); + addPositionButton.addClickListener(click -> addPosition()); + + HorizontalLayout toolbar = new HorizontalLayout(filterText, addPositionButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editPosition(FieldPosition fieldPosition) { + if (fieldPosition == null) { + closeEditor(); + } else { + form.setPosition(fieldPosition); + form.setRoosters(fieldPosition.getRoosters()); + form.setVisible(true); + addClassName("editing"); + } + } + + private void closeEditor() { + form.setPosition(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addPosition() { + grid.asSingleSelect().clear(); + editPosition(new FieldPosition()); + } + + public void updateList() { + grid.setItems(service.findAllPositions(filterText.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/views/RoosterForm.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/RoosterForm.java new file mode 100644 index 0000000..5f9d259 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/RoosterForm.java @@ -0,0 +1,117 @@ +package de.thpeetz.kontor.tysc.views; + +import java.util.List; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.textfield.IntegerField; +import com.vaadin.flow.data.binder.BeanValidationBinder; +import com.vaadin.flow.data.binder.Binder; + +import de.thpeetz.kontor.tysc.data.FieldPosition; +import de.thpeetz.kontor.tysc.data.Player; +import de.thpeetz.kontor.tysc.data.Rooster; +import de.thpeetz.kontor.tysc.data.Team; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class RoosterForm extends FormLayout { + IntegerField year = new IntegerField("Year"); + ComboBox team = new ComboBox<>("Team"); + ComboBox player = new ComboBox<>("Player"); + ComboBox position = new ComboBox<>("Position"); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(Rooster.class); + + public RoosterForm(List teams, List players, List positions) { + addClassName("rooster-form"); + binder.bindInstanceFields(this); + + team.setItems(teams); + team.setItemLabelGenerator(Team::getName); + player.setItems(players); + player.setItemLabelGenerator(Player::getFullName); + position.setItems(positions); + position.setItemLabelGenerator(FieldPosition::getName); + add(year, team, player, position, createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new SaveEvent(this, binder.getBean())); + } + } + + public void setRooster(Rooster rooster) { + binder.setBean(rooster); + } + + public abstract static class RoosterFormEvent extends ComponentEvent { + private Rooster rooster; + + protected RoosterFormEvent(RoosterForm source, Rooster rooster) { + super(source, false); + this.rooster = rooster; + } + + public Rooster getRooster() { + return rooster; + } + } + + public static class SaveEvent extends RoosterFormEvent { + SaveEvent(RoosterForm source, Rooster rooster) { + super(source, rooster); + } + } + + public static class DeleteEvent extends RoosterFormEvent { + DeleteEvent(RoosterForm source, Rooster rooster) { + super(source, rooster); + } + } + + public static class CloseEvent extends RoosterFormEvent { + CloseEvent(RoosterForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/views/RoosterView.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/RoosterView.java new file mode 100644 index 0000000..92da6eb --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/RoosterView.java @@ -0,0 +1,121 @@ +package de.thpeetz.kontor.tysc.views; + +import org.springframework.context.annotation.Scope; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; + +import de.thpeetz.kontor.common.views.MainLayout; +import de.thpeetz.kontor.tysc.TyscConstants; +import de.thpeetz.kontor.tysc.data.Rooster; +import de.thpeetz.kontor.tysc.services.SportService; +import jakarta.annotation.security.PermitAll; + +@SpringComponent +@Scope("prototype") +@PermitAll +@Route(value = TyscConstants.ROOSTER_ROUTE, layout = MainLayout.class) +@PageTitle("Rooster | Tysc | Kontor") +public class RoosterView extends VerticalLayout { + + Grid grid = new Grid<>(Rooster.class); + RoosterForm form; + SportService service; + + public RoosterView(SportService service) { + this.service = service; + addClassName("rooster-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + public Grid getGrid() { + return grid; + } + + private void configureGrid() { + grid.addClassName("rooster-grid"); + grid.setSizeFull(); + grid.setColumns("year", "team.name", "player.fullName", "position.name"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editRooster(event.getValue())); + } + + public RoosterForm getForm() { + return form; + } + + private void configureForm() { + form = new RoosterForm(service.findAllTeams(null), service.findAllPlayers(null), service.findAllPositions(null)); + form.setWidth("25em"); + form.setVisible(false); + form.addSaveListener(this::saveRooster); + form.addDeleteListener(this::deleteRooster); + form.addCloseListener(e -> closeEditor()); + } + + private void saveRooster(RoosterForm.SaveEvent event) { + service.saveRooster(event.getRooster()); + updateList(); + closeEditor(); + } + + private void deleteRooster(RoosterForm.DeleteEvent event) { + service.deleteRooster(event.getRooster()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + Button addRoosterButton = new Button("Add Rooster"); + addRoosterButton.addClickListener(click -> addRooster()); + + HorizontalLayout toolbar = new HorizontalLayout(addRoosterButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editRooster(Rooster rooster) { + if (rooster == null) { + closeEditor(); + } else { + form.setRooster(rooster); + form.setVisible(true); + addClassName("editing"); + } + } + + private void closeEditor() { + form.setRooster(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addRooster() { + grid.asSingleSelect().clear(); + editRooster(new Rooster()); + } + + public void updateList() { + grid.setItems(service.findAllRoosters()); + } +} \ No newline at end of file diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/views/SportForm.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/SportForm.java new file mode 100644 index 0000000..30582e1 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/SportForm.java @@ -0,0 +1,103 @@ +package de.thpeetz.kontor.tysc.views; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +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 de.thpeetz.kontor.tysc.data.Sport; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SportForm extends FormLayout { + + TextField name = new TextField("Name"); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(Sport.class); + + public SportForm() { + addClassName("sport-form"); + binder.bindInstanceFields(this); + + add(name, createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new SaveEvent(this, binder.getBean())); + } + } + + public void setSport(Sport sport) { + binder.setBean(sport); + } + + public abstract static class SportFormEvent extends ComponentEvent { + private Sport sport; + + protected SportFormEvent(SportForm source, Sport sport) { + super(source, false); + this.sport = sport; + } + + public Sport getSport() { + return sport; + } + } + + public static class SaveEvent extends SportFormEvent { + SaveEvent(SportForm source, Sport sport) { + super(source, sport); + } + } + + public static class DeleteEvent extends SportFormEvent { + DeleteEvent(SportForm source, Sport sport) { + super(source, sport); + } + } + + public static class CloseEvent extends SportFormEvent { + CloseEvent(SportForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/views/SportView.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/SportView.java new file mode 100644 index 0000000..c6008c3 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/SportView.java @@ -0,0 +1,129 @@ +package de.thpeetz.kontor.tysc.views; + +import org.springframework.context.annotation.Scope; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; + +import de.thpeetz.kontor.common.views.MainLayout; +import de.thpeetz.kontor.tysc.TyscConstants; +import de.thpeetz.kontor.tysc.data.Sport; +import de.thpeetz.kontor.tysc.services.SportService; +import jakarta.annotation.security.PermitAll; + +@SpringComponent +@Scope("prototype") +@PermitAll +@Route(value = TyscConstants.SPORT_ROUTE, layout = MainLayout.class) +@PageTitle("Sport | Tysc | Kontor") +public class SportView extends VerticalLayout { + + Grid grid = new Grid<>(Sport.class); + TextField filterText = new TextField(); + SportForm form; + SportService service; + + public SportView(SportService service) { + this.service = service; + addClassName("sport-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + public Grid getGrid() { + return grid; + } + + private void configureGrid() { + grid.addClassName("sport-grid"); + grid.setSizeFull(); + grid.setColumns("name"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editSport(event.getValue())); + } + + public SportForm getForm() { + return form; + } + + private void configureForm() { + form = new SportForm(); + form.setWidth("25em"); + form.setVisible(false); + form.addSaveListener(this::saveSport); + form.addDeleteListener(this::deleteSport); + form.addCloseListener(e -> closeEditor()); + } + + private void saveSport(SportForm.SaveEvent event) { + service.saveSport(event.getSport()); + updateList(); + closeEditor(); + } + + private void deleteSport(SportForm.DeleteEvent event) { + service.deleteSport(event.getSport()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + filterText.setPlaceholder("Filter by name..."); + filterText.setClearButtonVisible(true); + filterText.setValueChangeMode(ValueChangeMode.LAZY); + filterText.addValueChangeListener(e -> updateList()); + + Button addSportButton = new Button("Add sport"); + addSportButton.addClickListener(click -> addSport()); + + HorizontalLayout toolbar = new HorizontalLayout(filterText, addSportButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editSport(Sport sport) { + if (sport == null) { + closeEditor(); + } else { + form.setSport(sport); + form.setVisible(true); + addClassName("editing"); + } + } + + private void closeEditor() { + form.setSport(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addSport() { + grid.asSingleSelect().clear(); + editSport(new Sport()); + } + + public void updateList() { + grid.setItems(service.findAllSports(filterText.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/views/TeamForm.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/TeamForm.java new file mode 100644 index 0000000..63cb56b --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/TeamForm.java @@ -0,0 +1,115 @@ +package de.thpeetz.kontor.tysc.views; + +import java.util.List; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.grid.Grid; +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 de.thpeetz.kontor.tysc.data.Rooster; +import de.thpeetz.kontor.tysc.data.Team; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class TeamForm extends FormLayout { + + TextField name = new TextField("Name"); + Grid roosters = new Grid<>(Rooster.class); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(Team.class); + + public TeamForm() { + addClassName("team-form"); + binder.bindInstanceFields(this); + + roosters.setColumns("player.fullName", "position.name", "year"); + roosters.getColumns().forEach(col -> col.setAutoWidth(true)); + add(name, roosters, createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new SaveEvent(this, binder.getBean())); + } + } + + public void setTeam(Team team) { + binder.setBean(team); + } + + public void setRoosters(List roosters) { + log.info("Setting roosters: {}", roosters); + this.roosters.setItems(roosters); + } + + public abstract static class TeamFormEvent extends ComponentEvent { + private Team team; + + protected TeamFormEvent(TeamForm source, Team team) { + super(source, false); + this.team = team; + } + + public Team getTeam() { + return team; + } + } + + public static class SaveEvent extends TeamFormEvent { + SaveEvent(TeamForm source, Team team) { + super(source, team); + } + } + + public static class DeleteEvent extends TeamFormEvent { + DeleteEvent(TeamForm source, Team team) { + super(source, team); + } + } + + public static class CloseEvent extends TeamFormEvent { + CloseEvent(TeamForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/views/TeamView.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/TeamView.java new file mode 100644 index 0000000..1e48f98 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/TeamView.java @@ -0,0 +1,130 @@ +package de.thpeetz.kontor.tysc.views; + +import org.springframework.context.annotation.Scope; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; + +import de.thpeetz.kontor.common.views.MainLayout; +import de.thpeetz.kontor.tysc.TyscConstants; +import de.thpeetz.kontor.tysc.data.Team; +import de.thpeetz.kontor.tysc.services.SportService; +import jakarta.annotation.security.PermitAll; + +@SpringComponent +@Scope("prototype") +@PermitAll +@Route(value = TyscConstants.TEAM_ROUTE, layout = MainLayout.class) +@PageTitle("Team | Tysc | Kontor") +public class TeamView extends VerticalLayout { + + Grid grid = new Grid<>(Team.class); + TextField filterText = new TextField(); + TeamForm form; + SportService service; + + public TeamView(SportService service) { + this.service = service; + addClassName("team-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + public Grid getGrid() { + return grid; + } + + private void configureGrid() { + grid.addClassName("team-grid"); + grid.setSizeFull(); + grid.setColumns("name", "shortName", "sport.name"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editTeam(event.getValue())); + } + + public TeamForm getForm() { + return form; + } + + private void configureForm() { + form = new TeamForm(); + form.setWidth("25em"); + form.setVisible(false); + form.addSaveListener(this::saveTeam); + form.addDeleteListener(this::deleteTeam); + form.addCloseListener(e -> closeEditor()); + } + + private void saveTeam(TeamForm.SaveEvent event) { + service.saveTeam(event.getTeam()); + updateList(); + closeEditor(); + } + + private void deleteTeam(TeamForm.DeleteEvent event) { + service.deleteTeam(event.getTeam()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + filterText.setPlaceholder("Filter by name..."); + filterText.setClearButtonVisible(true); + filterText.setValueChangeMode(ValueChangeMode.LAZY); + filterText.addValueChangeListener(e -> updateList()); + + Button addTeamButton = new Button("Add team"); + addTeamButton.addClickListener(click -> addTeam()); + + HorizontalLayout toolbar = new HorizontalLayout(filterText, addTeamButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editTeam(Team team) { + if (team == null) { + closeEditor(); + } else { + form.setTeam(team); + form.setRoosters(team.getRoosters()); + form.setVisible(true); + addClassName("editing"); + } + } + + private void closeEditor() { + form.setTeam(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addTeam() { + grid.asSingleSelect().clear(); + editTeam(new Team()); + } + + public void updateList() { + grid.setItems(service.findAllTeams(filterText.getValue())); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/views/TyscLayout.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/TyscLayout.java new file mode 100644 index 0000000..66fb09b --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/TyscLayout.java @@ -0,0 +1,41 @@ +package de.thpeetz.kontor.tysc.views; + +import com.vaadin.flow.component.applayout.AppLayout; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.theme.lumo.LumoUtility; + +import de.thpeetz.kontor.admin.services.AdminService; +import de.thpeetz.kontor.common.views.KontorLayoutUtil; +import de.thpeetz.kontor.security.SecurityService; +import de.thpeetz.kontor.tysc.TyscConstants; +import lombok.extern.slf4j.Slf4j; + +/** + * Represents a custom layout for the comic view in the application. + * This layout extends the AppLayout class. + */ +@Slf4j +public class TyscLayout extends AppLayout { + + private final AdminService adminService; + + private final SecurityService securityService; + + public TyscLayout(AdminService adminService, SecurityService securityService) { + this.adminService = adminService; + this.securityService = securityService; + + KontorLayoutUtil layout = new KontorLayoutUtil(this, adminService, securityService); + layout.setSecondaryNavigation(getSecondaryNavigation()); + layout.createHeader(TyscConstants.TYSC); + } + + private HorizontalLayout getSecondaryNavigation() { + HorizontalLayout navigation = new HorizontalLayout(); + navigation.addClassNames(LumoUtility.JustifyContent.CENTER, LumoUtility.Gap.SMALL, LumoUtility.Height.MEDIUM); + navigation.add(TyscConstants.getSportLink(), TyscConstants.getTeamLink(), TyscConstants.getPositionLink(), + TyscConstants.getPlayerLink(), TyscConstants.getRoosterLink(), TyscConstants.getVendorLink(), + TyscConstants.getCardSetLink(), TyscConstants.getCardLink()); + return navigation; + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/views/VendorForm.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/VendorForm.java new file mode 100644 index 0000000..254b1ff --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/VendorForm.java @@ -0,0 +1,115 @@ +package de.thpeetz.kontor.tysc.views; + +import java.util.List; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.grid.Grid; +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 de.thpeetz.kontor.tysc.data.Vendor; +import de.thpeetz.kontor.tysc.data.CardSet; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class VendorForm extends FormLayout { + + TextField name = new TextField("Name"); + Grid cardSets = new Grid<>(CardSet.class); + + Button save = new Button("Save"); + Button delete = new Button("Delete"); + Button close = new Button("Cancel"); + + Binder binder = new BeanValidationBinder<>(Vendor.class); + + public VendorForm() { + addClassName("vendor-form"); + binder.bindInstanceFields(this); + + cardSets.setColumns("name", "parallelSet"); + cardSets.getColumns().forEach(col -> col.setAutoWidth(true)); + add(name, cardSets, createButtonsLayout()); + } + + private HorizontalLayout createButtonsLayout() { + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + close.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + save.addClickShortcut(Key.ENTER); + close.addClickShortcut(Key.ESCAPE); + + save.addClickListener(event -> validateAndSave()); + delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean()))); + close.addClickListener(event -> fireEvent(new CloseEvent(this))); + + binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); + return new HorizontalLayout(save, delete, close); + } + + private void validateAndSave() { + if (binder.isValid()) { + fireEvent(new SaveEvent(this, binder.getBean())); + } + } + + public void setVendor(Vendor vendor) { + binder.setBean(vendor); + } + + public void setCardSets(List sets) { + log.info("Setting card sets: {}", sets); + this.cardSets.setItems(sets); + } + + public abstract static class VendorFormEvent extends ComponentEvent { + private Vendor vendor; + + protected VendorFormEvent(VendorForm source, Vendor vendor) { + super(source, false); + this.vendor = vendor; + } + + public Vendor getVendor() { + return vendor; + } + } + + public static class SaveEvent extends VendorFormEvent { + SaveEvent(VendorForm source, Vendor vendor) { + super(source, vendor); + } + } + + public static class DeleteEvent extends VendorFormEvent { + DeleteEvent(VendorForm source, Vendor vendor) { + super(source, vendor); + } + } + + public static class CloseEvent extends VendorFormEvent { + CloseEvent(VendorForm source) { + super(source, null); + } + } + + public void addDeleteListener(ComponentEventListener listener) { + addListener(DeleteEvent.class, listener); + } + + public void addSaveListener(ComponentEventListener listener) { + addListener(SaveEvent.class, listener); + } + + public void addCloseListener(ComponentEventListener listener) { + addListener(CloseEvent.class, listener); + } +} diff --git a/springboot/src/main/java/de/thpeetz/kontor/tysc/views/VendorView.java b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/VendorView.java new file mode 100644 index 0000000..08b5d32 --- /dev/null +++ b/springboot/src/main/java/de/thpeetz/kontor/tysc/views/VendorView.java @@ -0,0 +1,130 @@ +package de.thpeetz.kontor.tysc.views; + +import org.springframework.context.annotation.Scope; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.annotation.SpringComponent; + +import de.thpeetz.kontor.common.views.MainLayout; +import de.thpeetz.kontor.tysc.TyscConstants; +import de.thpeetz.kontor.tysc.data.Vendor; +import de.thpeetz.kontor.tysc.services.CardService; +import jakarta.annotation.security.PermitAll; + +@SpringComponent +@Scope("prototype") +@PermitAll +@Route(value = TyscConstants.VENDOR_ROUTE, layout = MainLayout.class) +@PageTitle("Vendor | Tysc | Kontor") +public class VendorView extends VerticalLayout { + + Grid grid = new Grid<>(Vendor.class); + TextField filterText = new TextField(); + VendorForm form; + CardService service; + + public VendorView(CardService service) { + this.service = service; + addClassName("vendor-view"); + setSizeFull(); + configureGrid(); + configureForm(); + + add(getToolbar(), getContent()); + updateList(); + } + + public Grid getGrid() { + return grid; + } + + private void configureGrid() { + grid.addClassName("vendor-grid"); + grid.setSizeFull(); + grid.setColumns("name"); + grid.getColumns().forEach(col -> col.setAutoWidth(true)); + grid.asSingleSelect().addValueChangeListener(event -> editVendor(event.getValue())); + } + + public VendorForm getForm() { + return form; + } + + private void configureForm() { + form = new VendorForm(); + form.setWidth("25em"); + form.setVisible(false); + form.addSaveListener(this::saveVendor); + form.addDeleteListener(this::deleteVendor); + form.addCloseListener(e -> closeEditor()); + } + + private void saveVendor(VendorForm.SaveEvent event) { + service.saveVendor(event.getVendor()); + updateList(); + closeEditor(); + } + + private void deleteVendor(VendorForm.DeleteEvent event) { + service.deleteVendor(event.getVendor()); + updateList(); + closeEditor(); + } + + private Component getContent() { + HorizontalLayout content = new HorizontalLayout(grid, form); + content.setFlexGrow(2, grid); + content.setFlexGrow(1, form); + content.addClassName("content"); + content.setSizeFull(); + return content; + } + + private HorizontalLayout getToolbar() { + filterText.setPlaceholder("Filter by name..."); + filterText.setClearButtonVisible(true); + filterText.setValueChangeMode(ValueChangeMode.LAZY); + filterText.addValueChangeListener(e -> updateList()); + + Button addVendorButton = new Button("Add vendor"); + addVendorButton.addClickListener(click -> addVendor()); + + HorizontalLayout toolbar = new HorizontalLayout(filterText, addVendorButton); + toolbar.addClassName("toolbar"); + return toolbar; + } + + public void editVendor(Vendor vendor) { + if (vendor == null) { + closeEditor(); + } else { + form.setVendor(vendor); + form.setCardSets(vendor.getCardSets()); + form.setVisible(true); + addClassName("editing"); + } + } + + private void closeEditor() { + form.setVendor(null); + form.setVisible(false); + removeClassName("editing"); + } + + private void addVendor() { + grid.asSingleSelect().clear(); + editVendor(new Vendor()); + } + + public void updateList() { + grid.setItems(service.findAllVendors(filterText.getValue())); + } +} diff --git a/springboot/src/main/resources/META-INF/resources/icons/icon.png b/springboot/src/main/resources/META-INF/resources/icons/icon.png new file mode 100644 index 0000000..77bb2c9 Binary files /dev/null and b/springboot/src/main/resources/META-INF/resources/icons/icon.png differ diff --git a/springboot/src/main/resources/META-INF/resources/images/offline.png b/springboot/src/main/resources/META-INF/resources/images/offline.png new file mode 100644 index 0000000..723e24c Binary files /dev/null and b/springboot/src/main/resources/META-INF/resources/images/offline.png differ diff --git a/springboot/src/main/resources/META-INF/resources/offline.html b/springboot/src/main/resources/META-INF/resources/offline.html new file mode 100644 index 0000000..791d1e8 --- /dev/null +++ b/springboot/src/main/resources/META-INF/resources/offline.html @@ -0,0 +1,38 @@ + + + + + + + Offline | Vaadin CRM + + + + +
+ VaadinCRM is offline +

Oh deer, you're offline

+

Your internet connection is offline. Get back online to continue using Vaadin CRM.

+
+ + + diff --git a/springboot/src/main/resources/application.yml b/springboot/src/main/resources/application.yml new file mode 100644 index 0000000..fd8dfff --- /dev/null +++ b/springboot/src/main/resources/application.yml @@ -0,0 +1,67 @@ +server: + port: 8085 +app: + name: 'Kontor' + shortName: 'Kontor' + description: 'Kontor is a Spring Boot application' +spring: + profiles: + active: local,dev,test,prod + devtools: + add-properties: false + datasource: + driverClassName: org.mariadb.jdbc.Driver + url: jdbc:mariadb://localhost:3306/kontor + username: 'kontor' + password: 'kontor' + #driverClassName: org.hsqldb.jdbc.JDBCDriver + #url: jdbc:hsqldb:file:kontorHSQLDB + #username: 'sa' + #password: 'sa' + #jpa + #database-platform: org.hibernate.community.dialect.SQLiteDialect + #datasource + #driverClassName: org.sqlite.JDBC + #url: "jdbc:sqlite:file:./kontorDb?cache=shared" + #username=sa + #password=sa + jpa: + defer-datasource-initialization: true + #hibernate.ddl-auto=create-drop + hibernate: + ddl-auto: update + #ddl-auto: create-drop + show-sql: false + sql: + init: + mode: never + mustache: + check-template-location: false +management: + endpoints: + web: + exposure: + include: "*" + endpoint: + health: + show-details: always +logging: + level: + org: + atmosphere: INFO + hibernate: INFO + springframework: + web: INFO + guru: + springframework: + controllers: DEBUG +jwt: + auth: + secret: 'J6GOtcwC2NJI1l0VkHu20PacPFGTxpirBxWwynoHjsc=' +mail: + protocol: 'imap' + host: 'corky.svpdata.eu' + port: 143 + userName: 'thomas.peetz@thpeetz.de' + password: 'fS9f4JYDIO7A' + starttls: true diff --git a/springboot/src/main/resources/banner.txt b/springboot/src/main/resources/banner.txt new file mode 100644 index 0000000..b21c682 --- /dev/null +++ b/springboot/src/main/resources/banner.txt @@ -0,0 +1,8 @@ +,--. ,--. ,--. +| .' / ,---. ,--,--, ,-' '-. ,---. ,--.--. +| . ' | .-. || \'-. .-'| .-. || .--' +| |\ \' '-' '| || | | | ' '-' '| | +`--' '--' `---' `--''--' `--' `---' `--' + +${application.title} ${application.version} +Powered by Spring Boot ${spring-boot.version} \ No newline at end of file diff --git a/springboot/src/main/resources/logback-spring.xml b/springboot/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..b085f8c --- /dev/null +++ b/springboot/src/main/resources/logback-spring.xml @@ -0,0 +1,42 @@ + + + + + + + + + %white(%d{ISO8601}) %highlight(%-5level) [%green(%t)] %yellow(%C{1}) : %msg%n%throwable + + + + + + ${LOGS}/spring-boot-logger.log + + %d %p %C{1} [%t] %m%n + + + + + ${LOGS}/archived/spring-boot-logger-%d{yyyy-MM-dd}.%i.log + + + 10MB + + + + + + + + + + + + diff --git a/springboot/src/test/java/de/thpeetz/kontor/ApplicationTests.java b/springboot/src/test/java/de/thpeetz/kontor/ApplicationTests.java new file mode 100644 index 0000000..abf5c95 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/ApplicationTests.java @@ -0,0 +1,13 @@ +package de.thpeetz.kontor; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ApplicationTests { + + @Test + void contextLoads() { + + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/bookshelf/TestConstants.java b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/TestConstants.java new file mode 100644 index 0000000..bb9d04d --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/TestConstants.java @@ -0,0 +1,19 @@ +package de.thpeetz.kontor.bookshelf; + +public class TestConstants { + + public static final Integer ARTICLEAUTHOR_COUNT = 0; + public static final Integer ARTICLE_COUNT = 0; + public static final Integer AUTHOR_COUNT = 1; + public static final Integer BOOKAUTHOR_COUNT = 0; + public static final Integer BOOK_COUNT = 0; + public static final Integer PUBLISHER_COUNT = 0; + public static final String ARTICLE_TITLE = "Title"; + public static final String AUTHOR_FIRSTNAME = "Firstname"; + public static final String AUTHOR_LASTNAME = "Lastname"; + public static final String PUBLISHER_NIKOL = "Nikol Verlags GmbH"; + public static final String PUBLISHER_NAME = "Publisher"; + public static final String PUBLISHER_SEARCH = "kol"; + public static final String BOOK_TITLE = "Book Title"; + public static final String BOOK_ISBN = "978-3-123-467-890"; +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/ArticleAuthorRepositoryTest.java b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/ArticleAuthorRepositoryTest.java new file mode 100644 index 0000000..b54b976 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/ArticleAuthorRepositoryTest.java @@ -0,0 +1,74 @@ +package de.thpeetz.kontor.bookshelf.data; + +import de.thpeetz.kontor.bookshelf.TestConstants; +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@Disabled +class ArticleAuthorRepositoryTest { + + @Autowired + ArticleAuthorRepository articleAuthorRepository; + + @Autowired + AuthorRepository authorRepository; + + @Autowired + ArticleRepository articleRepository; + + @Test + @Order(1) + void saveArticleAuthor() { + Author author = new Author(); + author.setFirstName(TestConstants.AUTHOR_FIRSTNAME); + author.setLastName(TestConstants.AUTHOR_LASTNAME); + Author savedAuthor = authorRepository.save(author); + assertEquals(TestConstants.AUTHOR_COUNT+1, authorRepository.findAll().size()); + Article article = new Article(); + article.setTitle(TestConstants.ARTICLE_TITLE); + Article savedArticle = articleRepository.save(article); + ArticleAuthor articleAuthor = new ArticleAuthor(); + articleAuthor.setArticle(article); + articleAuthor.setAuthor(author); + ArticleAuthor savedArticleAuthor = articleAuthorRepository.save(articleAuthor); + assertEquals(TestConstants.ARTICLEAUTHOR_COUNT+1, articleAuthorRepository.findAll().size()); + } + + @Test + @Order(2) + void testFindByArticle() { + Article article = articleRepository.findByTitle(TestConstants.ARTICLE_TITLE).get(0); + assertEquals(1, articleAuthorRepository.findByArticle(article).size()); + } + + @Test + @Order(3) + void testFindByAuthor() { + Author author = authorRepository.findByFirstNameAndLastName(TestConstants.AUTHOR_FIRSTNAME, TestConstants.AUTHOR_LASTNAME); + assertEquals(1, articleAuthorRepository.findByAuthor(author).size()); + } + + @Test + @Order(4) + void deleteArticleAuthor() { + assertEquals(1, articleAuthorRepository.findAll().size()); + ArticleAuthor articleAuthor = articleAuthorRepository.findAll().get(0); + Author author = articleAuthor.getAuthor(); + author.getArticleAuthors().remove(articleAuthor); + author = authorRepository.save(author); + Article article = articleAuthor.getArticle(); + article.getAuthors().remove(articleAuthor); + article = articleRepository.save(article); + articleAuthorRepository.delete(articleAuthor); + assertEquals(TestConstants.ARTICLEAUTHOR_COUNT, articleAuthorRepository.findAll().size()); + authorRepository.delete(author); + assertEquals(TestConstants.AUTHOR_COUNT, authorRepository.findAll().size()); + articleRepository.delete(article); + assertEquals(TestConstants.ARTICLE_COUNT, articleRepository.findAll().size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/ArticleAuthorTest.java b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/ArticleAuthorTest.java new file mode 100644 index 0000000..bd14fd3 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/ArticleAuthorTest.java @@ -0,0 +1,77 @@ +package de.thpeetz.kontor.bookshelf.data; + +import de.thpeetz.kontor.bookshelf.TestConstants; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.transaction.TransactionSystemException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest +@TestMethodOrder(OrderAnnotation.class) +@Disabled +class ArticleAuthorTest { + + @Autowired + ArticleRepository articleRepository; + + @Autowired + AuthorRepository authorRepository; + + @Autowired + ArticleAuthorRepository articleAuthorRepository; + + @Test + @Order(1) + void checkInitialLoad() { + assertEquals(TestConstants.ARTICLEAUTHOR_COUNT, articleAuthorRepository.findAll().size()); + } + + @Test + @Order(2) + void exceptionThrownWhenSavingEmptyValues() { + ArticleAuthor articleAuthor = new ArticleAuthor(); + assertThrows(TransactionSystemException.class, () -> { + articleAuthorRepository.save(articleAuthor); + }); + } + + @Test + @Order(3) + void exceptionThrownWhenSavingIdenticalEntry() { + Author author = new Author(); + author.setFirstName(TestConstants.AUTHOR_FIRSTNAME); + author.setLastName(TestConstants.AUTHOR_LASTNAME); + Author savedAuthor = authorRepository.save(author); + Article article = new Article(); + article.setTitle(TestConstants.ARTICLE_TITLE); + Article savedArticle = articleRepository.save(article); + ArticleAuthor articleAuthor = new ArticleAuthor(); + articleAuthor.setArticle(article); + articleAuthor.setAuthor(author); + ArticleAuthor savedArticleAuthor = articleAuthorRepository.save(articleAuthor); + ArticleAuthor articleAuthor1 = new ArticleAuthor(); + articleAuthor1.setArticle(article); + articleAuthor1.setAuthor(author); + assertThrows(DataIntegrityViolationException.class, () -> { + articleAuthorRepository.save(articleAuthor1); + }); + articleAuthorRepository.delete(savedArticleAuthor); + assertEquals(TestConstants.ARTICLEAUTHOR_COUNT, articleAuthorRepository.findAll().size()); + savedAuthor.getArticleAuthors().remove(savedArticleAuthor); + authorRepository.save(savedAuthor); + authorRepository.delete(savedAuthor); + assertEquals(TestConstants.AUTHOR_COUNT, authorRepository.findAll().size()); + savedArticle.getAuthors().remove(savedArticleAuthor); + articleRepository.save(savedArticle); + articleRepository.delete(savedArticle); + assertEquals(TestConstants.ARTICLE_COUNT, articleRepository.findAll().size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/ArticleRepositoryTest.java b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/ArticleRepositoryTest.java new file mode 100644 index 0000000..7d74119 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/ArticleRepositoryTest.java @@ -0,0 +1,61 @@ +package de.thpeetz.kontor.bookshelf.data; + +import de.thpeetz.kontor.bookshelf.TestConstants; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class ArticleRepositoryTest { + + @Autowired + ArticleRepository articleRepository; + + @Test + @Order(1) + void checkInitialLoad() { + assertEquals(TestConstants.ARTICLE_COUNT, articleRepository.findAll().size()); + } + + @Test + @Order(2) + void saveArticle() { + Article article = new Article(); + article.setTitle(TestConstants.ARTICLE_TITLE); + articleRepository.save(article); + assertEquals(TestConstants.ARTICLE_COUNT+1, articleRepository.findAll().size()); + } + + @Test + @Order(3) + void search() { + List
articles = articleRepository.search(TestConstants.ARTICLE_TITLE.substring(2,5)); + assertEquals(1, articles.size()); + assertEquals(0, articleRepository.search(TestConstants.BOOK_TITLE).size()); + } + + @Test + @Order(4) + void findByTitle() { + List
articles = articleRepository.search(TestConstants.ARTICLE_TITLE); + assertEquals(1, articles.size()); + } + + @Test + @Order(5) + void deleteArticle() { + List
articles = articleRepository.findAll(); + assertEquals(1, articles.size()); + assertEquals(0, articles.get(0).getAuthors().size()); + articleRepository.delete(articles.get(0)); + assertEquals(TestConstants.ARTICLE_COUNT, articleRepository.findAll().size()); + } +} \ No newline at end of file diff --git a/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/ArticleTest.java b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/ArticleTest.java new file mode 100644 index 0000000..2179dfe --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/ArticleTest.java @@ -0,0 +1,45 @@ +package de.thpeetz.kontor.bookshelf.data; + +import de.thpeetz.kontor.bookshelf.TestConstants; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.transaction.TransactionSystemException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest +class ArticleTest { + + @Autowired + ArticleRepository articleRepository; + + @Test + void checkInitialLoad() { + assertEquals(TestConstants.BOOK_COUNT, articleRepository.findAll().size()); + } + + @Test + void exceptionThrownWhenSavingEmptyFields() { + Article article = new Article(); + assertThrows(TransactionSystemException.class, () -> { + articleRepository.save(article); + }); + } + + @Test + void exceptionThrownWhenSavingIdenticalTitle() { + Article article = new Article(); + article.setTitle(TestConstants.ARTICLE_TITLE); + Article savedArticle = articleRepository.save(article); + Article article1 = new Article(); + article1.setTitle(article.getTitle()); + assertThrows(DataIntegrityViolationException.class, ()-> { + articleRepository.save(article1); + }); + articleRepository.delete(savedArticle); + assertEquals(TestConstants.ARTICLE_COUNT, articleRepository.findAll().size()); + } +} \ No newline at end of file diff --git a/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/AuthorRepositoryTest.java b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/AuthorRepositoryTest.java new file mode 100644 index 0000000..f984d11 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/AuthorRepositoryTest.java @@ -0,0 +1,69 @@ +package de.thpeetz.kontor.bookshelf.data; + +import de.thpeetz.kontor.bookshelf.TestConstants; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@SpringBootTest +@TestMethodOrder(OrderAnnotation.class) +@Disabled +class AuthorRepositoryTest { + + @Autowired + AuthorRepository authorRepository; + + @Test + @Order(1) + void checkInitialLoad() { + assertEquals(TestConstants.AUTHOR_COUNT, authorRepository.findAll().size()); + } + + @Test + @Order(2) + void saveAutor() { + Author author = new Author(); + author.setFirstName(TestConstants.AUTHOR_FIRSTNAME); + author.setLastName(TestConstants.AUTHOR_LASTNAME); + authorRepository.save(author); + assertEquals(TestConstants.AUTHOR_COUNT+1, authorRepository.findAll().size()); + } + + @Test + @Order(3) + void findAuthor() { + Author existingAuthor = authorRepository.findAll().get(0); + assertNotNull(existingAuthor); + Author found = authorRepository.findByFirstNameAndLastName(existingAuthor.getFirstName(), existingAuthor.getLastName()); + assertEquals(existingAuthor, found); + } + + @Test + @Order(4) + void searchAuthor() { + List authors = authorRepository.search("glas"); + assertEquals(1, authors.size()); + assertEquals("Douglas", authors.get(0).getFirstName()); + List authors2 = authorRepository.search("dams"); + assertEquals(1, authors2.size()); + assertEquals("Adams", authors2.get(0).getLastName()); + } + + @Test + @Order(5) + void deleteAuthor() { + Author existingAuthor = authorRepository.findByFirstNameAndLastName(TestConstants.AUTHOR_FIRSTNAME, TestConstants.AUTHOR_LASTNAME); + assertNotNull(existingAuthor); + authorRepository.delete(existingAuthor); + assertEquals(TestConstants.AUTHOR_COUNT, authorRepository.findAll().size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/AuthorTest.java b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/AuthorTest.java new file mode 100644 index 0000000..149c263 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/AuthorTest.java @@ -0,0 +1,44 @@ +package de.thpeetz.kontor.bookshelf.data; + +import de.thpeetz.kontor.bookshelf.TestConstants; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.transaction.TransactionSystemException; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +class AuthorTest { + + @Autowired + private AuthorRepository authorRepository; + + @Test + void throwExceptionWhenPlayerSavedWithEmptyName() { + Author author = new Author(); + assertThrows(TransactionSystemException.class, () -> { + authorRepository.save(author); + }); + } + + @Test + void throwExceptionWhenPlayerSavedWithExistingName() { + assertEquals(TestConstants.AUTHOR_COUNT, authorRepository.findAll().size()); + Author existingAuthor = new Author(); + existingAuthor.setFirstName(TestConstants.AUTHOR_FIRSTNAME); + existingAuthor.setLastName(TestConstants.AUTHOR_LASTNAME); + existingAuthor = authorRepository.save(existingAuthor); + assertEquals(TestConstants.AUTHOR_COUNT+1, authorRepository.findAll().size()); + Author author = new Author(); + author.setFirstName(existingAuthor.getFirstName()); + author.setLastName(existingAuthor.getLastName()); + assertThrows(DataIntegrityViolationException.class, () -> { + authorRepository.save(author); + }); + assertNotNull(existingAuthor); + authorRepository.delete(existingAuthor); + assertEquals(TestConstants.AUTHOR_COUNT, authorRepository.findAll().size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/BookAuthorRepositoryTest.java b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/BookAuthorRepositoryTest.java new file mode 100644 index 0000000..4df5fd9 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/BookAuthorRepositoryTest.java @@ -0,0 +1,94 @@ +package de.thpeetz.kontor.bookshelf.data; + +import de.thpeetz.kontor.bookshelf.TestConstants; +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@Disabled +class BookAuthorRepositoryTest { + + @Autowired + BookRepository bookRepository; + + @Autowired + AuthorRepository authorRepository; + + @Autowired + BookAuthorRepository bookAuthorRepository; + + @Autowired + BookshelfPublisherRepository bookshelfPublisherRepository; + + @Test + @Order(1) + void saveBookAuthor() { + Author author = new Author(); + author.setFirstName(TestConstants.AUTHOR_FIRSTNAME); + author.setLastName(TestConstants.AUTHOR_LASTNAME); + Author savedAuthor = authorRepository.save(author); + assertEquals(TestConstants.AUTHOR_COUNT+1, authorRepository.findAll().size()); + BookshelfPublisher publisher = new BookshelfPublisher(); + publisher.setName(TestConstants.PUBLISHER_NAME); + BookshelfPublisher savedPublisher = bookshelfPublisherRepository.save(publisher); + assertEquals(TestConstants.PUBLISHER_COUNT+1, bookshelfPublisherRepository.findAll().size()); + Book book = new Book(); + book.setTitle(TestConstants.ARTICLE_TITLE); + book.setIsbn(TestConstants.BOOK_ISBN); + book.setPublisher(savedPublisher); + Book savedBook = bookRepository.save(book); + assertEquals(TestConstants.BOOK_COUNT+1, bookRepository.findAll().size()); + BookAuthor bookAuthor = new BookAuthor(); + bookAuthor.setBook(savedBook); + bookAuthor.setAuthor(savedAuthor); + BookAuthor savedBookAuthor = bookAuthorRepository.save(bookAuthor); + assertEquals(TestConstants.BOOKAUTHOR_COUNT+1, bookAuthorRepository.findAll().size()); + } + + @Test + @Order(2) + void testFindByBook() { + List bookAuthors = bookAuthorRepository.findAll(); + assertEquals(1, bookAuthors.size()); + Book book = bookAuthors.get(0).getBook(); + assertEquals(1, bookAuthorRepository.findByBook(book).size()); + } + + @Test + @Order(3) + void testFindByAuthor() { + List bookAuthors = bookAuthorRepository.findAll(); + assertEquals(1, bookAuthors.size()); + Author author = bookAuthors.get(0).getAuthor(); + assertEquals(1, bookAuthorRepository.findByAuthor(author).size()); + } + + @Test + @Order(4) + void deleteBookAuthor() { + BookAuthor bookAuthor = bookAuthorRepository.findAll().get(0); + Author author = bookAuthor.getAuthor(); + author.getBookAuthors().remove(bookAuthor); + author = authorRepository.save(author); + Book book = bookAuthor.getBook(); + book.getAuthors().remove(bookAuthor); + BookshelfPublisher publisher = book.getPublisher(); + publisher.getBooks().remove(book); + bookshelfPublisherRepository.save(publisher); + book = bookRepository.save(book); + bookshelfPublisherRepository.delete(publisher); + assertEquals(TestConstants.PUBLISHER_COUNT, bookshelfPublisherRepository.findAll().size()); + bookAuthorRepository.delete(bookAuthor); + assertEquals(TestConstants.BOOKAUTHOR_COUNT, bookAuthorRepository.findAll().size()); + authorRepository.delete(author); + assertEquals(TestConstants.AUTHOR_COUNT, authorRepository.findAll().size()); + bookRepository.delete(book); + assertEquals(TestConstants.BOOK_COUNT, bookRepository.findAll().size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/BookAuthorTest.java b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/BookAuthorTest.java new file mode 100644 index 0000000..dc79341 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/BookAuthorTest.java @@ -0,0 +1,82 @@ +package de.thpeetz.kontor.bookshelf.data; + +import de.thpeetz.kontor.bookshelf.TestConstants; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.transaction.TransactionSystemException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest +@Disabled +class BookAuthorTest { + + @Autowired + BookRepository bookRepository; + + @Autowired + AuthorRepository authorRepository; + + @Autowired + BookAuthorRepository bookAuthorRepository; + + @Autowired + BookshelfPublisherRepository bookshelfPublisherRepository; + + @Test + void checkInitialLoad() { + assertEquals(TestConstants.BOOKAUTHOR_COUNT, bookAuthorRepository.findAll().size()); + } + + @Test + void exceptionThrownWhenSavingEmptyValues() { + BookAuthor bookAuthor = new BookAuthor(); + assertThrows(TransactionSystemException.class, () -> { + bookAuthorRepository.save(bookAuthor); + }); + } + + @Test + void exceptionThrownWhenSavingIdenticalEntry() { + Author author = new Author(); + author.setFirstName(TestConstants.AUTHOR_FIRSTNAME); + author.setLastName(TestConstants.AUTHOR_LASTNAME); + Author savedAuthor = authorRepository.save(author); + BookshelfPublisher publisher = new BookshelfPublisher(); + publisher.setName(TestConstants.PUBLISHER_NAME); + BookshelfPublisher savedPublisher = bookshelfPublisherRepository.save(publisher); + Book book = new Book(); + book.setTitle(TestConstants.BOOK_TITLE); + book.setIsbn(TestConstants.BOOK_ISBN); + book.setPublisher(publisher); + Book savedBook = bookRepository.save(book); + BookAuthor bookAuthor = new BookAuthor(); + bookAuthor.setBook(book); + bookAuthor.setAuthor(author); + BookAuthor savedBookAuthor = bookAuthorRepository.save(bookAuthor); + BookAuthor bookAuthor1 = new BookAuthor(); + bookAuthor1.setBook(book); + bookAuthor1.setAuthor(author); + assertThrows(DataIntegrityViolationException.class, () -> { + bookAuthorRepository.save(bookAuthor1); + }); + savedAuthor.getBookAuthors().remove(savedBookAuthor); + authorRepository.save(savedAuthor); + savedBook.getAuthors().remove(savedBookAuthor); + bookRepository.save(savedBook); + savedPublisher.getBooks().remove(savedBook); + bookshelfPublisherRepository.save(savedPublisher); + authorRepository.delete(savedAuthor); + assertEquals(TestConstants.AUTHOR_COUNT, authorRepository.findAll().size()); + bookRepository.delete(savedBook); + assertEquals(TestConstants.ARTICLE_COUNT, bookRepository.findAll().size()); + bookshelfPublisherRepository.delete(savedPublisher); + assertEquals(TestConstants.PUBLISHER_COUNT, bookshelfPublisherRepository.findAll().size()); + bookAuthorRepository.delete(savedBookAuthor); + assertEquals(TestConstants.ARTICLEAUTHOR_COUNT, bookAuthorRepository.findAll().size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/BookRepositoryTest.java b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/BookRepositoryTest.java new file mode 100644 index 0000000..969d465 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/BookRepositoryTest.java @@ -0,0 +1,87 @@ +package de.thpeetz.kontor.bookshelf.data; + +import de.thpeetz.kontor.bookshelf.TestConstants; +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@Disabled +class BookRepositoryTest { + + @Autowired + BookRepository bookRepository; + + @Autowired + BookshelfPublisherRepository bookshelfPublisherRepository; + + @Test + @Order(1) + void checkInitialLoad(){ + assertEquals(TestConstants.BOOK_COUNT, bookRepository.findAll().size()); + } + + @Test + @Order(2) + void saveBook() { + BookshelfPublisher publisher = new BookshelfPublisher(); + publisher.setName(TestConstants.PUBLISHER_NAME); + bookshelfPublisherRepository.save(publisher); + Book book = new Book(); + book.setTitle(TestConstants.BOOK_TITLE); + book.setIsbn(TestConstants.BOOK_ISBN); + book.setPublisher(publisher); + bookRepository.save(book); + assertEquals(TestConstants.BOOK_COUNT+1, bookRepository.findAll().size()); + } + + @Test + @Order(3) + void search() { + List books = bookRepository.search(TestConstants.BOOK_TITLE.substring(2,5)); + assertEquals(1, books.size()); + assertEquals(1, bookRepository.search(TestConstants.BOOK_ISBN.substring(3,7)).size()); + assertEquals(0, bookRepository.search("Article").size()); + } + + @Test + @Order(4) + void findByTitle() { + List books = bookRepository.findByTitle(TestConstants.BOOK_TITLE); + assertEquals(1, books.size()); + assertEquals(TestConstants.BOOK_ISBN, books.get(0).getIsbn()); + } + + @Test + @Order(5) + void findByTitleIgnoreCase() { + List books = bookRepository.findByTitleIgnoreCase(TestConstants.BOOK_TITLE.toLowerCase()); + assertEquals(1, books.size()); + assertEquals(TestConstants.BOOK_ISBN, books.get(0).getIsbn()); + } + + @Test + @Order(6) + void findByIsbn() { + List books = bookRepository.findByIsbn(TestConstants.BOOK_ISBN); + assertEquals(1, books.size()); + assertEquals(TestConstants.BOOK_TITLE, books.get(0).getTitle()); + } + + @Test + @Order(7) + void deleteBook() { + List books = bookRepository.findByIsbn(TestConstants.BOOK_ISBN); + Book book = books.get(0); + BookshelfPublisher publisher = book.getPublisher(); + bookshelfPublisherRepository.delete(publisher); + bookRepository.delete(book); + assertEquals(TestConstants.BOOK_COUNT, bookRepository.findAll().size()); + assertEquals(TestConstants.PUBLISHER_COUNT, bookshelfPublisherRepository.findAll().size()); + } +} \ No newline at end of file diff --git a/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/BookTest.java b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/BookTest.java new file mode 100644 index 0000000..0ac3324 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/BookTest.java @@ -0,0 +1,56 @@ +package de.thpeetz.kontor.bookshelf.data; + +import de.thpeetz.kontor.bookshelf.TestConstants; +import de.thpeetz.kontor.comics.data.Publisher; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.orm.jpa.JpaSystemException; +import org.springframework.transaction.TransactionSystemException; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +class BookTest { + + @Autowired + BookRepository bookRepository; + + @Autowired + BookshelfPublisherRepository bookshelfPublisherRepository; + + @Test + void checkInitialLoad() { + assertEquals(TestConstants.BOOK_COUNT, bookRepository.findAll().size()); + } + + @Test + void exceptionThrownWhenSavingEmptyFields() { + Book book = new Book(); + assertThrows(TransactionSystemException.class, () -> { + bookRepository.save(book); + }); + } + + @Test + void exceptionThrownWhenSavingIdenticalISBN() { + BookshelfPublisher publisher = new BookshelfPublisher(); + publisher.setName(TestConstants.PUBLISHER_NAME); + BookshelfPublisher savedPublisher = bookshelfPublisherRepository.save(publisher); + Book book = new Book(); + book.setTitle(TestConstants.BOOK_TITLE); + book.setIsbn(TestConstants.BOOK_ISBN); + book.setPublisher(savedPublisher); + Book savedBook = bookRepository.save(book); + Book book1 = new Book(); + book1.setTitle(book.getTitle()); + book1.setIsbn(book.getIsbn()); + assertThrows(TransactionSystemException.class, ()-> { + bookRepository.save(book1); + }); + bookRepository.delete(book); + assertEquals(TestConstants.BOOK_COUNT, bookRepository.findAll().size()); + bookshelfPublisherRepository.delete(savedPublisher); + assertEquals(TestConstants.PUBLISHER_COUNT, bookshelfPublisherRepository.findAll().size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/BookshelfPublisherRepositoryTest.java b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/BookshelfPublisherRepositoryTest.java new file mode 100644 index 0000000..98b60fa --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/BookshelfPublisherRepositoryTest.java @@ -0,0 +1,72 @@ +package de.thpeetz.kontor.bookshelf.data; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import de.thpeetz.kontor.bookshelf.TestConstants; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@SpringBootTest +@TestMethodOrder(OrderAnnotation.class) +class BookshelfPublisherRepositoryTest { + + @Autowired + private BookshelfPublisherRepository publisherRepository; + + @Test + @Order(1) + void checkInitialLoad() { + assertEquals(TestConstants.PUBLISHER_COUNT, publisherRepository.findAll().size()); + } + + @Test + @Order(2) + void savePublisher() { + BookshelfPublisher publisher = new BookshelfPublisher(); + publisher.setName(TestConstants.PUBLISHER_NAME); + publisherRepository.save(publisher); + assertEquals(TestConstants.PUBLISHER_COUNT + 1, publisherRepository.findAll().size()); + } + + @Test + @Order(3) + void findPublisherByName() { + assertEquals(TestConstants.PUBLISHER_COUNT + 1, publisherRepository.findAll().size()); + log.info("Liste der Publisher: {}", publisherRepository.findAll()); + BookshelfPublisher publisher = publisherRepository.findByName(TestConstants.PUBLISHER_NAME); + assertNotNull(publisher); + assertEquals(TestConstants.PUBLISHER_NAME, publisher.getName()); + BookshelfPublisher notFound = publisherRepository.findByName(TestConstants.PUBLISHER_SEARCH); + assertNull(notFound); + List publishers = publisherRepository + .findByNameIgnoreCase(TestConstants.PUBLISHER_NAME.toLowerCase()); + assertEquals(1, publishers.size()); + assertEquals(TestConstants.PUBLISHER_NAME, publishers.get(0).getName()); + } + + @Test + @Order(4) + void searchPublisher() { + List publishers = publisherRepository.search(TestConstants.PUBLISHER_NAME.substring(2, 6)); + assertEquals(1, publishers.size()); + assertEquals(TestConstants.PUBLISHER_NAME, publishers.get(0).getName()); + } + + @Test + @Order(5) + void deletePublisher() { + BookshelfPublisher publisher = publisherRepository.findByName(TestConstants.PUBLISHER_NAME); + assertNotNull(publisher); + publisherRepository.delete(publisher); + assertEquals(TestConstants.PUBLISHER_COUNT, publisherRepository.findAll().size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/BookshelfPublisherTest.java b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/BookshelfPublisherTest.java new file mode 100644 index 0000000..7a73ae1 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/data/BookshelfPublisherTest.java @@ -0,0 +1,45 @@ +package de.thpeetz.kontor.bookshelf.data; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.transaction.TransactionSystemException; + +import de.thpeetz.kontor.bookshelf.TestConstants; + +@SpringBootTest +class BookshelfPublisherTest { + + @Autowired + private BookshelfPublisherRepository publisherRepository; + + @Test + void checkInitialDataLoad() { + assertEquals(TestConstants.PUBLISHER_COUNT, publisherRepository.findAll().size()); + } + + @Test + void throwExceptionWhenPublisherSavedWithEmptyName() { + BookshelfPublisher publisher = new BookshelfPublisher(); + assertThrows(TransactionSystemException.class, () -> { + publisherRepository.save(publisher); + }); + } + + @Test + void savePublisherWithIdenticalName() { + BookshelfPublisher publisher1 = new BookshelfPublisher(); + publisher1.setName(TestConstants.PUBLISHER_NAME); + publisherRepository.save(publisher1); + BookshelfPublisher publisher2 = new BookshelfPublisher(); + publisher2.setName(TestConstants.PUBLISHER_NAME); + assertThrows(DataIntegrityViolationException.class, () -> { + publisherRepository.save(publisher2); + }); + publisherRepository.delete(publisher1); + assertEquals(TestConstants.PUBLISHER_COUNT, publisherRepository.findAll().size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/bookshelf/services/BookshelfServiceTest.java b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/services/BookshelfServiceTest.java new file mode 100644 index 0000000..b78a899 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/bookshelf/services/BookshelfServiceTest.java @@ -0,0 +1,102 @@ +package de.thpeetz.kontor.bookshelf.services; + +import de.thpeetz.kontor.bookshelf.TestConstants; +import de.thpeetz.kontor.bookshelf.data.Author; +import de.thpeetz.kontor.bookshelf.data.Book; +import de.thpeetz.kontor.bookshelf.data.BookshelfPublisher; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@SpringBootTest +@TestMethodOrder(OrderAnnotation.class) +class BookshelfServiceTest { + + @Autowired + private BookshelfService bookshelfService; + + @Test + @Order(1) + void testFindAllPublishers() { + List publishers = bookshelfService.findAllPublishers(null); + assertEquals(TestConstants.PUBLISHER_COUNT, publishers.size()); + } + + @Test + @Order(2) + void testSavePublisher() { + BookshelfPublisher publisher = new BookshelfPublisher(); + publisher.setName(TestConstants.PUBLISHER_NAME); + bookshelfService.savePublisher(publisher); + assertEquals(TestConstants.PUBLISHER_COUNT + 1, bookshelfService.findAllPublishers(null).size()); + } + + @Test + @Order(3) + void testFindPublisherByName() { + assertEquals(TestConstants.PUBLISHER_COUNT + 1, bookshelfService.findAllPublishers(null).size()); + BookshelfPublisher publisher = bookshelfService.findPublisherByName(TestConstants.PUBLISHER_NAME); + assertNotNull(publisher); + } + + @Test + @Order(4) + void testDeletePublisher() { + List publishers = bookshelfService.findAllPublishers(TestConstants.PUBLISHER_NAME); + assertEquals(1, publishers.size()); + bookshelfService.deletePublisher(publishers.get(0)); + assertEquals(TestConstants.PUBLISHER_COUNT, bookshelfService.findAllPublishers(null).size()); + } + + @Test + @Order(5) + void testFindAllAuthors() { + assertEquals(TestConstants.AUTHOR_COUNT, bookshelfService.findAllAuthors(null).size()); + } + + @Test + @Order(6) + void testSaveAuthor() { + Author author = new Author(); + author.setFirstName(TestConstants.AUTHOR_FIRSTNAME); + author.setLastName(TestConstants.AUTHOR_LASTNAME); + bookshelfService.saveAuthor(author); + assertEquals(TestConstants.AUTHOR_COUNT+1, bookshelfService.findAllAuthors(null).size()); + } + + @Test + @Order(7) + void testDeleteAuthor() { + List authors = bookshelfService.findAllAuthors(TestConstants.AUTHOR_FIRSTNAME); + assertEquals(1, authors.size()); + bookshelfService.deleteAuthor(authors.get(0)); + assertEquals(TestConstants.AUTHOR_COUNT, bookshelfService.findAllAuthors(null).size()); + } + + @Test + @Order(8) + void findAllBooks() { + assertEquals(TestConstants.BOOK_COUNT, bookshelfService.findAllBooks(null).size()); + } + + @Test + @Order(9) + void saveBook() { + assertEquals(TestConstants.BOOK_COUNT, bookshelfService.findAllBooks(null).size()); + } + + @Test + @Order(10) + void deleteBook() { + List books = bookshelfService.findAllBooks(null); + assertEquals(0, books.size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/comics/ComicConstantsTest.java b/springboot/src/test/java/de/thpeetz/kontor/comics/ComicConstantsTest.java new file mode 100644 index 0000000..b1e50ce --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/comics/ComicConstantsTest.java @@ -0,0 +1,14 @@ +package de.thpeetz.kontor.comics; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class ComicConstantsTest { + + @Test + void getArtistConstants() { + assertEquals("Artist", ComicConstants.ARTIST); + assertEquals("comics/artist", ComicConstants.ARTIST_ROUTE); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/comics/TestConstants.java b/springboot/src/test/java/de/thpeetz/kontor/comics/TestConstants.java new file mode 100644 index 0000000..b77f55d --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/comics/TestConstants.java @@ -0,0 +1,67 @@ +package de.thpeetz.kontor.comics; + +import de.thpeetz.kontor.comics.data.Artist; +import de.thpeetz.kontor.comics.data.Comic; +import de.thpeetz.kontor.comics.data.Publisher; +import de.thpeetz.kontor.comics.data.Worktype; +import de.thpeetz.kontor.comics.services.ComicService; + +public class TestConstants { + public static final String ARTIST_NAME = "Lastname, Firstname"; + public static final String COMIC_TITLE = "TestComic"; + public static final String WORKTYPE_INKER = "Inker"; + public static final String ISSUE_ISSUENUMBER = "Issuenumber"; + public static final String PUBLISHER_NAME = "Publisher"; + public static final String SOJOURN_TPB_COMIC_TITLE = "Sojourn"; + public static final String SOJOURN_TPB_NAME = "The Dragons Tale"; + public static final String STORYARC_NAME = "StoryArc"; + public static final String TRADEPAPERBACK_NAME = "TradePaperback"; + public static final String VOLUME_NAME = "Volume"; + public static final String WORKTYPE_NAME = "Worktype"; + public static final int ARTIST_COUNT = 5; + public static final int BATTLE_POPE_ISSUE_COUNT = 12; + public static final int COMIC_COUNT = 169; + public static final int COMICWORK_COUNT = 18; + public static final int ISSUE_COUNT = 750; + public static final int MARVEL_COMIC_COUNT = 50; + public static final int PUBLISHER_COUNT = 18; + public static final int SOJOURN_TPB_COUNT = 4; + public static final int SOJOURN_TPB_START = 7; + public static final int SOJOURN_TPB_END = 12; + public static final int STORYARC_COUNT = 3; + public static final int TRADEPAPERBACK_COUNT = 40; + public static final int VOLUME_COUNT = 0; + public static final int WORKTYPE_COUNT = 3; + + public static Comic getComicWithStoryArcs(ComicService service) { + return service.findComicByTitle("Emma Frost"); + } + + public static Comic getComicWithTradePaperbacks(ComicService service) { + return service.findComicByTitle(SOJOURN_TPB_COMIC_TITLE); + } + + public static Comic getComicWithIssues(ComicService service) { + return service.findComicByTitle("Battle Pope"); + } + + public static Comic getComicWithoutReferences(ComicService service) { + return service.findComicByTitle("Gen13"); + } + + public static Artist getArtistWithoutReferences(ComicService service) { + return service.findArtistByName("Marz, Ron"); + } + + public static Worktype getWorktypeWithoutReferences(ComicService service) { + return service.findWorktypeByName(WORKTYPE_INKER); + } + + public static Publisher getPublisherForComicTests(ComicService service) { + return service.findPublisherByName("Marvel"); + } + + public static Publisher getAnotherPublisherForComicTests(ComicService service) { + return service.findPublisherByName("DC"); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/comics/data/ArtistRepositoryTest.java b/springboot/src/test/java/de/thpeetz/kontor/comics/data/ArtistRepositoryTest.java new file mode 100644 index 0000000..96d53f8 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/comics/data/ArtistRepositoryTest.java @@ -0,0 +1,73 @@ +package de.thpeetz.kontor.comics.data; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +@TestMethodOrder(OrderAnnotation.class) +class ArtistRepositoryTest { + + private Artist stanLee; + private static final String ARTISTNAME = "Lee, Stan"; + + @Autowired + ArtistRepository artistRepository; + + @BeforeEach + void setupData() { + stanLee = new Artist(); + stanLee.setName(ARTISTNAME); + } + + @Test + @Order(1) + void checkInitialLoad() { + int count = artistRepository.findAll().size(); + assertEquals(5, count); + } + + @Test + @Order(2) + void saveArtist() { + int count = artistRepository.findAll().size(); + artistRepository.save(stanLee); + assertEquals(count+1, artistRepository.findAll().size()); + } + + @Test + @Order(3) + void findArtist() { + List artists = artistRepository.findByNameIgnoreCase("lEE, sTAN"); + assertTrue(artists.size() > 0); + assertEquals(artists.get(0).getName(), stanLee.getName()); + } + + @Test + @Order(4) + void searchArtist() { + List artists = artistRepository.search("Lee"); + assertEquals(1, artists.size()); + assertEquals(ARTISTNAME, artists.get(0).getName()); + List artists2 = artistRepository.search("Stan"); + assertEquals(1, artists2.size()); + assertEquals(ARTISTNAME, artists2.get(0).getName()); + } + + @Test + @Order(5) + void deleteArtist() { + int count = artistRepository.findAll().size(); + Artist artist = artistRepository.findByName(ARTISTNAME); + artistRepository.delete(artist); + assertEquals(count-1, artistRepository.findAll().size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/comics/data/ArtistTest.java b/springboot/src/test/java/de/thpeetz/kontor/comics/data/ArtistTest.java new file mode 100644 index 0000000..855795e --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/comics/data/ArtistTest.java @@ -0,0 +1,48 @@ +package de.thpeetz.kontor.comics.data; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.transaction.TransactionSystemException; + +import de.thpeetz.kontor.comics.TestConstants; + +@SpringBootTest +class ArtistTest { + + @Autowired + private ArtistRepository artistRepository; + + @Test + void checkInitialDataLoad() { + List artists = artistRepository.findAll(); + assertEquals(TestConstants.ARTIST_COUNT, artists.size()); + } + + @Test + void throwExceptionWhenArtistSavedWithEmptyName() { + Artist artist1 = new Artist(); + assertThrows(TransactionSystemException.class, () -> { + artistRepository.save(artist1); + }); + } + + @Test + void saveArtistWithIdenticalName() { + String artistName = "Lastname, Firstname"; + Artist artist1 = new Artist(); + artist1.setName(artistName); + artistRepository.save(artist1); + Artist artist2 = new Artist(); + artist2.setName(artistName); + assertThrows(DataIntegrityViolationException.class, () -> { + artistRepository.save(artist2); + }); + artistRepository.delete(artist1); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/comics/data/ComicRepositoryTest.java b/springboot/src/test/java/de/thpeetz/kontor/comics/data/ComicRepositoryTest.java new file mode 100644 index 0000000..70c0d54 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/comics/data/ComicRepositoryTest.java @@ -0,0 +1,53 @@ +package de.thpeetz.kontor.comics.data; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ComicRepositoryTest { + + @Autowired + private ComicRepository comicRepository; + + @Autowired + private PublisherRepository publisherRepository; + + @Test + void testFindByTitle() { + List comics = comicRepository.findByTitle("Emma Frost"); + assertEquals(1, comics.size()); + assertEquals("Emma Frost", comics.get(0).getTitle()); + Publisher publisher = comics.get(0).getPublisher(); + assertEquals("Marvel", publisher.getName()); + } + + @Test + void testFindByTitleIgnoreCase() { + List comics = comicRepository.findByTitleIgnoreCase("x-men"); + assertEquals(1, comics.size()); + assertEquals("X-Men", comics.get(0).getTitle()); + } + @Test + void testFindByTitleAndPublisher() { + Publisher publisher = publisherRepository.findByName("Marvel"); + assertNotNull(publisher); + Comic found = comicRepository.findByTitleAndPublisher("Emma Frost", publisher); + assertNotNull(found); + assertEquals("Emma Frost", found.getTitle()); + assertEquals("Marvel", found.getPublisher().getName()); + + } + + @Test + void testSearch() { + List comics = comicRepository.search("X-men"); + assertEquals(11, comics.size()); + assertTrue(comics.stream().map(comic -> comic.getTitle()).collect(Collectors.toList()).contains("Astonishing X-Men")); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/comics/data/ComicTest.java b/springboot/src/test/java/de/thpeetz/kontor/comics/data/ComicTest.java new file mode 100644 index 0000000..5560346 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/comics/data/ComicTest.java @@ -0,0 +1,77 @@ +package de.thpeetz.kontor.comics.data; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.orm.jpa.JpaSystemException; +import org.springframework.transaction.TransactionSystemException; + +import de.thpeetz.kontor.comics.TestConstants; +import de.thpeetz.kontor.comics.services.ComicService; + +@SpringBootTest +@TestMethodOrder(OrderAnnotation.class) +class ComicTest { + + @Autowired + private ComicRepository comicRepository; + + @Autowired + private ComicService comicService; + + @Test + @Order(1) + void checkInitialDataLoad() { + List comics = comicRepository.findAll(); + assertEquals(TestConstants.COMIC_COUNT, comics.size()); + } + + @Test + @Order(2) + void exceptionThrownWhenSavingComicWithoutPublisher() { + Comic comic = new Comic(); + comic.setTitle(TestConstants.COMIC_TITLE); + assertThrows(TransactionSystemException.class, () -> { + comicRepository.save(comic); + }); + } + + @Test + @Order(3) + void exceptionThrownWhenSavingComicWithEmptyTitle() { + Comic comic = new Comic(); + Publisher publisher = TestConstants.getPublisherForComicTests(comicService); + comic.setPublisher(publisher); + assertNull(comic.getTitle()); + assertThrows(TransactionSystemException.class, () -> { + comicRepository.save(comic); + }); + comic.setTitle(""); + assertTrue(comic.getTitle().isEmpty()); + assertThrows(TransactionSystemException.class, () -> { + comicRepository.save(comic); + }); + } + + @Test + @Order(4) + void exceptionThrownWhenSavingComicWithSameNameFromDifferentPublishers() { + Publisher publisher = TestConstants.getPublisherForComicTests(comicService); + Comic comic = new Comic(); + comic.setTitle(TestConstants.SOJOURN_TPB_COMIC_TITLE); + comic.setPublisher(publisher); + assertThrows(DataIntegrityViolationException.class, () -> { + comicRepository.save(comic); + }); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/comics/data/ComicWorkRepositoryTest.java b/springboot/src/test/java/de/thpeetz/kontor/comics/data/ComicWorkRepositoryTest.java new file mode 100644 index 0000000..0902b84 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/comics/data/ComicWorkRepositoryTest.java @@ -0,0 +1,83 @@ +package de.thpeetz.kontor.comics.data; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import de.thpeetz.kontor.comics.services.ComicService; + +@SpringBootTest +@TestMethodOrder(OrderAnnotation.class) +class ComicWorkRepositoryTest { + + @Autowired + private ComicWorkRepository comicWorkRepository; + + @Autowired + private ComicRepository comicRepository; + + @Autowired + private WorktypeRepository worktypeRepository; + + @Autowired + private ArtistRepository artistRepository; + + @Autowired + private ComicService comicService; + + @Test + @Order(1) + void checkInitialLoad() { + int count = comicWorkRepository.findAll().size(); + assertEquals(18, count); + } + + @Test + @Order(2) + void saveComicWork() { + int count = comicWorkRepository.findAll().size(); + Artist artist = artistRepository.findByName("Turner, Michael"); + Worktype worktype = worktypeRepository.findByName("Writer"); + Comic comic = comicRepository.findByTitle("Emma Frost").get(0); + ComicWork comicWork = new ComicWork(); + comicWork.setArtist(artist); + comicWork.setComic(comic); + comicWork.setWorkType(worktype); + comicWorkRepository.save(comicWork); + assertEquals(count + 1, comicWorkRepository.findAll().size()); + } + + @Test + @Order(3) + void findByComicAndArtistAndComicWork() { + assertEquals(19, comicWorkRepository.count()); + Artist artist = artistRepository.findByName("Turner, Michael"); + Worktype worktype = worktypeRepository.findByName("Writer"); + Comic comic = comicRepository.findByTitle("Emma Frost").get(0); + ComicWork comicWork = comicWorkRepository.findbyComicAndArtistAndWorktype(comic, artist, worktype); + assertNotNull(comicWork); + assertEquals(comicWork.getArtist().getName(), artist.getName()); + assertEquals(comicWork.getComic().getTitle(), comic.getTitle()); + assertEquals(comicWork.getWorkType().getName(), worktype.getName()); + ComicWork notFound = comicWorkRepository.findbyComicAndArtistAndWorktype(comic, artist, null); + assertNull(notFound); + } + + @Test + @Order(4) + void deleteComicWork() { + long count = comicWorkRepository.count(); + Artist artist = artistRepository.findByName("Turner, Michael"); + Worktype worktype = worktypeRepository.findByName("Writer"); + Comic comic = comicRepository.findByTitle("Emma Frost").get(0); + ComicWork comicWork = comicWorkRepository.findbyComicAndArtistAndWorktype(comic, artist, worktype); + assertNotNull(comicWork); + comicService.deleteComicWork(comicWork); + assertEquals(count - 1, comicWorkRepository.count()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/comics/data/ComicWorkTest.java b/springboot/src/test/java/de/thpeetz/kontor/comics/data/ComicWorkTest.java new file mode 100644 index 0000000..ac2eac2 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/comics/data/ComicWorkTest.java @@ -0,0 +1,24 @@ +package de.thpeetz.kontor.comics.data; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import de.thpeetz.kontor.comics.services.ComicService; + +@SpringBootTest +public class ComicWorkTest { + + @Autowired + private ComicService comicService; + + @Test + void checkInitialDataLoad() { + List comicWorks = comicService.findAllComicWorks(); + assertEquals(18, comicWorks.size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/comics/data/IssueRepositoryTest.java b/springboot/src/test/java/de/thpeetz/kontor/comics/data/IssueRepositoryTest.java new file mode 100644 index 0000000..8d3be93 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/comics/data/IssueRepositoryTest.java @@ -0,0 +1,45 @@ +package de.thpeetz.kontor.comics.data; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import de.thpeetz.kontor.comics.TestConstants; +import de.thpeetz.kontor.comics.services.ComicService; + +@SpringBootTest +class IssueRepositoryTest { + + @Autowired + private IssueRepository issueRepository; + + @Autowired + private ComicService comicService; + + @Test + void testFindByComic() { + Comic comic = TestConstants.getComicWithIssues(comicService); + List issues = issueRepository.findByComic(comic); + assertEquals(TestConstants.BATTLE_POPE_ISSUE_COUNT, issues.size()); + } + + @Test + void testFindByComicAndIssueNumber() { + Comic comic = TestConstants.getComicWithIssues(comicService); + Issue issue = issueRepository.findByComicAndIssueNumber(comic, "12"); + assertNotNull(issue); + assertFalse(issue.getIsRead()); + } + + @Test + void testSearch() { + List issues = issueRepository.search("2"); + assertEquals(187, issues.size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/comics/data/IssueTest.java b/springboot/src/test/java/de/thpeetz/kontor/comics/data/IssueTest.java new file mode 100644 index 0000000..1760fd1 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/comics/data/IssueTest.java @@ -0,0 +1,62 @@ +package de.thpeetz.kontor.comics.data; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.TransactionSystemException; + +import de.thpeetz.kontor.comics.TestConstants; +import de.thpeetz.kontor.comics.services.ComicService; + +@SpringBootTest +class IssueTest { + + @Autowired + private IssueRepository issueRepository; + + @Autowired + private ComicService comicService; + + @Test + void checkInitialDataLoad() { + List issues = comicService.findAllIssues(); + assertEquals(TestConstants.ISSUE_COUNT, issues.size()); + } + + @Test + void exceptionThrownWhenSavingIssueWithEmptyNumber() { + Comic comic = TestConstants.getComicWithoutReferences(comicService); + assertNotNull(comic); + Issue issue = new Issue(); + issue.setComic(comic); + assertThrows(TransactionSystemException.class, () -> { + issueRepository.save(issue); + }); + } + + @Test + void exceptionThrownWhenSavingStoryArcWithoutComic() { + Issue issue = new Issue(); + issue.setIssueNumber(TestConstants.ISSUE_ISSUENUMBER); + assertThrows(TransactionSystemException.class, () -> { + issueRepository.save(issue); + }); + } + + @Test + void saveStoryArc() { + Comic comic = TestConstants.getComicWithoutReferences(comicService); + assertEquals(0, comic.getStoryArcs().size()); + Issue issue = new Issue(); + issue.setComic(comic); + issue.setIssueNumber(TestConstants.ISSUE_ISSUENUMBER); + Issue savedInstance = issueRepository.save(issue); + assertNotNull(savedInstance); + comicService.deleteIssue(savedInstance); + assertEquals(0, comic.getStoryArcs().size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/comics/data/PublisherRepositoryTest.java b/springboot/src/test/java/de/thpeetz/kontor/comics/data/PublisherRepositoryTest.java new file mode 100644 index 0000000..50175b4 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/comics/data/PublisherRepositoryTest.java @@ -0,0 +1,68 @@ +package de.thpeetz.kontor.comics.data; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import de.thpeetz.kontor.comics.TestConstants; + +@SpringBootTest +@TestMethodOrder(OrderAnnotation.class) +class PublisherRepositoryTest { + + @Autowired + private PublisherRepository publisherRepository; + + @Test + @Order(1) + void checkInitialLoad() { + assertEquals(TestConstants.PUBLISHER_COUNT, publisherRepository.findAll().size()); + } + + @Test + @Order(2) + void savePublisher() { + Publisher publisher = new Publisher(); + publisher.setName(TestConstants.PUBLISHER_NAME); + publisherRepository.save(publisher); + assertEquals(TestConstants.PUBLISHER_COUNT + 1, publisherRepository.findAll().size()); + } + + @Test + @Order(3) + void findPublisherByName() { + Publisher publisher = publisherRepository.findByName(TestConstants.PUBLISHER_NAME); + assertNotNull(publisher); + assertEquals(TestConstants.PUBLISHER_NAME, publisher.getName()); + Publisher notFound = publisherRepository.findByName("Cow"); + assertNull(notFound); + List publishers = publisherRepository + .findByNameIgnoreCase(TestConstants.PUBLISHER_NAME.toLowerCase()); + assertEquals(1, publishers.size()); + assertEquals(TestConstants.PUBLISHER_NAME, publishers.get(0).getName()); + } + + @Test + @Order(4) + void searchPublisher() { + List publishers = publisherRepository.search("Cow"); + assertEquals(1, publishers.size()); + assertEquals("Top Cow Productions", publishers.get(0).getName()); + } + + @Test + @Order(5) + void deletePublisher() { + Publisher publisher = publisherRepository.findByName(TestConstants.PUBLISHER_NAME); + assertNotNull(publisher); + publisherRepository.delete(publisher); + assertEquals(TestConstants.PUBLISHER_COUNT, publisherRepository.findAll().size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/comics/data/PublisherTest.java b/springboot/src/test/java/de/thpeetz/kontor/comics/data/PublisherTest.java new file mode 100644 index 0000000..264a1d0 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/comics/data/PublisherTest.java @@ -0,0 +1,45 @@ +package de.thpeetz.kontor.comics.data; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.transaction.TransactionSystemException; + +import de.thpeetz.kontor.comics.TestConstants; + +@SpringBootTest +class PublisherTest { + + @Autowired + private PublisherRepository publisherRepository; + + @Test + void checkInitialDataLoad() { + assertEquals(TestConstants.PUBLISHER_COUNT, publisherRepository.findAll().size()); + } + + @Test + void throwExceptionWhenPublisherSavedWithEmptyName() { + Publisher publisher = new Publisher(); + assertThrows(TransactionSystemException.class, () -> { + publisherRepository.save(publisher); + }); + } + + @Test + void savePublisherWithIdenticalName() { + Publisher publisher1 = new Publisher(); + publisher1.setName(TestConstants.PUBLISHER_NAME); + publisherRepository.save(publisher1); + Publisher publisher2 = new Publisher(); + publisher2.setName(TestConstants.PUBLISHER_NAME); + assertThrows(DataIntegrityViolationException.class, () -> { + publisherRepository.save(publisher2); + }); + publisherRepository.delete(publisher1); + assertEquals(TestConstants.PUBLISHER_COUNT, publisherRepository.findAll().size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/comics/data/StoryArcRepositoryTest.java b/springboot/src/test/java/de/thpeetz/kontor/comics/data/StoryArcRepositoryTest.java new file mode 100644 index 0000000..52bad7e --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/comics/data/StoryArcRepositoryTest.java @@ -0,0 +1,45 @@ +package de.thpeetz.kontor.comics.data; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import de.thpeetz.kontor.comics.TestConstants; +import de.thpeetz.kontor.comics.services.ComicService; + +@SpringBootTest +class StoryArcRepositoryTest { + + @Autowired + private StoryArcRepository storyArcRepository; + + @Autowired + private ComicService comicService; + + @Test + void testSearch() { + List storyArcs = storyArcRepository.search("Learn"); + assertNotNull(storyArcs); + assertEquals(1, storyArcs.size()); + } + + @Test + void findByComic() { + Comic comic = TestConstants.getComicWithStoryArcs(comicService); + List storyArcs = storyArcRepository.findByComic(comic); + assertNotNull(storyArcs); + assertEquals(3, storyArcs.size()); + } + + @Test + void findByNameAndComic() { + Comic comic = TestConstants.getComicWithStoryArcs(comicService); + StoryArc storyArc = storyArcRepository.findByNameAndComic("Higher Learning", comic); + assertNotNull(storyArc); + assertEquals("Emma Frost", storyArc.getComic().getTitle()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/comics/data/StoryArcTest.java b/springboot/src/test/java/de/thpeetz/kontor/comics/data/StoryArcTest.java new file mode 100644 index 0000000..07134ca --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/comics/data/StoryArcTest.java @@ -0,0 +1,61 @@ +package de.thpeetz.kontor.comics.data; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.TransactionSystemException; + +import de.thpeetz.kontor.comics.TestConstants; +import de.thpeetz.kontor.comics.services.ComicService; + +@SpringBootTest +class StoryArcTest { + + @Autowired + private StoryArcRepository storyArcRepository; + + @Autowired + private ComicService comicService; + + @Test + void checkInitialLoad() { + assertEquals(TestConstants.STORYARC_COUNT, storyArcRepository.findAll().size()); + } + + @Test + void exceptionThrownWhenSavingStoryArcWithEmptyName() { + Comic comic = TestConstants.getComicWithoutReferences(comicService); + assertNotNull(comic); + StoryArc storyArc = new StoryArc(); + storyArc.setComic(comic); + assertThrows(TransactionSystemException.class, () -> { + storyArcRepository.save(storyArc); + }); + } + + @Test + void exceptionThrownWhenSavingStoryArcWithoutComic() { + StoryArc storyArc = new StoryArc(); + storyArc.setName(TestConstants.STORYARC_NAME); + assertThrows(TransactionSystemException.class, () -> { + storyArcRepository.save(storyArc); + }); + } + + @Test + void saveStoryArc() { + Comic comic = TestConstants.getComicWithoutReferences(comicService); + assertEquals(0, comic.getStoryArcs().size()); + StoryArc storyArc = new StoryArc(); + storyArc.setName(TestConstants.STORYARC_NAME); + storyArc.setComic(comic); + StoryArc savedInstance = storyArcRepository.save(storyArc); + assertNotNull(savedInstance); + comicService.deleteStoryArc(savedInstance); + assertEquals(0, comic.getStoryArcs().size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/comics/data/TradePaperbackRepositoryTest.java b/springboot/src/test/java/de/thpeetz/kontor/comics/data/TradePaperbackRepositoryTest.java new file mode 100644 index 0000000..2c66c12 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/comics/data/TradePaperbackRepositoryTest.java @@ -0,0 +1,59 @@ +package de.thpeetz.kontor.comics.data; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import de.thpeetz.kontor.comics.TestConstants; +import de.thpeetz.kontor.comics.services.ComicService; + +@SpringBootTest +class TradePaperbackRepositoryTest { + + @Autowired + private TradePaperbackRepository tradePaperbackRepository; + + @Autowired + private ComicService comicService; + + @Test + void testSearch() { + List tradePaperbacks = tradePaperbackRepository.search("Dragon"); + assertNotNull(tradePaperbacks); + assertEquals(1, tradePaperbacks.size()); + } + + @Test + void testFindByComic() { + Comic comic = TestConstants.getComicWithTradePaperbacks(comicService); + List tradePaperbacks = tradePaperbackRepository.findByComic(comic); + assertNotNull(tradePaperbacks); + assertEquals(TestConstants.SOJOURN_TPB_COUNT, tradePaperbacks.size()); + } + + @Test + void testFindByNameAndComic() { + Comic comic = TestConstants.getComicWithTradePaperbacks(comicService); + List tradePaperbacks = tradePaperbackRepository + .findByNameAndComic(TestConstants.SOJOURN_TPB_NAME, comic); + assertNotNull(tradePaperbacks); + assertTrue(tradePaperbacks.size() > 0); + assertEquals(TestConstants.SOJOURN_TPB_COMIC_TITLE, tradePaperbacks.get(0).getComic().getTitle()); + } + + @Test + void testFindByFields() { + Comic comic = TestConstants.getComicWithTradePaperbacks(comicService); + TradePaperback tradePaperback = tradePaperbackRepository.findByFields(TestConstants.SOJOURN_TPB_NAME, + comic, TestConstants.SOJOURN_TPB_START, TestConstants.SOJOURN_TPB_END); + assertNotNull(tradePaperback); + assertEquals(TestConstants.SOJOURN_TPB_COMIC_TITLE, tradePaperback.getComic().getTitle()); + assertEquals(TestConstants.SOJOURN_TPB_END, tradePaperback.getIssueEnd()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/comics/data/TradePaperbackTest.java b/springboot/src/test/java/de/thpeetz/kontor/comics/data/TradePaperbackTest.java new file mode 100644 index 0000000..61df1d6 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/comics/data/TradePaperbackTest.java @@ -0,0 +1,59 @@ +package de.thpeetz.kontor.comics.data; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.TransactionSystemException; + +import de.thpeetz.kontor.comics.TestConstants; +import de.thpeetz.kontor.comics.services.ComicService; + +@SpringBootTest +class TradePaperbackTest { + + @Autowired + private TradePaperbackRepository tradePaperbackRepository; + + @Autowired + private ComicService comicService; + + @Test + void checkInitialDataLoad() { + assertEquals(TestConstants.TRADEPAPERBACK_COUNT, tradePaperbackRepository.findAll().size()); + } + + @Test + void exceptionThrownWhenSavingTradePaperbackWithEmptyName() { + Comic comic = TestConstants.getComicWithoutReferences(comicService); + assertNotNull(comic); + TradePaperback tradePaperback = new TradePaperback(); + tradePaperback.setComic(comic); + assertThrows(TransactionSystemException.class, () -> { + tradePaperbackRepository.save(tradePaperback); + }); + } + + @Test + void exceptionThrownWhenSavingTradePaperbackWithoutComic() { + TradePaperback tradePaperback = new TradePaperback(); + tradePaperback.setName(TestConstants.TRADEPAPERBACK_NAME); + assertThrows(TransactionSystemException.class, () -> { + tradePaperbackRepository.save(tradePaperback); + }); + } + + @Test + void saveTradePaperback() { + Comic comic = TestConstants.getComicWithoutReferences(comicService); + assertEquals(0, comic.getTradePaperbacks().size()); + TradePaperback tradePaperback = new TradePaperback(); + tradePaperback.setName(TestConstants.TRADEPAPERBACK_NAME); + tradePaperback.setComic(comic); + TradePaperback savedInstance = tradePaperbackRepository.save(tradePaperback); + assertNotNull(savedInstance); + comicService.deleteTradePaperBack(savedInstance); + assertEquals(0, comic.getTradePaperbacks().size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/comics/data/VolumeRepositoryTest.java b/springboot/src/test/java/de/thpeetz/kontor/comics/data/VolumeRepositoryTest.java new file mode 100644 index 0000000..ce04a11 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/comics/data/VolumeRepositoryTest.java @@ -0,0 +1,61 @@ +package de.thpeetz.kontor.comics.data; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import de.thpeetz.kontor.comics.TestConstants; +import de.thpeetz.kontor.comics.services.ComicService; + +@SpringBootTest +class VolumeRepositoryTest { + + @Autowired + private VolumeRepository volumeRepository; + + @Autowired + private ComicRepository comicRepository; + + @Autowired + private ComicService comicService; + + @Test + void testFindByComic() { + Comic comic = TestConstants.getComicWithoutReferences(comicService); + assertEquals(0, comic.getVolumes().size()); + Volume volume = new Volume(); + volume.setName(TestConstants.VOLUME_NAME); + volume.setComic(comic); + Volume savedInstance = volumeRepository.save(volume); + assertNotNull(savedInstance); + + List found = volumeRepository.findByComic(comic); + assertEquals(1, found.size()); + + comicService.deleteVolume(found.get(0)); + assertEquals(0, comic.getVolumes().size()); + assertEquals(TestConstants.VOLUME_COUNT, volumeRepository.count()); + } + + @Test + void testFindByName() { + Comic comic = TestConstants.getComicWithoutReferences(comicService); + assertEquals(0, comic.getVolumes().size()); + Volume volume = new Volume(); + volume.setName(TestConstants.VOLUME_NAME); + volume.setComic(comic); + Volume savedInstance = volumeRepository.save(volume); + assertNotNull(savedInstance); + + List found = volumeRepository.findByName(TestConstants.VOLUME_NAME); + assertEquals(1, found.size()); + + comicService.deleteVolume(found.get(0)); + assertEquals(0, comic.getVolumes().size()); + assertEquals(TestConstants.VOLUME_COUNT, volumeRepository.count()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/comics/data/VolumeTest.java b/springboot/src/test/java/de/thpeetz/kontor/comics/data/VolumeTest.java new file mode 100644 index 0000000..7189965 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/comics/data/VolumeTest.java @@ -0,0 +1,64 @@ +package de.thpeetz.kontor.comics.data; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.TransactionSystemException; + +import de.thpeetz.kontor.comics.TestConstants; +import de.thpeetz.kontor.comics.services.ComicService; + +@SpringBootTest +class VolumeTest { + + @Autowired + private VolumeRepository volumeRepository; + + @Autowired + private ComicRepository comicRepository; + + @Autowired + private ComicService comicService; + + @Test + void checkInitialDataLoad() { + assertEquals(TestConstants.VOLUME_COUNT, volumeRepository.count()); + } + + @Test + void exceptionThrownWhenSavingVolumeWithEmptyName() { + Comic comic = TestConstants.getComicWithoutReferences(comicService); + Volume volume = new Volume(); + volume.setComic(comic); + assertThrows(TransactionSystemException.class, () -> { + volumeRepository.save(volume); + }); + } + + @Test + void exceptionThrownWhenSavingVolumeWithoutComic() { + Volume volume = new Volume(); + volume.setName(TestConstants.VOLUME_NAME); + assertThrows(TransactionSystemException.class, () -> { + volumeRepository.save(volume); + }); + } + + @Test + void saveVolume() { + Comic comic = TestConstants.getComicWithoutReferences(comicService); + assertEquals(0, comic.getVolumes().size()); + Volume volume = new Volume(); + volume.setName(TestConstants.VOLUME_NAME); + volume.setComic(comic); + Volume savedInstance = volumeRepository.save(volume); + assertNotNull(savedInstance); + comicService.deleteVolume(savedInstance); + assertEquals(0, comic.getVolumes().size()); + assertEquals(TestConstants.VOLUME_COUNT, volumeRepository.count()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/comics/data/WorktypeRepositoryTest.java b/springboot/src/test/java/de/thpeetz/kontor/comics/data/WorktypeRepositoryTest.java new file mode 100644 index 0000000..5dae5e8 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/comics/data/WorktypeRepositoryTest.java @@ -0,0 +1,46 @@ +package de.thpeetz.kontor.comics.data; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import de.thpeetz.kontor.comics.TestConstants; + +@SpringBootTest +class WorktypeRepositoryTest { + + @Autowired + private WorktypeRepository worktypeRepository; + + @Test + void findWorktypeByName() { + Worktype found = worktypeRepository.findByName("Writer"); + assertNotNull(found); + Worktype notFound = worktypeRepository.findByName("er"); + assertNull(notFound); + } + + @Test + void findWorktypeByNameIgnoreCase() { + List worktypes = worktypeRepository.findByNameIgnoreCase("Writer".toLowerCase()); + assertNotNull(worktypes); + assertEquals(1, worktypes.size()); + assertEquals("Writer", worktypes.get(0).getName()); + } + + @Test + void searchWorktype() { + List worktypes = worktypeRepository.search("er"); + assertEquals(3, worktypes.size()); + assertTrue(worktypes.stream().map(worktype -> worktype.getName()).collect(Collectors.toList()).contains("Writer")); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/comics/data/WorktypeTest.java b/springboot/src/test/java/de/thpeetz/kontor/comics/data/WorktypeTest.java new file mode 100644 index 0000000..3145ee9 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/comics/data/WorktypeTest.java @@ -0,0 +1,83 @@ +package de.thpeetz.kontor.comics.data; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.orm.jpa.JpaSystemException; +import org.springframework.transaction.TransactionSystemException; + +import de.thpeetz.kontor.comics.TestConstants; + +@SpringBootTest +@TestMethodOrder(OrderAnnotation.class) +class WorktypeTest { + + @Autowired + private WorktypeRepository worktypeRepository; + + @Test + @Order(1) + void checkInitialDataLoad() { + List worktypes = worktypeRepository.findAll(); + assertEquals(3, worktypes.size()); + } + + @Test + @Order(2) + void findWorktypeByName() { + Worktype found = worktypeRepository.findByName(TestConstants.WORKTYPE_INKER); + assertNotNull(found); + assertEquals(TestConstants.WORKTYPE_INKER, found.getName()); + Worktype notFound = worktypeRepository.findByName("er"); + assertNull(notFound); + } + + @Test + @Order(3) + void throwExceptionWhenArtistSavedWithEmptyName() { + Worktype worktype1 = new Worktype(); + assertThrows(TransactionSystemException.class, () -> { + worktypeRepository.save(worktype1); + }); + } + + @Test + @Order(4) + void saveWorktypeWithIdenticalName() { + Worktype worktype1 = new Worktype(); + worktype1.setName(TestConstants.WORKTYPE_NAME); + worktypeRepository.save(worktype1); + Worktype worktype2 = new Worktype(); + worktype2.setName(TestConstants.WORKTYPE_NAME); + assertThrows(DataIntegrityViolationException.class, () -> { + worktypeRepository.save(worktype2); + }); + worktypeRepository.delete(worktype1); + } + + @Test + @Order(5) + void saveWorktype() { + Worktype worktype = new Worktype(); + worktype.setName(TestConstants.WORKTYPE_NAME); + worktypeRepository.save(worktype); + assertEquals(TestConstants.WORKTYPE_COUNT + 1, worktypeRepository.count()); + } + + @Test + @Order(6) + void deleteWorktype() { + Worktype worktype = worktypeRepository.findByName(TestConstants.WORKTYPE_NAME); + assertNotNull(worktype); + worktypeRepository.delete(worktype); + assertEquals(TestConstants.WORKTYPE_COUNT, worktypeRepository.count()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/comics/services/ComicServiceTest.java b/springboot/src/test/java/de/thpeetz/kontor/comics/services/ComicServiceTest.java new file mode 100644 index 0000000..d449298 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/comics/services/ComicServiceTest.java @@ -0,0 +1,350 @@ +package de.thpeetz.kontor.comics.services; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import de.thpeetz.kontor.comics.TestConstants; +import de.thpeetz.kontor.comics.data.Artist; +import de.thpeetz.kontor.comics.data.Comic; +import de.thpeetz.kontor.comics.data.ComicWork; +import de.thpeetz.kontor.comics.data.Issue; +import de.thpeetz.kontor.comics.data.Publisher; +import de.thpeetz.kontor.comics.data.StoryArc; +import de.thpeetz.kontor.comics.data.TradePaperback; +import de.thpeetz.kontor.comics.data.Volume; +import de.thpeetz.kontor.comics.data.Worktype; + +@SpringBootTest +@TestMethodOrder(OrderAnnotation.class) +class ComicServiceTest { + + private static final Logger log = LoggerFactory.getLogger(ComicServiceTest.class); + @Autowired + private ComicService comicService; + + @Test + @Order(1) + void testFindAllArtists() { + assertEquals(TestConstants.ARTIST_COUNT, comicService.findAllArtists(null).size()); + assertEquals(1, comicService.findAllArtists("Turn").size()); + } + + @Test + @Order(1) + void testFindArtistByName() { + assertNull(comicService.findArtistByName("Lee, Stan")); + Artist artist = comicService.findArtistByName("Turner, Michael"); + assertNotNull(artist); + assertNotNull(artist.getComicWorks()); + assertFalse(artist.getComicWorks().isEmpty()); + } + + @Test + @Order(2) + void testSaveArtist() { + Artist artist = new Artist(); + artist.setName(TestConstants.ARTIST_NAME); + comicService.saveArtist(artist); + assertEquals(TestConstants.ARTIST_COUNT + 1, comicService.findAllArtists(null).size()); + } + + @Test + @Order(3) + void testDeleteArtist() { + List artists = comicService.findAllArtists(TestConstants.ARTIST_NAME); + assertEquals(1, artists.size()); + comicService.deleteArtist(artists.get(0)); + assertEquals(TestConstants.ARTIST_COUNT, comicService.findAllArtists(null).size()); + } + + @Test + @Order(4) + void testFindAllPublishers() { + assertEquals(TestConstants.PUBLISHER_COUNT, comicService.findAllPublishers(null).size()); + assertEquals(1, comicService.findAllPublishers("Cow").size()); + } + + @Test + @Order(4) + void testFindPublisherByName() { + Publisher publisher = comicService.findPublisherByName("Marvel"); + assertNotNull(publisher); + assertNotNull(publisher.getComics()); + assertEquals(TestConstants.MARVEL_COMIC_COUNT, publisher.getComics().size()); + assertNull(comicService.findPublisherByName(TestConstants.PUBLISHER_NAME)); + } + + @Test + @Order(5) + void testSavePublisher() { + Publisher publisher = new Publisher(); + publisher.setName(TestConstants.PUBLISHER_NAME); + comicService.savePublisher(publisher); + assertEquals(TestConstants.PUBLISHER_COUNT + 1, comicService.findAllPublishers(null).size()); + } + + @Test + @Order(6) + void testDeletePublisher() { + List publishers = comicService.findAllPublishers(TestConstants.PUBLISHER_NAME); + assertEquals(1, publishers.size()); + comicService.deletePublisher(publishers.get(0)); + assertEquals(TestConstants.PUBLISHER_COUNT, comicService.findAllPublishers(null).size()); + } + + @Test + @Order(7) + void testFindAllWorktypes() { + assertEquals(TestConstants.WORKTYPE_COUNT, comicService.findAllWorktypes(null).size()); + assertEquals(1, comicService.findAllWorktypes("ite").size()); + } + + @Test + @Order(7) + void testFindWorktypeByName() { + Worktype worktype = comicService.findWorktypeByName("Writer"); + assertNotNull(worktype); + assertNotNull(worktype.getComicWorks()); + assertFalse(worktype.getComicWorks().isEmpty()); + worktype = comicService.findWorktypeByName("Inker"); + assertNotNull(worktype); + assertNotNull(worktype.getComicWorks()); + assertTrue(worktype.getComicWorks().isEmpty()); + assertNull(comicService.findWorktypeByName(TestConstants.WORKTYPE_NAME)); + } + + @Test + @Order(8) + void testSaveWorktype() { + Worktype worktype = new Worktype(); + worktype.setName(TestConstants.WORKTYPE_NAME); + comicService.saveWorktype(worktype); + assertEquals(TestConstants.WORKTYPE_COUNT + 1, comicService.findAllWorktypes(null).size()); + } + + @Test + @Order(9) + void testDeleteWorktype() { + List worktypes = comicService.findAllWorktypes(TestConstants.WORKTYPE_NAME); + assertEquals(1, worktypes.size()); + comicService.deleteWorktype(worktypes.get(0)); + assertEquals(TestConstants.WORKTYPE_COUNT, comicService.findAllWorktypes(null).size()); + } + + @Test + @Order(10) + void testFindComicByTitle() { + assertNull(comicService.findComicByTitle(null)); + assertNotNull(comicService.findComicByTitle("Danger Girl")); + assertNull(comicService.findComicByTitle("x-men")); + } + + @Test + @Order(11) + void testSaveComic() { + Publisher marvel = comicService.findAllPublishers("Marvel").get(0); + Comic comic = new Comic(); + comic.setTitle(TestConstants.COMIC_TITLE); + comic.setPublisher(marvel); + comicService.saveComic(comic); + assertEquals(TestConstants.COMIC_COUNT + 1, comicService.findAllComics(null).size()); + } + + @Test + @Order(12) + void testDeleteComic() { + assertEquals(TestConstants.COMIC_COUNT + 1, comicService.findAllComics(null).size()); + List comics = comicService.findAllComics(TestConstants.COMIC_TITLE); + assertEquals(1, comics.size()); + Comic comic = comicService.findComicByTitle(TestConstants.COMIC_TITLE); + assertNotNull(comic); + comicService.deleteComic(comic); + Publisher marvel = comicService.findPublisherByName("Marvel"); + assertNotNull(marvel.getComics()); + assertEquals(TestConstants.MARVEL_COMIC_COUNT, marvel.getComics().size()); + assertEquals(0, comicService.findAllComics(TestConstants.COMIC_TITLE).size()); + assertEquals(TestConstants.COMIC_COUNT, comicService.findAllComics(null).size()); + } + + @Test + @Order(13) + void testFindAllIssues() { + assertEquals(TestConstants.ISSUE_COUNT, comicService.findAllIssues().size()); + } + + @Test + @Order(14) + void testFindAllIssuesForComic() { + Comic comic = TestConstants.getComicWithIssues(comicService); + List issues = comicService.findAllIssuesForComic(comic); + assertEquals(12, issues.size()); + } + + @Test + @Order(15) + void testSaveIssue() { + Comic comic = TestConstants.getComicWithoutReferences(comicService); + Issue issue = new Issue(); + issue.setComic(comic); + issue.setIssueNumber(TestConstants.ISSUE_ISSUENUMBER); + comicService.saveIssue(issue); + assertEquals(TestConstants.ISSUE_COUNT + 1, comicService.findAllIssues().size()); + comic = TestConstants.getComicWithoutReferences(comicService); + assertNotNull(comic.getIssues()); + assertEquals(1, comic.getIssues().size()); + } + + @Test + @Order(16) + void testDeleteIssue() { + Comic comic = TestConstants.getComicWithoutReferences(comicService); + List issues = comicService.findAllIssuesForComic(comic); + assertEquals(1, issues.size()); + comicService.deleteIssue(issues.get(0)); + assertEquals(TestConstants.ISSUE_COUNT, comicService.findAllIssues().size()); + } + + @Test + @Order(17) + void testFindAllTradePaperbacks() { + assertEquals(TestConstants.TRADEPAPERBACK_COUNT, comicService.findAllTradePaperbacks(null).size()); + assertEquals(7, comicService.findAllTradePaperbacks("of").size()); + } + + @Test + @Order(18) + void testSaveTradePaperBack() { + Comic comic = TestConstants.getComicWithoutReferences(comicService); + TradePaperback tradePaperback = new TradePaperback(); + tradePaperback.setComic(comic); + tradePaperback.setName(TestConstants.TRADEPAPERBACK_NAME); + comicService.saveTradePaperBack(tradePaperback); + assertEquals(TestConstants.TRADEPAPERBACK_COUNT + 1, comicService.findAllTradePaperbacks(null).size()); + } + + @Test + @Order(19) + void testDeleteTradePaperBack() { + List tradePaperbacks = comicService.findAllTradePaperbacks(TestConstants.TRADEPAPERBACK_NAME); + assertEquals(1, tradePaperbacks.size()); + comicService.deleteTradePaperBack(tradePaperbacks.get(0)); + assertEquals(TestConstants.TRADEPAPERBACK_COUNT, comicService.findAllTradePaperbacks(null).size()); + } + + @Test + @Order(20) + void testFindAllStoryArcs() { + assertEquals(TestConstants.STORYARC_COUNT, comicService.findAllStoryArcs().size()); + } + + @Test + @Order(21) + void testFindAllStoryArcsForComic() { + assertEquals(TestConstants.STORYARC_COUNT, comicService.findAllStoryArcsForComic(null).size()); + Comic comic = TestConstants.getComicWithStoryArcs(comicService); + assertEquals(3, comicService.findAllStoryArcsForComic(comic).size()); + } + + @Test + @Order(22) + void testSaveStoryArc() { + Comic comic = TestConstants.getComicWithoutReferences(comicService); + StoryArc storyArc = new StoryArc(); + storyArc.setName(TestConstants.STORYARC_NAME); + storyArc.setComic(comic); + comicService.saveStoryArc(storyArc); + assertEquals(TestConstants.STORYARC_COUNT + 1, comicService.findAllStoryArcs().size()); + } + + @Test + @Order(23) + void testDeleteStoryArc() { + Comic comic = TestConstants.getComicWithoutReferences(comicService); + List storyArcs = comicService.findAllStoryArcsForComic(comic); + assertEquals(1, storyArcs.size()); + comicService.deleteStoryArc(storyArcs.get(0)); + assertEquals(TestConstants.STORYARC_COUNT, comicService.findAllStoryArcs().size()); + } + + @Test + @Order(24) + void testFindAllVolumes() { + assertEquals(TestConstants.VOLUME_COUNT, comicService.findAllVolumes().size()); + } + + @Test + @Order(25) + void testSaveVolume() { + Comic comic = TestConstants.getComicWithoutReferences(comicService); + Volume volume = new Volume(); + volume.setName(TestConstants.VOLUME_NAME); + volume.setComic(comic); + comicService.saveVolume(volume); + assertEquals(TestConstants.VOLUME_COUNT + 1, comicService.findAllVolumes().size()); + } + + @Test + @Order(26) + void testFindAllVolumesForComic() { + Comic comic = TestConstants.getComicWithoutReferences(comicService); + List volumes = comicService.findAllVolumesForComic(comic); + assertEquals(1, volumes.size()); + } + + @Test + @Order(27) + void testDeleteVolume() { + Comic comic = TestConstants.getComicWithoutReferences(comicService); + List volumes = comicService.findAllVolumesForComic(comic); + comicService.deleteVolume(volumes.get(0)); + assertEquals(TestConstants.VOLUME_COUNT, comicService.findAllVolumes().size()); + } + + @Test + @Order(28) + void testFindAllComicWorks() { + assertEquals(TestConstants.COMICWORK_COUNT, comicService.findAllComicWorks().size()); + } + + @Test + @Order(29) + void testSaveComicWork() { + Comic comic = TestConstants.getComicWithoutReferences(comicService); + Artist artist = TestConstants.getArtistWithoutReferences(comicService); + Worktype worktype = TestConstants.getWorktypeWithoutReferences(comicService); + ComicWork comicWork = new ComicWork(); + comicWork.setComic(comic); + comicWork.setArtist(artist); + comicWork.setWorkType(worktype); + comicService.saveComicWork(comicWork); + assertEquals(TestConstants.COMICWORK_COUNT + 1, comicService.findAllComicWorks().size()); + } + + @Test + @Order(30) + void testDeleteComicWork() { + Comic comic = TestConstants.getComicWithoutReferences(comicService); + Artist artist = TestConstants.getArtistWithoutReferences(comicService); + Worktype worktype = TestConstants.getWorktypeWithoutReferences(comicService); + List comicWorks = comic.getComicWorks(); + assertNotNull(comicWorks); + assertNotNull(artist.getComicWorks()); + assertNotNull(worktype.getComicWorks()); + assertEquals(1, comicWorks.size()); + assertEquals(1, artist.getComicWorks().size()); + assertEquals(1, worktype.getComicWorks().size()); + ComicWork comicWork = comicWorks.get(0); + comicService.deleteComicWork(comicWork); + assertEquals(TestConstants.COMICWORK_COUNT, comicService.findAllComicWorks().size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/media/TestConstants.java b/springboot/src/test/java/de/thpeetz/kontor/media/TestConstants.java new file mode 100644 index 0000000..32ee883 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/media/TestConstants.java @@ -0,0 +1,5 @@ +package de.thpeetz.kontor.media; + +public class TestConstants { + public static final String URL = "https://example.com/link.mp4"; +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/media/data/MediaArticleTest.java b/springboot/src/test/java/de/thpeetz/kontor/media/data/MediaArticleTest.java new file mode 100644 index 0000000..2f2adc7 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/media/data/MediaArticleTest.java @@ -0,0 +1,32 @@ +package de.thpeetz.kontor.media.data; + +import de.thpeetz.kontor.media.services.MediaArticleService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertFalse; + +@SpringBootTest +public class MediaArticleTest { + + @Autowired + private MediaArticleRepository mediaArticleRepository; + + @Test + void checkInitialLoad() { + assertTrue(mediaArticleRepository.findAll().isEmpty()); + } + + @Test + void checkDefaultValues() { + MediaArticle mediaArticle = new MediaArticle(); + assertNull(mediaArticle.getUrl()); + assertNull(mediaArticle.getTitle()); + assertNull(mediaArticle.getCreatedDate()); + assertNull(mediaArticle.getLastModifiedDate()); + assertNull(mediaArticle.getId()); + assertFalse(mediaArticle.isReview()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/media/data/MediaFileTest.java b/springboot/src/test/java/de/thpeetz/kontor/media/data/MediaFileTest.java new file mode 100644 index 0000000..6ca61ce --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/media/data/MediaFileTest.java @@ -0,0 +1,37 @@ +package de.thpeetz.kontor.media.data; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +public class MediaFileTest { + + @Autowired + private MediaFileRepository mediaFileRepository; + + @Test + void checkInitialDataLoad() { + List mediaFileList = mediaFileRepository.findAll(); + assertTrue(mediaFileList.isEmpty()); + } + + @Test + void checkDefaultValues() { + MediaFile mediaFile = new MediaFile(); + assertNull(mediaFile.getUrl()); + assertNull(mediaFile.getTitle()); + assertNull(mediaFile.getFileName()); + assertNull(mediaFile.getPath()); + assertNull(mediaFile.getCloudLink()); + assertNull(mediaFile.getCreatedDate()); + assertNull(mediaFile.getLastModifiedDate()); + assertNull(mediaFile.getId()); + assertFalse(mediaFile.isReview()); + assertFalse(mediaFile.isShouldDownload()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/media/data/MediaVideoTest.java b/springboot/src/test/java/de/thpeetz/kontor/media/data/MediaVideoTest.java new file mode 100644 index 0000000..1a40335 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/media/data/MediaVideoTest.java @@ -0,0 +1,38 @@ +package de.thpeetz.kontor.media.data; + +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertFalse; + +@SpringBootTest +public class MediaVideoTest { + + @Autowired + private MediaVideoRepository mediaVideoRepository; + + @Test + @Order(1) + void checkInitialLoad() { + assertTrue(mediaVideoRepository.findAll().isEmpty()); + } + + @Test + @Order(2) + void checkDefaultValues() { + MediaVideo mediaVideo = new MediaVideo(); + assertNull(mediaVideo.getUrl()); + assertNull(mediaVideo.getTitle()); + assertNull(mediaVideo.getFileName()); + assertNull(mediaVideo.getPath()); + assertNull(mediaVideo.getCloudLink()); + assertNull(mediaVideo.getCreatedDate()); + assertNull(mediaVideo.getLastModifiedDate()); + assertNull(mediaVideo.getId()); + assertFalse(mediaVideo.isReview()); + assertFalse(mediaVideo.isShouldDownload()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/media/services/MediaArticleServiceTest.java b/springboot/src/test/java/de/thpeetz/kontor/media/services/MediaArticleServiceTest.java new file mode 100644 index 0000000..6178aa8 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/media/services/MediaArticleServiceTest.java @@ -0,0 +1,50 @@ +package de.thpeetz.kontor.media.services; + +import de.thpeetz.kontor.media.TestConstants; +import de.thpeetz.kontor.media.data.MediaArticle; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@Slf4j +@SpringBootTest +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class MediaArticleServiceTest { + + @Autowired + MediaArticleService mediaArticleService; + + @Test + @Order(1) + void findAllMediaArticles() { + assertTrue(mediaArticleService.findAllMediaArticles(null).isEmpty()); + } + + @Test + @Order(2) + void saveMediaArticle() { + int mediaArticleCount = mediaArticleService.findAllMediaArticles(null).size(); + MediaArticle mediaArticle = new MediaArticle(); + mediaArticle.setUrl(TestConstants.URL); + mediaArticleService.saveMediaArticle(mediaArticle); + assertEquals(++mediaArticleCount, mediaArticleService.findAllMediaArticles(null).size()); + } + + @Test + @Order(3) + void deleteMediaArticle() { + int mediaArticleCount = mediaArticleService.findAllMediaArticles(null).size(); + List mediaArticleList = mediaArticleService.findAllMediaArticles(TestConstants.URL); + assertEquals(1, mediaArticleService.findAllMediaArticles(null).size()); + mediaArticleService.deleteMediaArticle(mediaArticleList.get(0)); + assertEquals(--mediaArticleCount, mediaArticleService.findAllMediaArticles(null).size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/media/services/MediaFileServiceTest.java b/springboot/src/test/java/de/thpeetz/kontor/media/services/MediaFileServiceTest.java new file mode 100644 index 0000000..2201be5 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/media/services/MediaFileServiceTest.java @@ -0,0 +1,48 @@ +package de.thpeetz.kontor.media.services; + +import de.thpeetz.kontor.media.TestConstants; +import de.thpeetz.kontor.media.data.MediaFile; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class MediaFileServiceTest { + + @Autowired + private MediaFileService mediaFileService; + + @Test + @Order(1) + void testFindAllMediaFiles() { + assertTrue(mediaFileService.findAllMediaFiles(null).isEmpty()); + } + + @Test + @Order(2) + void testSaveMediaFile() { + int mediaFileCount = mediaFileService.findAllMediaFiles(null).size(); + MediaFile mediaFile = new MediaFile(); + mediaFile.setUrl(TestConstants.URL); + mediaFileService.saveMediaFile(mediaFile); + assertEquals(mediaFileCount +1, mediaFileService.findAllMediaFiles(null).size()); + } + + @Test + @Order(3) + void testDeleteMediaFile() { + int mediaFileCount = mediaFileService.findAllMediaFiles(null).size(); + List mediaFileList = mediaFileService.findAllMediaFiles(TestConstants.URL); + assertEquals(1, mediaFileList.size()); + mediaFileService.deleteMediaFile(mediaFileList.get(0)); + assertEquals(--mediaFileCount, mediaFileService.findAllMediaFiles(null).size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/media/services/MediaVideoServiceTest.java b/springboot/src/test/java/de/thpeetz/kontor/media/services/MediaVideoServiceTest.java new file mode 100644 index 0000000..36a6fb1 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/media/services/MediaVideoServiceTest.java @@ -0,0 +1,48 @@ +package de.thpeetz.kontor.media.services; + +import de.thpeetz.kontor.media.TestConstants; +import de.thpeetz.kontor.media.data.MediaArticle; +import de.thpeetz.kontor.media.data.MediaVideo; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Slf4j +@SpringBootTest +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class MediaVideoServiceTest { + + @Autowired + private MediaVideoService mediaVideoService; + + @Test + void findAllMediaVideos() { + assertTrue(mediaVideoService.findAllMediaVideos(null).isEmpty()); + } + + @Test + void saveMediaVideo() { + int mediaVideoCount = mediaVideoService.findAllMediaVideos(null).size(); + MediaVideo mediaVideo = new MediaVideo(); + mediaVideo.setUrl(TestConstants.URL); + mediaVideoService.saveMediaVideo(mediaVideo); + assertEquals(++mediaVideoCount, mediaVideoService.findAllMediaVideos(null).size()); + } + + @Test + void deleteMediaVideo() { + int mediaVideoCount = mediaVideoService.findAllMediaVideos(null).size(); + List mediaVideoList = mediaVideoService.findAllMediaVideos(TestConstants.URL); + assertEquals(1, mediaVideoList.size()); + mediaVideoService.deleteMediaVideo(mediaVideoList.get(0)); + assertEquals(--mediaVideoCount, mediaVideoService.findAllMediaVideos(null).size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/tysc/TestConstants.java b/springboot/src/test/java/de/thpeetz/kontor/tysc/TestConstants.java new file mode 100644 index 0000000..a05c1dd --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/tysc/TestConstants.java @@ -0,0 +1,39 @@ +package de.thpeetz.kontor.tysc; + +public class TestConstants { + + public static final Integer CARD_COUNT = 10; + public static final Integer CARD_CARDNUMBER = 999; + public static final Integer CARD_YEAR = 2000; + public static final Integer CARDSET_COUNT = 15; + public static final Integer FOOTBALL_TEAM_COUNT = 33; + public static final Integer FOOTBALL_POSITION_COUNT = 25; + public static final Integer PLAYER_COUNT = 38; + public static final Integer PLAYER_CHRIS_COUNT = 3; + public static final Integer POSITION_COUNT = 44; + public static final Integer POSITION_CENTER_COUNT = 3; + public static final Integer ROOSTER_COUNT = 11; + public static final Integer ROOSTER_YEAR = 1900; + public static final Integer SPORT_COUNT = 4; + public static final Integer TEAM_COUNT = 122; + public static final Integer VENDOR_COUNT = 9; + public static final String CARDSET_NAME = "CardSet"; + public static final String CARDSET_MYSTIQUE_NAME = "Mystique"; + public static final String DOLPHINS_NAME = "Miami Dolphins"; + public static final Object DOLPHINS_SHORT = "Dolphins"; + public static final String FOOTBALL_NAME = "Football"; + public static final String PLAYER_FIRSTNAME = "FirstName"; + public static final String PLAYER_LASTNAME = "LastName"; + public static final String PLAYER_CHRIS_NAME = "Chris"; + public static final String POSITION_CENTER = "Center"; + public static final String POSITION_NAME = "Position"; + public static final String POSITION_SHORTNAME = "POS"; + public static final String QUARTERBACK_NAME = "Quarterback"; + public static final String QUARTERBACK_SHORTNAME = "QB"; + public static final String SPORT_NAME = "Sport"; + public static final String TEAM_NAME = "Musterstadt Team"; + public static final String TEAM_SHORTNAME = "Team"; + public static final String TOPPS_NAME = "Topps"; + public static final String VENDOR_FLEER = "Fleer"; + public static final String VENDOR_NAME = "Vendor"; +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/tysc/data/CardRepositoryTest.java b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/CardRepositoryTest.java new file mode 100644 index 0000000..e2ff8b2 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/CardRepositoryTest.java @@ -0,0 +1,35 @@ +package de.thpeetz.kontor.tysc.data; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class CardRepositoryTest { + + @Autowired + private CardRepository cardRepository; + + @Test + void testSearchByFields() { + List cards = cardRepository.findAll(); + Card existingCard = cards.get(3); + Vendor vendor = existingCard.getVendor(); + CardSet cardSet = existingCard.getCardSet(); + Rooster rooster = existingCard.getRooster(); + int cardNumber = existingCard.getCardNumber(); + int year = existingCard.getYear(); + Card card = cardRepository.search(vendor, cardSet, rooster, cardNumber, year); + assertNotNull(card); + } + + @Test + void testSearch() { + assertEquals(2, cardRepository.search("2").size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/tysc/data/CardSetRepositoryTest.java b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/CardSetRepositoryTest.java new file mode 100644 index 0000000..71a08ba --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/CardSetRepositoryTest.java @@ -0,0 +1,42 @@ +package de.thpeetz.kontor.tysc.data; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import de.thpeetz.kontor.tysc.TestConstants; + +@SpringBootTest +class CardSetRepositoryTest { + + @Autowired + private CardSetRepository cardSetRepository; + + @Autowired + private VendorRepository vendorRepository; + + @Test + void testFindByName() { + List cardSets = cardSetRepository.findByName(TestConstants.CARDSET_MYSTIQUE_NAME); + assertEquals(1, cardSets.size()); + } + + @Test + void testFindByNameAndVendor() { + Vendor fleer = vendorRepository.findByName(TestConstants.VENDOR_FLEER); + assertNotNull(fleer); + CardSet cardSet = cardSetRepository.findByNameAndVendor(TestConstants.CARDSET_MYSTIQUE_NAME, fleer); + assertNotNull(cardSet); + } + + @Test + void testSearch() { + List cardSets = cardSetRepository.search("SP"); + assertEquals(3, cardSets.size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/tysc/data/CardSetTest.java b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/CardSetTest.java new file mode 100644 index 0000000..a5699d6 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/CardSetTest.java @@ -0,0 +1,22 @@ +package de.thpeetz.kontor.tysc.data; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import de.thpeetz.kontor.tysc.TestConstants; +import de.thpeetz.kontor.tysc.services.CardService; + +@SpringBootTest +class CardSetTest { + + @Autowired + private CardService cardService; + + @Test + void checkInitialDataLoad() { + assertEquals(TestConstants.CARDSET_COUNT, cardService.findAllCardSets(null).size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/tysc/data/CardTest.java b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/CardTest.java new file mode 100644 index 0000000..0d71d8d --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/CardTest.java @@ -0,0 +1,22 @@ +package de.thpeetz.kontor.tysc.data; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import de.thpeetz.kontor.tysc.TestConstants; +import de.thpeetz.kontor.tysc.services.CardService; + +@SpringBootTest +class CardTest { + + @Autowired + private CardService cardService; + + @Test + void checkInitialDataLoad() { + assertEquals(TestConstants.CARD_COUNT, cardService.findAllCards(null).size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/tysc/data/FieldPositionRepositoryTest.java b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/FieldPositionRepositoryTest.java new file mode 100644 index 0000000..a9273db --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/FieldPositionRepositoryTest.java @@ -0,0 +1,59 @@ +package de.thpeetz.kontor.tysc.data; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import de.thpeetz.kontor.tysc.TestConstants; + +@SpringBootTest +class FieldPositionRepositoryTest { + + @Autowired + private FieldPositionRepository fieldPositionRepository; + + @Autowired + private SportRepository sportRepository; + + @Test + void testFindByShortName() { + FieldPosition position = fieldPositionRepository.findByShortName(TestConstants.QUARTERBACK_SHORTNAME); + assertNotNull(position); + assertEquals(TestConstants.QUARTERBACK_NAME, position.getName()); + } + + @Test + void testFindByShortNameAndSport() { + Sport sport = sportRepository.findByName(TestConstants.FOOTBALL_NAME); + FieldPosition position = fieldPositionRepository.findByShortNameAndSport(TestConstants.QUARTERBACK_SHORTNAME, sport); + assertNotNull(position); + assertEquals(TestConstants.QUARTERBACK_NAME, position.getName()); + } + + @Test + void testFindByShortNameIgnoreCase() { + List positions = fieldPositionRepository + .findByShortNameIgnoreCase(TestConstants.QUARTERBACK_SHORTNAME.toLowerCase()); + assertNotNull(positions); + assertEquals(1, positions.size()); + assertEquals(TestConstants.QUARTERBACK_NAME, positions.get(0).getName()); + } + + @Test + void testFindBySport() { + Sport sport = sportRepository.findByName(TestConstants.FOOTBALL_NAME); + List positions = fieldPositionRepository.findBySport(sport); + assertEquals(TestConstants.FOOTBALL_POSITION_COUNT, positions.size()); + } + + @Test + void testSearch() { + List positions = fieldPositionRepository.search("back"); + assertEquals(8, positions.size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/tysc/data/FieldPositionTest.java b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/FieldPositionTest.java new file mode 100644 index 0000000..8a8b9b7 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/FieldPositionTest.java @@ -0,0 +1,44 @@ +package de.thpeetz.kontor.tysc.data; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.TransactionSystemException; + +import de.thpeetz.kontor.tysc.TestConstants; + +@SpringBootTest +class FieldPositionTest { + + @Autowired + private FieldPositionRepository fieldPositionRepository; + + @Test + void checkInitialDataLoad() { + assertEquals(Integer.toUnsignedLong(TestConstants.POSITION_COUNT), fieldPositionRepository.count()); + } + + @Test + void throwExceptionWhenPositionSavedWithEmptyName() { + FieldPosition fieldPosition = new FieldPosition(); + assertThrows(TransactionSystemException.class, () -> { + fieldPositionRepository.save(fieldPosition); + }); + fieldPosition.setName("Position"); + assertThrows(TransactionSystemException.class, () -> { + fieldPositionRepository.save(fieldPosition); + }); + } + + @Test + void throwExceptionWhenPositionSavedWithExistingName() { + FieldPosition existingFieldPosition = fieldPositionRepository.findAll().get(11); + FieldPosition fieldPosition = new FieldPosition(); + fieldPosition.setName(existingFieldPosition.getName()); + assertThrows(TransactionSystemException.class, () -> { + fieldPositionRepository.save(fieldPosition); + }); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/tysc/data/PlayerRepositoryTest.java b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/PlayerRepositoryTest.java new file mode 100644 index 0000000..7cbd540 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/PlayerRepositoryTest.java @@ -0,0 +1,31 @@ +package de.thpeetz.kontor.tysc.data; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class PlayerRepositoryTest { + + @Autowired + private PlayerRepository playerRepository; + + @Test + void testFindByFirstNameAndLastName() { + Player existingPlayer = playerRepository.findAll().get(11); + String firstName = existingPlayer.getFirstName(); + String lastName = existingPlayer.getLastName(); + Player found = playerRepository.findByFirstNameAndLastName(firstName, lastName); + assertEquals(existingPlayer.getFullName(), found.getFullName()); + } + + @Test + void testSearch() { + List players = playerRepository.search("can"); + assertEquals(1, players.size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/tysc/data/PlayerTest.java b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/PlayerTest.java new file mode 100644 index 0000000..941537b --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/PlayerTest.java @@ -0,0 +1,53 @@ +package de.thpeetz.kontor.tysc.data; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.transaction.TransactionSystemException; + +import de.thpeetz.kontor.tysc.TestConstants; + +@SpringBootTest +@TestMethodOrder(OrderAnnotation.class) +class PlayerTest { + + @Autowired + private PlayerRepository playerRepository; + + @Test + @Order(1) + void checkInitialDataLoad() { + List players = playerRepository.findAll(); + assertEquals(TestConstants.PLAYER_COUNT, players.size()); + } + + @Test + @Order(2) + void throwExceptionWhenPlayerSavedWithEmptyName() { + Player player = new Player(); + assertThrows(TransactionSystemException.class, () -> { + playerRepository.save(player); + }); + } + + @Test + @Order(3) + void throwExceptionWhenPlayerSavedWithExistingName() { + Player existingPlayer = playerRepository.findAll().get(10); + assertNotNull(existingPlayer); + Player player = new Player(); + player.setFirstName(existingPlayer.getFirstName()); + player.setLastName(existingPlayer.getLastName()); + assertThrows(DataIntegrityViolationException.class, () -> { + playerRepository.save(player); + }); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/tysc/data/RoosterRepositoryTest.java b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/RoosterRepositoryTest.java new file mode 100644 index 0000000..fa56d90 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/RoosterRepositoryTest.java @@ -0,0 +1,29 @@ +package de.thpeetz.kontor.tysc.data; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class RoosterRepositoryTest { + + @Autowired + private RoosterRepository roosterRepository; + + @Test + void testFindByReferences() { + List roosters = roosterRepository.findAll(); + Rooster existingRooster = roosters.get(4); + Team team = existingRooster.getTeam(); + Player player = existingRooster.getPlayer(); + FieldPosition position = existingRooster.getPosition(); + int year = existingRooster.getYear(); + Rooster rooster = roosterRepository.findByReferences(player, team, position, year); + assertNotNull(rooster); + assertEquals(existingRooster.getId(), rooster.getId()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/tysc/data/RoosterTest.java b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/RoosterTest.java new file mode 100644 index 0000000..caffea4 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/RoosterTest.java @@ -0,0 +1,47 @@ +package de.thpeetz.kontor.tysc.data; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DataIntegrityViolationException; + +import de.thpeetz.kontor.tysc.TestConstants; + +@SpringBootTest +class RoosterTest { + + @Autowired + private RoosterRepository roosterRepository; + + @Test + void checkInitialDataLoad() { + List roosters = roosterRepository.findAll(); + assertEquals(TestConstants.ROOSTER_COUNT, roosters.size()); + } + + @Test + void checkFetchingData() { + Rooster rooster = roosterRepository.findAll().get(4); + assertNotNull(rooster.getTeam()); + assertNotNull(rooster.getPlayer()); + assertNotNull(rooster.getPosition()); + } + + @Test + void exceptionThrownWhenSavingDuplicateRooster() { + List roosters = roosterRepository.findAll(); + Rooster existingRooster = roosters.get(5); + Rooster rooster = new Rooster(); + rooster.setPlayer(existingRooster.getPlayer()); + rooster.setTeam(existingRooster.getTeam()); + rooster.setPosition(existingRooster.getPosition()); + rooster.setYear(existingRooster.getYear()); + assertThrows(DataIntegrityViolationException.class, () -> { + roosterRepository.save(rooster); + }); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/tysc/data/SportRepositoryTest.java b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/SportRepositoryTest.java new file mode 100644 index 0000000..c0db4be --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/SportRepositoryTest.java @@ -0,0 +1,40 @@ +package de.thpeetz.kontor.tysc.data; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import de.thpeetz.kontor.tysc.TestConstants; + +@SpringBootTest +class SportRepositoryTest { + + @Autowired + private SportRepository sportRepository; + + @Test + void testFindByName() { + Sport sport = sportRepository.findByName(TestConstants.FOOTBALL_NAME); + assertNotNull(sport); + assertEquals(TestConstants.FOOTBALL_TEAM_COUNT, sport.getTeams().size()); + assertEquals(TestConstants.FOOTBALL_POSITION_COUNT, sport.getPositions().size()); + } + + @Test + void testFindByNameIgnoreCase() { + List sports = sportRepository.findByNameIgnoreCase(TestConstants.FOOTBALL_NAME.toLowerCase()); + assertNotNull(sports); + assertEquals(TestConstants.FOOTBALL_TEAM_COUNT, sports.get(0).getTeams().size()); + } + + @Test + void testSearch() { + List sports = sportRepository.search("ball"); + assertEquals(3, sports.size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/tysc/data/SportTest.java b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/SportTest.java new file mode 100644 index 0000000..e47bb50 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/SportTest.java @@ -0,0 +1,44 @@ +package de.thpeetz.kontor.tysc.data; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.transaction.TransactionSystemException; + +import de.thpeetz.kontor.tysc.TestConstants; + +@SpringBootTest +class SportTest { + + @Autowired + private SportRepository sportRepository; + + @Test + void checkInitialDataLoad() { + List sports = sportRepository.findAll(); + assertEquals(TestConstants.SPORT_COUNT, sports.size()); + } + + @Test + void exceptionThrownWhenSavingSportWithEmptyName() { + Sport sport = new Sport(); + assertThrows(TransactionSystemException.class, () -> { + sportRepository.save(sport); + }); + } + + @Test + void exceptionThrownWhenSavingSportWithExistingName() { + Sport existingSport = sportRepository.findAll().get(0); + Sport sport = new Sport(); + sport.setName(existingSport.getName()); + assertThrows(DataIntegrityViolationException.class, () -> { + sportRepository.save(sport); + }); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/tysc/data/TeamRepositoryTest.java b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/TeamRepositoryTest.java new file mode 100644 index 0000000..86f4200 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/TeamRepositoryTest.java @@ -0,0 +1,37 @@ +package de.thpeetz.kontor.tysc.data; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import de.thpeetz.kontor.tysc.TestConstants; + +@SpringBootTest +class TeamRepositoryTest { + + @Autowired + private TeamRepository teamRepository; + + @Test + void testFindByName() { + Team team = teamRepository.findByName(TestConstants.DOLPHINS_NAME); + assertEquals(TestConstants.DOLPHINS_SHORT, team.getShortName()); + } + + @Test + void testFindByNameIgnoreCase() { + List teams = teamRepository.findByNameIgnoreCase(TestConstants.DOLPHINS_NAME.toLowerCase()); + assertEquals(1, teams.size()); + assertEquals(TestConstants.DOLPHINS_SHORT, teams.get(0).getShortName()); + } + + @Test + void testSearch() { + List teams = teamRepository.search("dol"); + assertEquals(1, teams.size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/tysc/data/TeamTest.java b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/TeamTest.java new file mode 100644 index 0000000..7bd4ba2 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/TeamTest.java @@ -0,0 +1,53 @@ +package de.thpeetz.kontor.tysc.data; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.TransactionSystemException; + +import de.thpeetz.kontor.tysc.TestConstants; + +@SpringBootTest +class TeamTest { + + @Autowired + private TeamRepository teamRepository; + + @Test + void checkInitialDataLoad() { + List teams = teamRepository.findAll(); + assertEquals(TestConstants.TEAM_COUNT, teams.size()); + } + + @Test + void throwExceptionWhenTeamSavedWithEmptyName() { + Team team = new Team(); + assertThrows(TransactionSystemException.class, () -> { + teamRepository.save(team); + }); + } + + @Test + void throwExceptionWhenTeamSavedWithoutSport() { + Team team = new Team(); + team.setName(TestConstants.TEAM_NAME); + assertThrows(TransactionSystemException.class, () -> { + teamRepository.save(team); + }); + } + + @Test + void throwExceptionWhenTeamSavedWithExistingName() { + Team existingTeam = teamRepository.findAll().get(11); + Team team = new Team(); + team.setName(existingTeam.getName()); + team.setSport(existingTeam.getSport()); + assertThrows(TransactionSystemException.class, () -> { + teamRepository.save(team); + }); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/tysc/data/VendorRepositoryTest.java b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/VendorRepositoryTest.java new file mode 100644 index 0000000..9db7ef7 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/VendorRepositoryTest.java @@ -0,0 +1,41 @@ +package de.thpeetz.kontor.tysc.data; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import de.thpeetz.kontor.tysc.TestConstants; + +@SpringBootTest +class VendorRepositoryTest { + + @Autowired + private VendorRepository vendorRepository; + + @Test + void testFindByName() { + Vendor vendor = vendorRepository.findByName(TestConstants.TOPPS_NAME); + assertNotNull(vendor); + assertEquals(TestConstants.TOPPS_NAME, vendor.getName()); + } + + @Test + void testFindByNameIgnoreCase() { + List vendors = vendorRepository.findByNameIgnoreCase(TestConstants.TOPPS_NAME.toLowerCase()); + assertNotNull(vendors); + assertEquals(1, vendors.size()); + assertEquals(TestConstants.TOPPS_NAME, vendors.get(0).getName()); + } + + @Test + void testSearch() { + List vendors = vendorRepository.search("pp"); + assertNotNull(vendors); + assertEquals(2, vendors.size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/tysc/data/VendorTest.java b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/VendorTest.java new file mode 100644 index 0000000..a16bd77 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/tysc/data/VendorTest.java @@ -0,0 +1,44 @@ +package de.thpeetz.kontor.tysc.data; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.transaction.TransactionSystemException; + +import de.thpeetz.kontor.tysc.TestConstants; + +@SpringBootTest +class VendorTest { + + @Autowired + private VendorRepository vendorRepository; + + @Test + void checkInitialDataLoad() { + List vendors = vendorRepository.findAll(); + assertEquals(TestConstants.VENDOR_COUNT, vendors.size()); + } + + @Test + void exceptionThrownWhenSavingVendortWithEmptyName() { + Vendor vendor = new Vendor(); + assertThrows(TransactionSystemException.class, () -> { + vendorRepository.save(vendor); + }); + } + + @Test + void exceptionThrownWhenSavingSportWithExistingName() { + Vendor vendor = new Vendor(); + vendor.setName(TestConstants.TOPPS_NAME); + assertThrows(DataIntegrityViolationException.class, () -> { + vendorRepository.save(vendor); + }); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/tysc/services/CardServiceTest.java b/springboot/src/test/java/de/thpeetz/kontor/tysc/services/CardServiceTest.java new file mode 100644 index 0000000..e215217 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/tysc/services/CardServiceTest.java @@ -0,0 +1,126 @@ +package de.thpeetz.kontor.tysc.services; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import de.thpeetz.kontor.tysc.TestConstants; +import de.thpeetz.kontor.tysc.data.Card; +import de.thpeetz.kontor.tysc.data.CardSet; +import de.thpeetz.kontor.tysc.data.Rooster; +import de.thpeetz.kontor.tysc.data.Vendor; + +@SpringBootTest +@TestMethodOrder(OrderAnnotation.class) +class CardServiceTest { + + @Autowired + private CardService cardService; + + @Autowired + private SportService sportService; + + @Test + @Order(1) + void testFindAllVendors() { + assertEquals(TestConstants.VENDOR_COUNT, cardService.findAllVendors(null).size()); + assertEquals(2, cardService.findAllVendors("pp").size()); + } + + @Test + @Order(2) + void testSaveVendor() { + Vendor vendor = new Vendor(); + vendor.setName(TestConstants.VENDOR_NAME); + vendor = cardService.saveVendor(vendor); + assertNotNull(vendor); + assertEquals(TestConstants.VENDOR_COUNT + 1, cardService.findAllVendors(null).size()); + } + + @Test + @Order(3) + void testDeleteVendor() { + List vendors = cardService.findAllVendors(TestConstants.VENDOR_NAME); + assertEquals(1, vendors.size()); + Vendor vendor = vendors.get(0); + cardService.deleteVendor(vendor); + assertEquals(TestConstants.VENDOR_COUNT, cardService.findAllVendors(null).size()); + } + + @Test + @Order(4) + void testFindAllCardSets() { + assertEquals(TestConstants.CARDSET_COUNT, cardService.findAllCardSets(null).size()); + assertEquals(1, cardService.findAllCardSets("Ultra").size()); + } + + @Test + @Order(5) + void testSaveCardSet() { + List vendors = cardService.findAllVendors(TestConstants.VENDOR_FLEER); + assertEquals(1, vendors.size()); + Vendor vendor = vendors.get(0); + CardSet cardSet = new CardSet(); + cardSet.setName(TestConstants.CARDSET_NAME); + cardSet.setVendor(vendor); + cardSet.setInsertSet(false); + cardSet.setParallelSet(false); + cardService.saveCardSet(cardSet); + assertEquals(TestConstants.CARDSET_COUNT + 1, cardService.findAllCardSets(null).size()); + } + + @Test + @Order(6) + void testDeleteCardSet() { + List cardSets = cardService.findAllCardSets(TestConstants.CARDSET_NAME); + assertEquals(1, cardSets.size()); + CardSet cardSet = cardSets.get(0); + cardService.deleteCardSet(cardSet); + assertEquals(TestConstants.CARDSET_COUNT, cardService.findAllCardSets(null).size()); + } + + @Test + @Order(7) + void testFindAllCards() { + assertEquals(TestConstants.CARD_COUNT, cardService.findAllCards(null).size()); + assertEquals(1, cardService.findAllCards("112").size()); + } + + @Test + @Order(8) + void testSaveCard() { + List vendors = cardService.findAllVendors(TestConstants.VENDOR_FLEER); + assertEquals(1, vendors.size()); + Vendor vendor = vendors.get(0); + List cardSets = cardService.findAllCardSets(TestConstants.CARDSET_MYSTIQUE_NAME); + assertEquals(3, cardSets.size()); + CardSet cardSet = cardSets.get(0); + List roosters = sportService.findAllRoosters(); + Rooster rooster = roosters.get(0); + Card card = new Card(); + card.setCardNumber(TestConstants.CARD_CARDNUMBER); + card.setCardSet(cardSet); + card.setVendor(vendor); + card.setRooster(rooster); + card.setYear(TestConstants.CARD_YEAR); + cardService.saveCard(card); + assertEquals(TestConstants.CARD_COUNT + 1, cardService.findAllCards(null).size()); + } + + @Test + @Order(9) + void testDeleteCard() { + List cards = cardService.findAllCards(TestConstants.CARD_CARDNUMBER.toString()); + assertEquals(1, cards.size()); + Card card = cards.get(0); + cardService.deleteCard(card); + assertEquals(TestConstants.CARD_COUNT, cardService.findAllCards(null).size()); + } +} diff --git a/springboot/src/test/java/de/thpeetz/kontor/tysc/services/SportServiceTest.java b/springboot/src/test/java/de/thpeetz/kontor/tysc/services/SportServiceTest.java new file mode 100644 index 0000000..30b7ed6 --- /dev/null +++ b/springboot/src/test/java/de/thpeetz/kontor/tysc/services/SportServiceTest.java @@ -0,0 +1,200 @@ +package de.thpeetz.kontor.tysc.services; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import de.thpeetz.kontor.tysc.data.FieldPosition; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import de.thpeetz.kontor.tysc.TestConstants; +import de.thpeetz.kontor.tysc.data.Player; +import de.thpeetz.kontor.tysc.data.Rooster; +import de.thpeetz.kontor.tysc.data.Sport; +import de.thpeetz.kontor.tysc.data.Team; + +@SpringBootTest +@TestMethodOrder(OrderAnnotation.class) +class SportServiceTest { + + @Autowired + private SportService sportService; + + + @Test + @Order(1) + void testFindAllSports() { + List sports = sportService.findAllSports(null); + assertEquals(TestConstants.SPORT_COUNT, sports.size()); + assertEquals(3, sportService.findAllSports("ball").size()); + } + + @Test + @Order(2) + void testSaveSport() { + Sport sport = new Sport(); + sport.setName(TestConstants.SPORT_NAME); + sport = sportService.saveSport(sport); + assertNotNull(sport); + assertEquals(TestConstants.SPORT_COUNT+1, sportService.findAllSports(null).size()); + } + + @Test + @Order(3) + void testDeleteSport() { + List sports = sportService.findAllSports(TestConstants.SPORT_NAME); + assertEquals(1, sports.size()); + Sport sport = sports.get(0); + sportService.deleteSport(sport); + assertEquals(TestConstants.SPORT_COUNT, sportService.findAllSports(null).size()); + } + + @Test + @Order(4) + void testFindAllPlayers() { + assertEquals(TestConstants.PLAYER_COUNT, sportService.findAllPlayers(null).size()); + assertEquals(TestConstants.PLAYER_CHRIS_COUNT, sportService.findAllPlayers(TestConstants.PLAYER_CHRIS_NAME).size()); + } + + @Test + @Order(5) + void testSavePlayer() { + Player player = new Player(); + player.setFirstName(TestConstants.PLAYER_FIRSTNAME); + player.setLastName(TestConstants.PLAYER_LASTNAME); + player = sportService.savePlayer(player); + assertNotNull(player); + } + + @Test + @Order(6) + void testDeletePlayer() { + List players = sportService.findAllPlayers(TestConstants.PLAYER_LASTNAME); + assertEquals(1, players.size()); + Player player = players.get(0); + sportService.deletePlayer(player); + assertEquals(TestConstants.PLAYER_COUNT, sportService.findAllPlayers(null).size()); + } + + + @Test + @Order(7) + void testFindAllPositions() { + assertEquals(TestConstants.POSITION_COUNT, sportService.findAllPositions(null).size()); + assertEquals(TestConstants.POSITION_CENTER_COUNT, sportService.findAllPositions(TestConstants.POSITION_CENTER).size()); + } + + @Test + @Order(8) + void testFindAllPositionsForSport() { + List sports = sportService.findAllSports(TestConstants.FOOTBALL_NAME); + assertEquals(1, sports.size()); + Sport football = sports.get(0); + assertEquals(TestConstants.FOOTBALL_POSITION_COUNT, sportService.findAllPositionsForSport(football).size()); + } + + @Test + @Order(9) + void testSavePosition() { + List sports = sportService.findAllSports(TestConstants.FOOTBALL_NAME); + Sport football = sports.get(0); + FieldPosition position = new FieldPosition(); + position.setSport(football); + position.setName(TestConstants.POSITION_NAME); + position.setShortName(TestConstants.POSITION_SHORTNAME); + sportService.savePosition(position); + assertEquals(TestConstants.POSITION_COUNT+1, sportService.findAllPositions(null).size()); + } + + @Test + @Order(10) + void testDeletePosition() { + List positions = sportService.findAllPositions(TestConstants.POSITION_NAME); + assertEquals(1, positions.size()); + FieldPosition position = positions.get(0); + sportService.deletePosition(position); + assertEquals(TestConstants.POSITION_COUNT, sportService.findAllPositions(null).size()); + } + + @Test + @Order(11) + void testFindAllTeams() { + assertEquals(TestConstants.TEAM_COUNT, sportService.findAllTeams(null).size()); + assertEquals(1, sportService.findAllTeams("sharks").size()); + } + + @Test + @Order(12) + void testSaveTeam() { + List sports = sportService.findAllSports(TestConstants.FOOTBALL_NAME); + Sport football = sports.get(0); + Team team = new Team(); + team.setSport(football); + team.setName(TestConstants.TEAM_NAME); + team.setShortName(TestConstants.TEAM_SHORTNAME); + sportService.saveTeam(team); + assertEquals(TestConstants.TEAM_COUNT+1, sportService.findAllTeams(null).size()); + } + + @Test + @Order(13) + void testDeleteTeam() { + List teams = sportService.findAllTeams(TestConstants.TEAM_NAME); + assertEquals(1, teams.size()); + Team team = teams.get(0); + sportService.deleteTeam(team); + assertEquals(TestConstants.TEAM_COUNT, sportService.findAllTeams(null).size()); + } + + @Test + @Order(14) + void testFindAllRoosters() { + assertEquals(TestConstants.ROOSTER_COUNT, sportService.findAllRoosters().size()); + } + + @Test + @Order(15) + void testFindRoosterByFields() { + List roosters = sportService.findAllRoosters(); + Rooster existingRooster = roosters.get(4); + Team team = existingRooster.getTeam(); + Player player = existingRooster.getPlayer(); + FieldPosition position = existingRooster.getPosition(); + int year = existingRooster.getYear(); + Rooster rooster = sportService.findRoosterByFields(team, player, position, year); + assertNotNull(rooster); + assertEquals(existingRooster.getId(), rooster.getId()); + } + + @Test + @Order(16) + void testSaveRooster() { + Rooster rooster = new Rooster(); + Player player = sportService.findAllPlayers(TestConstants.PLAYER_CHRIS_NAME).get(0); + rooster.setPlayer(player); + Team team = sportService.findAllTeams(TestConstants.DOLPHINS_NAME).get(0); + rooster.setTeam(team); + FieldPosition position = sportService.findAllPositions(TestConstants.QUARTERBACK_NAME).get(0); + rooster.setPosition(position); + rooster.setYear(TestConstants.ROOSTER_YEAR); + sportService.saveRooster(rooster); + assertEquals(TestConstants.ROOSTER_COUNT+1, sportService.findAllRoosters().size()); + } + + @Test + @Order(17) + void testDeleteRooster() { + Team team = sportService.findAllTeams(TestConstants.DOLPHINS_NAME).get(0); + Player player = sportService.findAllPlayers(TestConstants.PLAYER_CHRIS_NAME).get(0); + FieldPosition position = sportService.findAllPositions(TestConstants.QUARTERBACK_NAME).get(0); + Rooster rooster = sportService.findRoosterByFields(team, player, position, TestConstants.ROOSTER_YEAR); + assertNotNull(rooster); + sportService.deleteRooster(rooster); + assertEquals(TestConstants.ROOSTER_COUNT, sportService.findAllRoosters().size()); + } +} diff --git a/springboot/src/test/resources/application.properties b/springboot/src/test/resources/application.properties new file mode 100644 index 0000000..53be2f4 --- /dev/null +++ b/springboot/src/test/resources/application.properties @@ -0,0 +1,30 @@ +server.port=8085 + +spring.hibernate.dialect=org.hibernate.dialect.HSQLDialect +spring.jpa.database-platform=org.hibernate.dialect.HSQLDialect +spring.datasource.driverClassName=org.hsqldb.jdbc.JDBCDriver +spring.datasource.url=jdbc:hsqldb:mem:testDb +spring.datasource.username=sa +spring.datasource.password=sa + +#spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect +#spring.datasource.driverClassName=org.sqlite.JDBC +#spring.datasource.url=jdbc:sqlite:file:./kontorTesrDb?cache=shared +#spring.datasource.username=sa +#spring.datasource.password=sa + +spring.jpa.defer-datasource-initialization = true +#spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=false +spring.sql.init.mode=always + +spring.mustache.check-template-location = false + +logging.level.org.atmosphere=INFO +logging.level.org.springframework.web=INFO +logging.level.guru.springframework.controllers=DEBUG +logging.level.org.hibernate=INFO +logging.level.de.thpeetz=DEBUG + +jwt.auth.secret=J6GOtcwC2NJI1l0VkHu20PacPFGTxpirBxWwynoHjsc=