Import kontor-flask into directory flask #46

Merged
tpeetz merged 16 commits from feature/4-import-kontor-flask-into-directory-flask into develop/0.1.0 2025-01-08 21:44:48 +00:00
309 changed files with 21223 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
.idea/
__pycache__/
bonus/
icons/
icons-shadowless/
.vscode/
+36
View File
@@ -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"
+2
View File
@@ -0,0 +1,2 @@
__pycache__/
.idea
+2
View File
@@ -0,0 +1,2 @@
# Kontor Flask
+5
View File
@@ -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)
+61
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
from flask import Blueprint
api_bp = Blueprint('api_bp', __name__)
from kontor.api import routes
+30
View File
@@ -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}!'}
+22
View File
@@ -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
+45
View File
@@ -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"))
+9
View File
@@ -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
+28
View File
@@ -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/<string:comic_id>')
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/<string:id>')
def publisher_detail(publisher_id):
publisher = Publisher.query.get(publisher_id)
return publisher_schema.dump(publisher)
+16
View File
@@ -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/<string:comic_id>')
def view(comic_id):
comic = Comic.query.get(comic_id)
return render_template('comics/view.html', comic=comic)
@@ -0,0 +1,28 @@
{% extends 'base.html' %}
{% block content %}
<span class="title">
<h1>{% block title %} Comics {% endblock %}</h1>
</span>
<div class="comics">
<h2>Comics Blueprint</h2>
{% for comic in comics %}
<div class="comic">
<b>
<p class="name">
<a href="{{ url_for('comics_bp.view', comic_id=comic.id)}}">{{ comic.title }}</a>
</p>
</b>
<b><p class="identifier">{{ comic.publisher.name }}</p></b>
<div class="date">
<h4>Created</h4>
<p>{{ comic.created_date }}</p>
<h4>Modified</h4>
<p>{{ comic.last_modified_date }}</p>
</div>
<p>Version: {{ comic.version }}</p>
<p>Review: {{ comic.review }}</p>
</div>
{% endfor %}
</div>
{% endblock %}
@@ -0,0 +1,27 @@
{% extends 'base.html' %}
{% block content %}
<span class="title">
<h1>{% block title %} Comics {% endblock %}</h1>
</span>
<div class="comics">
<h2>Comics Blueprint</h2>
<div class="comic">
<b>
<p class="name">
<a href="{{ url_for('comics_bp.view', comic_id=comic.id)}}">{{ comic.title }}</a>
</p>
</b>
<b>
<p class="identifier">{{ comic.id }}</p>
</b>
<div class="date">
<h4>Created</h4>
<p>{{ comic.created_date }}</p>
<h4>Modified</h4>
<p>{{ comic.last_modified_date }}</p>
</div>
<p>Version: {{ comic.version }}</p>
</div>
</div>
{% endblock %}
+10
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
import flask_sqlalchemy
from flask_marshmallow import Marshmallow
db = flask_sqlalchemy.SQLAlchemy()
ma = Marshmallow()
+5
View File
@@ -0,0 +1,5 @@
from flask import Blueprint
bp = Blueprint('main', __name__)
from kontor.main import routes
+18
View File
@@ -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')
+9
View File
@@ -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
+18
View File
@@ -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/<string:mediafile_id>')
def mediafile_detail(file_id):
file = MediaFile.query.get(file_id)
return mediafile_schema.dump(file)
+43
View File
@@ -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="<id>")),
"collection": ma.URLFor("media_api.mediafile_list"),
}
)
mediafile_schema = MediaFileSchema()
mediafiles_schema = MediaFileSchema(many=True)
+16
View File
@@ -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/<string:mediafile_id>')
def mediafile_detail(mediafile_id):
mediafile = MediaFile.query.get(mediafile_id)
return render_template('media/mediafile_detail.html', mediafile=mediafile)
@@ -0,0 +1,29 @@
{% extends 'base.html' %}
{% block content %}
<span class="title">
<h1>{% block title %} MediaFile {% endblock %}</h1>
</span>
<div class="comics">
<h2>MediaFile Blueprint</h2>
<div class="comic">
<b>
<p class="name">
<a href="{{ url_for('media_bp.mediafile_detail', mediafile_id=mediafile.id)}}">{{ mediafile.title }}</a>
</p>
</b>
<b>
<p class="identifier">{{ mediafile.id }}</p>
</b>
<div class="date">
<h4>Created</h4>
<p>{{ mediafile.created_date }}</p>
<h4>Modified</h4>
<p>{{ mediafile.last_modified_date }}</p>
</div>
<p>Version: {{ mediafile.version }}</p>
<p>Review: {{ mediafile.is_review() }}</p>
<p>Should Download: {{mediafile.is_download() }}</p>
</div>
</div>
{% endblock %}
@@ -0,0 +1,30 @@
{% extends 'base.html' %}
{% block content %}
<span class="title">
<h1>{% block title %} MediaFile {% endblock %}</h1>
</span>
<div class="comic">
<h2>MediaFile Blueprint</h2>
{% for mediafile in mediafiles %}
<div class="comic">
<b>
<p class="name">
<a href="{{ url_for('media_bp.mediafile_detail', mediafile_id=mediafile.id)}}">{{ mediafile.title }}</a>
</p>
</b>
<b><p class="identifier">{{ mediafile.id }}</p></b>
<div class="date">
<h4>Created</h4>
<p>{{ mediafile.created_date }}</p>
<h4>Modified</h4>
<p>{{ mediafile.last_modified_date }}</p>
</div>
<p>Version: {{ mediafile.version }}</p>
<p>Review: {{ mediafile.is_review() }}</p>
<p>Should Download: {{ mediafile.is_download() }}</p>
<p>Links: {{ mediafile._links }}</p>
</div>
{% endfor %}
</div>
{% endblock %}
+49
View File
@@ -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)
+68
View File
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %} {% endblock %} - FlaskApp</title>
<style>
h2 {
width: 100%;
}
.title {
margin: 5px;
width: 100%;
}
.content {
margin: 5px;
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.comic {
flex: 20%;
padding: 10px;
margin: 5px;
background-color: #f3f3f3;
inline-size: 100%;
}
.identifier a {
color: #00a36f;
text-decoration: none;
}
.date {
padding: 10px;
margin: 5px;
background-color: #ffffff;
color: #004835;
}
nav a {
color: #d64161;
font-size: 3em;
margin-left: 50px;
text-decoration: none;
}
.title a {
color: #00a36f;
text-decoration: none;
}
</style>
</head>
<body>
<nav>
<a href="{{ url_for('main.index') }}">Kontor</a>
<a href="{{ url_for('comics_bp.index') }}">Comics</a>
<a href="{{ url_for('media_bp.mediafile_list') }}">Media</a>
</nav>
<hr>
<div class="content">
{% block content %} {% endblock %}
</div>
</body>
</html>
+8
View File
@@ -0,0 +1,8 @@
{% extends 'base.html' %}
{% block content %}
<span class="title"><h1>{% block title %} The Home Page of Kontor {% endblock %}</h1></span>
<div class="content">
<h2>This is the main Flask blueprint</h2>
</div>
{% endblock %}
+89
View File
@@ -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
+79
View File
@@ -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
+106
View File
@@ -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}")
+151
View File
@@ -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()
+58
View File
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 836 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

+7
View File
@@ -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")
+83
View File
@@ -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
+30
View File
@@ -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
}
+9
View File
@@ -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
+32
View File
@@ -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
+3
View File
@@ -0,0 +1,3 @@
# kontor-spring
Kontor Anwendung mit Spring Boot und Vaadin
+240
View File
@@ -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"
}
@@ -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;
}
@@ -0,0 +1,3 @@
{
"lumoImports" : [ "typography", "color", "spacing", "badge", "utility" ]
}
+3
View File
@@ -0,0 +1,3 @@
description='Kontor with Spring Boot'
version=0.1.0-SNAPSHOT
group=de.thpeetz
+58
View File
@@ -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" }
Binary file not shown.
+7
View File
@@ -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
Vendored Executable
+249
View File
@@ -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" "$@"
+92
View File
@@ -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
+24
View File
@@ -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'
@@ -0,0 +1,509 @@
= Projektbeschreibung kontor-spring: Entwicklungs- und Projekthandbuch
:author: Thomas Peetz
:email: <thomas.peetz@thpeetz.de>
: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))<<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))<<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))<<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
<<Table-1, Tabelle 1>> <<Table-1>>
@@ -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<Artist> 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<Artist> grid) {
int count = grid.getListDataView().getItemCount();
List<Artist> artists = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(5, count);
return artists.get(0);
}
}
@@ -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<Artist> 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<Artist> deletedArtistReference = new AtomicReference<>(null);
form.addDeleteListener(e -> {
deletedArtistReference.set(e.getArtist());
});
form.delete.click();
Artist deletedArtist = deletedArtistReference.get();
assertEquals(ARTISTNAME, deletedArtist.getName());
}
}
@@ -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<Comic> 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<Comic> grid) {
int count = grid.getListDataView().getItemCount();
List<Comic> comics = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(169, count);
return comics.get(0);
}
}
@@ -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<ComicWork> 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<ComicWork> grid) {
int count = grid.getListDataView().getItemCount();
List<ComicWork> comicWorks = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(18, count);
return comicWorks.get(0);
}
}
@@ -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<Issue> 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<Issue> grid) {
int count = grid.getListDataView().getItemCount();
List<Issue> issues = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(750, count);
return issues.get(0);
}
}
@@ -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<Publisher> 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<Publisher> grid) {
int count = grid.getListDataView().getItemCount();
List<Publisher> publishers = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(18, count);
return publishers.get(0);
}
}
@@ -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<StoryArc> 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<StoryArc> grid) {
int count = grid.getListDataView().getItemCount();
List<StoryArc> storyArcs = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(3, count);
return storyArcs.get(0);
}
}
@@ -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<TradePaperback> 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<TradePaperback> grid) {
int count = grid.getListDataView().getItemCount();
List<TradePaperback> tradePaperbacks = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(40, count);
return tradePaperbacks.get(0);
}
}
@@ -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<Volume> 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<Volume> grid) {
int count = grid.getListDataView().getItemCount();
List<Volume> volumes = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(0, count);
if (count > 0) {
return volumes.get(0);
} else {
return null;
}
}
}
@@ -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<Worktype> 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<Worktype> grid) {
int count = grid.getListDataView().getItemCount();
List<Worktype> worktypes = grid.getListDataView().getItems().collect(Collectors.toList());
log.info("found worktypes: {}", worktypes);
assertEquals(3, count);
return worktypes.get(0);
}
}
@@ -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<CardSet> 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<CardSet> grid) {
int count = grid.getListDataView().getItemCount();
List<CardSet> cardSets = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(15, count);
return cardSets.get(0);
}
}
@@ -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<Card> 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<Card> grid) {
int count = grid.getListDataView().getItemCount();
List<Card> cards = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(10, count);
return cards.get(0);
}
}
@@ -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<FieldPosition> 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<FieldPosition> grid) {
int count = grid.getListDataView().getItemCount();
List<FieldPosition> positions = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(44, count);
return positions.get(0);
}
}
@@ -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<Player> 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<Player> grid) {
int count = grid.getListDataView().getItemCount();
List<Player> players = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(38, count);
return players.get(0);
}
}
@@ -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<Rooster> 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<Rooster> grid) {
int count = grid.getListDataView().getItemCount();
List<Rooster> roosters = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(11, count);
return roosters.get(0);
}
}
@@ -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<Sport> 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<Sport> grid) {
int count = grid.getListDataView().getItemCount();
List<Sport> sports = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(4, count);
return sports.get(0);
}
}
@@ -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<Team> 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<Team> grid) {
int count = grid.getListDataView().getItemCount();
List<Team> teams = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(122, count);
return teams.get(0);
}
}
@@ -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<Vendor> 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<Vendor> grid) {
int count = grid.getListDataView().getItemCount();
List<Vendor> vendors = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(9, count);
return vendors.get(0);
}
}
@@ -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=
@@ -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);
}
}
@@ -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;
}
}
@@ -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();
}
}
@@ -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<ContextRefreshedEvent> {
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<User> 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<MailAccount> 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<AuthorizationMatrix> 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);
}
}
@@ -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();
}
}
@@ -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<AuthorizationMatrix, String> {
List<AuthorizationMatrix> findByUser(User user);
List<AuthorizationMatrix> findByRole(Role role);
}
@@ -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<MailAccount, String> {
}
@@ -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();
}
}
@@ -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<MetaDataColumn, String> {
List<MetaDataColumn> 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<MetaDataColumn> search(@Param("searchTerm") String searchTerm);
}
@@ -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<MetaDataColumn> tableColumns = new LinkedList<>();
}
@@ -0,0 +1,8 @@
package de.thpeetz.kontor.admin.data;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MetaDataTableRepository extends JpaRepository<MetaDataTable, String> {
MetaDataTable findByTableName(String tableName);
}
@@ -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;
}
@@ -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<ModuleData, String> {
@Query("select m from ModuleData m where lower(m.moduleName) like lower(concat('%', :searchTerm, '%')) ")
List<ModuleData> search(@Param("searchTerm") String searchTerm);
ModuleData findByModuleName(String moduleName);
}
@@ -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<AuthorizationMatrix> matrix;
}
@@ -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<Role, String> {
@Query("select r from Role r " +
"where lower(r.name) like lower(concat('%', :searchTerm, '%')) ")
List<Role> search(@Param("searchTerm") String searchTerm);
@Query("select r from Role r " +
"where lower(r.name) like lower(:name) ")
Role findByName(@Param("name") String name);
}
@@ -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<AuthorizationMatrix> 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();
}
}
@@ -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<User, String> {
@Query("select u from User u " +
"where lower(u.lastName) like lower(concat('%', :searchTerm, '%')) ")
List<User> search(@Param("searchTerm") String searchTerm);
User findByUserName(String userName);
}
@@ -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<User> findAllUsers() {
return userRepository.findAll();
}
public List<Role> findAllRoles() {
return roleRepository.findAll();
}
public Collection<Role> 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<AuthorizationMatrix> 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);
}
}
@@ -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<User> 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<Role> roles) {
if (user == null) {
log.warn("User is null. Can't save it.");
}
log.info("First save user: {}", user);
user = userRepository.save(user);
List<Role> copy = roles.stream().collect(Collectors.toList());
List<AuthorizationMatrix> 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<? extends GrantedAuthority> 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<? extends GrantedAuthority> 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<Role> findAllRoles() {
return roleRepository.findAll();
}
}
@@ -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<MailAccount> findAllMailAccounts() {
return mailAccountRepository.findAll();
}
}
@@ -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<MetaDataColumn> findAllMetaDataColumns(String stringFilter) {
if (stringFilter == null || stringFilter.isEmpty()) {
log.debug("Found " + metaDataColumnRepository.count()+ " entries");
return metaDataColumnRepository.findAll();
} else {
List<MetaDataColumn> 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<MetaDataTable> findAllTables() {
return metaDataTableRepository.findAll();
}
}
@@ -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<ModuleData> 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);
}
}
}
@@ -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;
}
}
@@ -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> user = new ComboBox<>("User");
ComboBox<Role> role = new ComboBox<>("Role");
Button save = new Button("Save");
Button delete = new Button("Delete");
Button close = new Button("Cancel");
Binder<AuthorizationMatrix> binder = new BeanValidationBinder<>(AuthorizationMatrix.class);
public AuthorizationForm(List<User> users, List<Role> 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<AuthorizationForm> {
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<DeleteEvent> listener) {
addListener(DeleteEvent.class, listener);
}
public void addSaveListener(ComponentEventListener<SaveEvent> listener) {
addListener(SaveEvent.class, listener);
}
public void addCloseListener(ComponentEventListener<CloseEvent> listener) {
addListener(CloseEvent.class, listener);
}
}
@@ -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<AuthorizationMatrix> 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());
}
}
@@ -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);
}
}
}

Some files were not shown because too many files have changed in this diff Show More