Merge branch 'main' into develop/0.2.0

This commit is contained in:
Thomas Peetz
2025-04-30 19:32:42 +02:00
93 changed files with 4219 additions and 218 deletions
Binary file not shown.
+14 -14
View File
@@ -7,15 +7,15 @@ from sqlalchemy.orm import relationship, mapped_column, Mapped
from src.db.models.base import Base, BaseMixin
class User(Base, BaseMixin):
__tablename__ = 'user'
class Profile(Base, BaseMixin):
__tablename__ = 'profile'
first_name = Column(String(255))
last_name = Column(String(255))
user_name = Column(String(255), nullable=False)
email = Column(String(255))
password = Column(String(255))
enabled = Column(BIT(1))
matrix = relationship("AuthorizationMatrix")
assignments = relationship("Assignment")
tokens = relationship("Token")
def get_full_name(self) -> str:
@@ -35,22 +35,22 @@ class Token(Base, BaseMixin):
name = Column(String(255))
last_used_date: Mapped[datetime] = mapped_column()
enabled = Column(BIT(1))
user_id = Column(String(255), ForeignKey("user.id"), nullable=False)
user = relationship("User", back_populates="tokens")
profile_id = Column(String(255), ForeignKey("profile.id"), nullable=False)
profile = relationship("Profile", back_populates="tokens")
class Role(Base, BaseMixin):
__tablename__ = "role"
class Permission(Base, BaseMixin):
__tablename__ = "permission"
name = Column(String(255), nullable=False)
matrix = relationship("AuthorizationMatrix")
assignments = relationship("Assignment")
class AuthorizationMatrix(Base, BaseMixin):
__tablename__ = "authorization_matrix"
user_id = Column(String, ForeignKey("user.id"), nullable=False)
user = relationship("User", back_populates="matrix")
role_id = Column(String, ForeignKey("role.id"), nullable=False)
role = relationship("Role", back_populates="matrix")
class Assignment(Base, BaseMixin):
__tablename__ = "assignment"
profile_id = Column(String, ForeignKey("profile.id"), nullable=False)
profile = relationship("Profile", back_populates="assignments")
permission_id = Column(String, ForeignKey("permission.id"), nullable=False)
permission = relationship("Permission", back_populates="assignments")
class ModuleData(Base, BaseMixin):
+1 -1
View File
@@ -1,5 +1,5 @@
"""
copy data from SQLite to MariaDB
copy data from JSON to MariaDB
"""
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
from pathlib import Path
+14 -14
View File
@@ -7,15 +7,15 @@ from sqlalchemy.orm import relationship, mapped_column, Mapped
from .base import Base, BaseMixin
class User(Base, BaseMixin):
__tablename__ = 'user'
class Profile(Base, BaseMixin):
__tablename__ = 'profile'
first_name = Column(String(255))
last_name = Column(String(255))
user_name = Column(String(255), nullable=False)
email = Column(String(255))
password = Column(String(255))
enabled = Column(BIT(1))
matrix = relationship("AuthorizationMatrix")
assignments = relationship("Assignment")
tokens = relationship("Token")
def get_full_name(self) -> str:
@@ -35,22 +35,22 @@ class Token(Base, BaseMixin):
name = Column(String(255))
last_used_date: Mapped[datetime] = mapped_column()
enabled = Column(BIT(1))
user_id = Column(String(255), ForeignKey("user.id"), nullable=False)
user = relationship("User", back_populates="tokens")
profile_id = Column(String(255), ForeignKey("profile.id"), nullable=False)
profile = relationship("Profile", back_populates="tokens")
class Role(Base, BaseMixin):
__tablename__ = "role"
class Permission(Base, BaseMixin):
__tablename__ = "permission"
name = Column(String(255), nullable=False)
matrix = relationship("AuthorizationMatrix")
assignments = relationship("Assignment")
class AuthorizationMatrix(Base, BaseMixin):
__tablename__ = "authorization_matrix"
user_id = Column(String, ForeignKey("user.id"), nullable=False)
user = relationship("User", back_populates="matrix")
role_id = Column(String, ForeignKey("role.id"), nullable=False)
role = relationship("Role", back_populates="matrix")
class Assignment(Base, BaseMixin):
__tablename__ = "assignment"
profile_id = Column(String, ForeignKey("profile.id"), nullable=False)
profile = relationship("Profile", back_populates="assignments")
permission_id = Column(String, ForeignKey("permission.id"), nullable=False)
permission = relationship("Permission", back_populates="assignments")
class ModuleData(Base, BaseMixin):
+4 -4
View File
@@ -13,7 +13,7 @@ from sqlalchemy.orm import sessionmaker
from .tysc import Card, CardSet, Rooster, Team, FieldPosition, Player, Vendor, Sport
from .comic import Issue, TradePaperback, StoryArc, Volume, ComicWork, Artist, Comic, Publisher, WorkType
from .bookshelf import ArticleAuthor, BookAuthor, BookshelfPublisher, Article, Book, Author
from .admin import Mail, MailAccount, ModuleData, Role, User, Token, AuthorizationMatrix
from .admin import Mail, MailAccount, ModuleData, Permission, Profile, Token, Assignment
from .metadata import MetaDataTable, MetaDataColumn
from .media import MediaVideo, MediaArticle, MediaFile, MediaActor, MediaActorFile
@@ -81,10 +81,10 @@ class KontorDB:
self.registry[MediaVideo.__tablename__] = MediaVideo
self.registry[MetaDataColumn.__tablename__] = MetaDataColumn
self.registry[MetaDataTable.__tablename__] = MetaDataTable
self.registry[AuthorizationMatrix.__tablename__] = AuthorizationMatrix
self.registry[Assignment.__tablename__] = Assignment
self.registry[Token.__tablename__] = Token
self.registry[User.__tablename__] = User
self.registry[Role.__tablename__] = Role
self.registry[Profile.__tablename__] = Profile
self.registry[Permission.__tablename__] = Permission
self.registry[ModuleData.__tablename__] = ModuleData
self.registry[MailAccount.__tablename__] = MailAccount
self.registry[Mail.__tablename__] = Mail
@@ -18,36 +18,36 @@ public class AdminConstants {
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 ASSIGNMENT_ROUTE = "/admin/assignment";
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 PERMISSION = "Berechtigungen";
public static final String PERMISSION_ROUTE = "/admin/permission";
public static final String PROFILE = "Benutzer";
public static final String PROFILE_ROUTE = "/admin/profile";
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 getProfileNavigation() {
return new RouterLink(PROFILE, ProfileView.class);
}
public static RouterLink getRoleNavigation() {
return new RouterLink(ROLE, RoleView.class);
public static RouterLink getPermissionNavigation() {
return new RouterLink(PERMISSION, PermissionView.class);
}
public static RouterLink getAuthorizationNavigation() {
return new RouterLink(AUTHORIZATION, AuthorizationView.class);
return new RouterLink(AUTHORIZATION, AssignmentView.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());
SideNavItem administration = new SideNavItem(ADMIN_TITLE, PROFILE_ROUTE, VaadinIcon.GROUP.create());
administration.addItem(new SideNavItem(PROFILE, PROFILE_ROUTE, VaadinIcon.USERS.create()));
administration.addItem(new SideNavItem(PERMISSION, PermissionView.class));
SideNavItem data = new SideNavItem(DATA, ASSIGNMENT_ROUTE, VaadinIcon.DATABASE.create());
data.addItem(new SideNavItem(ComicConstants.COMICWORK, ComicWorkView.class));
data.addItem(new SideNavItem(MediaConstants.MEDIAACTORFILE, MediaActorFileView.class));
data.addItem(new SideNavItem(AUTHORIZATION, AuthorizationView.class));
data.addItem(new SideNavItem(AUTHORIZATION, AssignmentView.class));
data.addItem(new SideNavItem("Data Import", ModuleDataView.class));
data.addItem(new SideNavItem("Meta Data", MetaDataView.class));
administration.addItem(data);
@@ -1,9 +1,7 @@
package de.thpeetz.kontor.admin;
import de.thpeetz.kontor.admin.data.*;
import de.thpeetz.kontor.admin.repository.AuthorizationMatrixRepository;
import de.thpeetz.kontor.admin.repository.MailAccountRepository;
import de.thpeetz.kontor.admin.repository.UserRepository;
import de.thpeetz.kontor.admin.repository.*;
import de.thpeetz.kontor.admin.services.AdminService;
import de.thpeetz.kontor.admin.services.MetaDataService;
import de.thpeetz.kontor.mailclient.data.MailAccount;
@@ -23,10 +21,10 @@ public class SetupModuleAdmin implements ApplicationListener<ContextRefreshedEve
boolean alreadySetup = false;
@Autowired
private UserRepository userRepository;
private ProfileRepository profileRepository;
@Autowired
private AuthorizationMatrixRepository authorizationMatrixRepository;
private AssignmentRepository assignmentRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@@ -50,13 +48,13 @@ public class SetupModuleAdmin implements ApplicationListener<ContextRefreshedEve
}
// 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);
Permission adminPermission = adminService.addPermission("ROLE_ADMIN");
Permission userPermission = adminService.addPermission("ROLE_USER");
List<Profile> profiles = profileRepository.findAll();
if (profiles.isEmpty()) {
Profile adminProfile = initAdminUser();
initAssignments(adminPermission, adminProfile);
initAssignments(userPermission, adminProfile);
}
log.info("MailProperties: {}", mailProperties);
initMail(mailProperties);
@@ -98,32 +96,32 @@ public class SetupModuleAdmin implements ApplicationListener<ContextRefreshedEve
}
}
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());
private void initAssignments(Permission permission, Profile profile) {
log.info("initAssignments: Permission {} for Profile {}", permission.getName(), profile.getUserName());
Collection<Assignment> configuredRoles = assignmentRepository.findByProfile(profile);
if (configuredRoles.stream().anyMatch(assignment -> assignment.getPermission().getId().equals(permission.getId()))) {
log.info("Permission {} already defined", permission.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);
log.info("add Permission {} to Profile {}", permission.getName(), profile.getUserName());
final Assignment assignment = new Assignment();
assignment.setProfile(profile);
assignment.setPermission(permission);
assignmentRepository.save(assignment);
}
}
private User initAdminUser() {
private Profile initAdminUser() {
log.info("initAdminUser");
User admin = userRepository.findByUserName("admin");
Profile admin = profileRepository.findByUserName("admin");
if (admin == null) {
log.info("User admin not found, will be created.");
admin = new User();
log.info("Profile admin not found, will be created.");
admin = new Profile();
admin.setFirstName("Admin");
admin.setLastName("Administrator");
admin.setUserName("admin");
admin.setEmail("admin@example.org");
admin.setPassword(passwordEncoder.encode("admin"));
userRepository.save(admin);
profileRepository.save(admin);
}
return admin;
}
@@ -348,17 +346,17 @@ public class SetupModuleAdmin implements ApplicationListener<ContextRefreshedEve
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);
MetaDataTable userTable = metaDataService.getTable("user");
metaDataService.getColumn(userTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.TRUE, "", Boolean.FALSE, null);
metaDataService.getColumn(userTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null);
metaDataService.getColumn(userTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null);
metaDataService.getColumn(userTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null);
metaDataService.getColumn(userTable, "first_name", "first_name", "TEXT", null, 5, Boolean.TRUE, "", Boolean.FALSE, null);
metaDataService.getColumn(userTable, "last_name", "last_name", "TEXT", null, 6, Boolean.TRUE, "", Boolean.FALSE, null);
metaDataService.getColumn(userTable, "user_name", "user_name", "TEXT", "UNIQUE", 7, Boolean.TRUE, "", Boolean.FALSE, null);
metaDataService.getColumn(userTable, "email", "email", "TEXT", null, 8, Boolean.TRUE, "", Boolean.FALSE, null);
metaDataService.getColumn(userTable, "password", "password", "TEXT", null, 9, Boolean.FALSE, "Password", Boolean.FALSE, null);
metaDataService.getColumn(userTable, "enabled", "enabled", "BOOLEAN", null, 10, Boolean.TRUE, "", Boolean.TRUE, null);
MetaDataTable profileTable = metaDataService.getTable("profile");
metaDataService.getColumn(profileTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.TRUE, "", Boolean.FALSE, null);
metaDataService.getColumn(profileTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null);
metaDataService.getColumn(profileTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null);
metaDataService.getColumn(profileTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null);
metaDataService.getColumn(profileTable, "first_name", "first_name", "TEXT", null, 5, Boolean.TRUE, "", Boolean.FALSE, null);
metaDataService.getColumn(profileTable, "last_name", "last_name", "TEXT", null, 6, Boolean.TRUE, "", Boolean.FALSE, null);
metaDataService.getColumn(profileTable, "user_name", "user_name", "TEXT", "UNIQUE", 7, Boolean.TRUE, "", Boolean.FALSE, null);
metaDataService.getColumn(profileTable, "email", "email", "TEXT", null, 8, Boolean.TRUE, "", Boolean.FALSE, null);
metaDataService.getColumn(profileTable, "password", "password", "TEXT", null, 9, Boolean.FALSE, "Password", Boolean.FALSE, null);
metaDataService.getColumn(profileTable, "enabled", "enabled", "BOOLEAN", null, 10, Boolean.TRUE, "", Boolean.TRUE, null);
MetaDataTable tokenTable = metaDataService.getTable("token");
metaDataService.getColumn(tokenTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.TRUE, "", Boolean.FALSE, null);
metaDataService.getColumn(tokenTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null);
@@ -368,20 +366,20 @@ public class SetupModuleAdmin implements ApplicationListener<ContextRefreshedEve
metaDataService.getColumn(tokenTable, "name", "name", "TEXT", null, 6, Boolean.TRUE, "Name", Boolean.FALSE, null);
metaDataService.getColumn(tokenTable, "last_used_date", "used", "TIMESTAMP", null, 7, Boolean.FALSE, "", Boolean.FALSE, null);
metaDataService.getColumn(tokenTable, "enabled", "enabled", "BOOLEAN", null, 8, Boolean.TRUE, "", Boolean.TRUE, "Enabled");
metaDataService.getColumn(tokenTable, "user_id", "user_id", "TEXT", null, 9, Boolean.TRUE, "User", Boolean.FALSE, null, "user_name");
MetaDataTable roleTable = metaDataService.getTable("role");
metaDataService.getColumn(roleTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.TRUE, "", Boolean.FALSE, null);
metaDataService.getColumn(roleTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null);
metaDataService.getColumn(roleTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null);
metaDataService.getColumn(roleTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null);
metaDataService.getColumn(roleTable, "name", "name", "TEXT", null, 5, Boolean.TRUE, "", Boolean.FALSE, null);
MetaDataTable authorizationMatrix = metaDataService.getTable("authorization_matrix");
metaDataService.getColumn(authorizationMatrix, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.TRUE, "", Boolean.FALSE, null);
metaDataService.getColumn(authorizationMatrix, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null);
metaDataService.getColumn(authorizationMatrix, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null);
metaDataService.getColumn(authorizationMatrix, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null);
metaDataService.getColumn(authorizationMatrix, "user_id", "user_id", "TEXT", null, 5, Boolean.TRUE, "User", Boolean.FALSE, null, "user_name");
metaDataService.getColumn(authorizationMatrix, "role_id", "role_id", "TEXT", null, 6, Boolean.TRUE, "Role", Boolean.FALSE, null, "name");
metaDataService.getColumn(tokenTable, "profile_id", "profile_id", "TEXT", null, 9, Boolean.TRUE, "Profile", Boolean.FALSE, null, "user_name");
MetaDataTable permissionTable = metaDataService.getTable("permission");
metaDataService.getColumn(permissionTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.TRUE, "", Boolean.FALSE, null);
metaDataService.getColumn(permissionTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null);
metaDataService.getColumn(permissionTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null);
metaDataService.getColumn(permissionTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null);
metaDataService.getColumn(permissionTable, "name", "name", "TEXT", null, 5, Boolean.TRUE, "", Boolean.FALSE, null);
MetaDataTable AssignmentTable = metaDataService.getTable("assignment");
metaDataService.getColumn(AssignmentTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.TRUE, "", Boolean.FALSE, null);
metaDataService.getColumn(AssignmentTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null);
metaDataService.getColumn(AssignmentTable, "last_modified_date", "modified", "TIMESTAMP", null, 3, Boolean.FALSE, "", Boolean.FALSE, null);
metaDataService.getColumn(AssignmentTable, "version", "version", "LONG", null, 4, Boolean.FALSE, "", Boolean.FALSE, null);
metaDataService.getColumn(AssignmentTable, "profile_id", "profile_id", "TEXT", null, 5, Boolean.TRUE, "Profile", Boolean.FALSE, null, "user_name");
metaDataService.getColumn(AssignmentTable, "permission_id", "permission_id", "TEXT", null, 6, Boolean.TRUE, "Permission", Boolean.FALSE, null, "name");
MetaDataTable moduleDataTable = metaDataService.getTable("module_data");
metaDataService.getColumn(moduleDataTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1, Boolean.TRUE, "", Boolean.FALSE, null);
metaDataService.getColumn(moduleDataTable, "created_date", "created", "TIMESTAMP", null, 2, Boolean.FALSE, "", Boolean.FALSE, null);
@@ -0,0 +1,34 @@
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;
@Getter
@Setter
@Entity
public class Assignment extends AbstractEntity {
@ManyToOne
@JoinColumn(name = "profile_id")
@NotNull
private Profile profile;
@ManyToOne
@JoinColumn(name = "permission_id")
@NotNull
private Permission permission;
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("Assignment{");
sb.append("profile=").append(profile.getUserName());
sb.append(", permission=").append(permission.getName());
sb.append('}');
return sb.toString();
}
}
@@ -0,0 +1,29 @@
package de.thpeetz.kontor.admin.data;
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;
import java.util.List;
@Slf4j
@Getter
@Setter
@ToString
@Entity
public class Permission extends AbstractEntity {
@NotEmpty
private String name;
@OneToMany(fetch = FetchType.EAGER, mappedBy = "permission")
@Nullable
private List<Assignment> assignments;
}
@@ -0,0 +1,57 @@
package de.thpeetz.kontor.admin.data;
import de.thpeetz.kontor.common.data.AbstractEntity;
import jakarta.persistence.*;
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 Profile extends AbstractEntity {
private String firstName;
private String lastName;
@NotEmpty
private String userName;
private String email;
private String password;
private boolean enabled;
@OneToMany(fetch = FetchType.EAGER, mappedBy = "profile")
@Nullable
private List<Assignment> assignments = new LinkedList<>();
@OneToMany(fetch = FetchType.EAGER, mappedBy = "profile")
@Nullable
private List<Token> tokens = new LinkedList<>();
public String getFullName() {
StringBuilder fullNameBuilder = new StringBuilder();
if (firstName != null) {
fullNameBuilder.append(firstName);
}
if (lastName != null) {
if (!fullNameBuilder.isEmpty()) {
fullNameBuilder.append(" ");
}
fullNameBuilder.append(lastName);
}
return fullNameBuilder.toString();
}
}
@@ -31,7 +31,7 @@ public class Token extends AbstractEntity {
private boolean enabled;
@ManyToOne
@JoinColumn(name="user_id")
@JoinColumn(name="profile_id")
@NotNull
private User user;
private Profile profile;
}
@@ -0,0 +1,13 @@
package de.thpeetz.kontor.admin.repository;
import de.thpeetz.kontor.admin.data.*;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface AssignmentRepository extends JpaRepository<Assignment, String> {
List<Assignment> findByProfile(Profile profile);
List<Assignment> findByPermission(Permission permission);
}
@@ -0,0 +1,19 @@
package de.thpeetz.kontor.admin.repository;
import de.thpeetz.kontor.admin.data.Permission;
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 PermissionRepository extends JpaRepository<Permission, String> {
@Query("select p from Permission p " +
"where lower(p.name) like lower(concat('%', :searchTerm, '%')) ")
List<Permission> search(@Param("searchTerm") String searchTerm);
@Query("select p from Permission p " +
"where lower(p.name) like lower(:name) ")
Permission findByName(@Param("name") String name);
}
@@ -0,0 +1,18 @@
package de.thpeetz.kontor.admin.repository;
import de.thpeetz.kontor.admin.data.Profile;
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;
import java.util.UUID;
public interface ProfileRepository extends JpaRepository<Profile, String> {
@Query("select p from Profile p " +
"where lower(p.lastName) like lower(concat('%', :searchTerm, '%')) ")
List<Profile> search(@Param("searchTerm") String searchTerm);
Profile findByUserName(String userName);
}
@@ -3,108 +3,105 @@ package de.thpeetz.kontor.admin.services;
import java.util.Collection;
import java.util.List;
import de.thpeetz.kontor.admin.data.*;
import de.thpeetz.kontor.admin.repository.*;
import org.springframework.stereotype.Service;
import de.thpeetz.kontor.admin.data.AuthorizationMatrix;
import de.thpeetz.kontor.admin.repository.AuthorizationMatrixRepository;
import de.thpeetz.kontor.admin.data.Role;
import de.thpeetz.kontor.admin.repository.RoleRepository;
import de.thpeetz.kontor.admin.data.User;
import de.thpeetz.kontor.admin.repository.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;
private final ProfileRepository profileRepository;
private final PermissionRepository permissionRepository;
private final AssignmentRepository assignmentRepository;
public AdminService(ProfileRepository profileRepository,
PermissionRepository permissionRepository,
AssignmentRepository assignmentRepository) {
this.profileRepository = profileRepository;
this.permissionRepository = permissionRepository;
this.assignmentRepository = assignmentRepository;
}
public List<User> findAllUsers() {
return userRepository.findAll();
public List<Profile> findAllProfiles() { return profileRepository.findAll(); }
public Profile getProfile(String userName) {
log.debug("get Profile {}", userName);
return profileRepository.findByUserName(userName);
}
public List<Role> findAllRoles() {
return roleRepository.findAll();
}
public List<Permission> findAllPermissions() { return permissionRepository.findAll(); }
public Collection<Role> findAllRoles(String stringFilter) {
public Collection<Permission> findAllPermissions(String stringFilter) {
if (stringFilter == null || stringFilter.isEmpty()) {
return roleRepository.findAll();
return permissionRepository.findAll();
} else {
return roleRepository.search(stringFilter);
return permissionRepository.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);
public Permission addPermission(String permissionName) {
Permission permission = permissionRepository.findByName(permissionName);
if (permission == null) {
log.info("Permission {} was not found, will create it.", permissionName);
permission = new Permission();
permission.setName(permissionName);
permissionRepository.save(permission);
}
return role;
return permission;
}
public void saveRole(Role role) {
if (role == null) {
log.warn("Role is null. Can't save it.");
public void savePermission(Permission permission) {
if (permission == null) {
log.warn("Permission is null. Can't save it.");
}
log.info("saveRole: role={}", role);
roleRepository.save(role);
log.info("savePermission: permission={}", permission);
permissionRepository.save(permission);
}
public void deleteRole(Role role) {
if (role == null) {
log.warn("Role is null. Can't delete it.");
public void deletePermission(Permission permission) {
if (permission == null) {
log.warn("Permission is null. Can't delete it.");
return;
}
log.info("deleteRole: role={}", role);
roleRepository.delete(role);
log.info("deletePermission: permission={}", permission);
permissionRepository.delete(permission);
}
public List<AuthorizationMatrix> findAllAuthorizationMatrices() {
return authorizationMatrixRepository.findAll();
public List<Assignment> findAllAssignments() {
return assignmentRepository.findAll();
}
public void saveAuthorizationMatrix(AuthorizationMatrix authorizationMatrix) {
if (authorizationMatrix == null) {
log.warn("AuthorizationMatrix is null. Can't save it.");
public void saveAssignment(Assignment assignment) {
if (assignment == null) {
log.warn("Assignment is null. Can't save it.");
return;
}
log.info("saveAuthorizationMatrix: authorizationMatrix={}", authorizationMatrix);
authorizationMatrixRepository.save(authorizationMatrix);
log.info("saveAssignment: assignment={}", assignment);
assignmentRepository.save(assignment);
}
public void deleteAuthorizationMatrix(AuthorizationMatrix authorizationMatrix) {
if (authorizationMatrix == null) {
log.warn("AuthorizationMatrix is null. Can't delete it.");
public void deleteAssignment(Assignment assignment) {
if (assignment == null) {
log.warn("Assignment is null. Can't delete it.");
return;
}
log.info("deleteAuthorizationMatrix: authorizationMatrix={}", authorizationMatrix);
authorizationMatrixRepository.delete(authorizationMatrix);
log.info("deleteAssignment: assignment={}", assignment);
assignmentRepository.delete(assignment);
}
public String getUserFullName(String userName) {
log.debug("get Fullname für user {}", userName);
User user = userRepository.findByUserName(userName);
if (user == null) {
public String getProfileFullName(String userName) {
log.debug("get Fullname für Profile {}", userName);
Profile profile = profileRepository.findByUserName(userName);
if (profile == null) {
log.info("keinen Eintrag für {} gefunden", userName);
return userName;
} else {
log.info("Voller Name des User {}: {}", userName, user.getFullName());
return user.getFullName();
log.info("Voller Name des Profile {}: {}", userName, profile.getFullName());
return profile.getFullName();
}
}
public User getUser(String userName) {
log.debug("get User {}", userName);
return userRepository.findByUserName(userName);
}
}
@@ -1,9 +1,7 @@
package de.thpeetz.kontor.admin.services;
import de.thpeetz.kontor.admin.data.*;
import de.thpeetz.kontor.admin.repository.AuthorizationMatrixRepository;
import de.thpeetz.kontor.admin.repository.RoleRepository;
import de.thpeetz.kontor.admin.repository.UserRepository;
import de.thpeetz.kontor.admin.repository.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
@@ -26,83 +24,86 @@ public class KontorUserDetailsService implements UserDetailsService {
private static SecureRandom random = new SecureRandom();
@Autowired
private UserRepository userRepository;
private ProfileRepository profileRepository;
@Autowired
private RoleRepository roleRepository;
private PermissionRepository permissionRepository;
@Autowired
private AuthorizationMatrixRepository authorizationMatrixRepository;
private AssignmentRepository assignmentRepository;
public Collection<User> findAllUsers(String stringFilter) {
public Collection<Profile> findAllProfiles(String stringFilter) {
if (stringFilter == null || stringFilter.isEmpty()) {
return userRepository.findAll();
return profileRepository.findAll();
} else {
return userRepository.search(stringFilter);
return profileRepository.search(stringFilter);
}
}
public void saveUser(User user) {
if (user == null) {
log.warn("User is null. Can't save it.");
public void saveProfile(Profile profile) {
if (profile == null) {
log.warn("Profile is null. Can't save it.");
return;
}
log.info("saveUser: user={}", user);
userRepository.save(user);
log.info("saveProfile: profile={}", profile);
profileRepository.save(profile);
}
public void saveUser(User user, List<Role> roles) {
if (user == null) {
log.warn("User is null. Can't save it.");
public void saveProfile(Profile profile, List<Permission> permissions) {
if (profile == null) {
log.warn("Profile is null. Can't save it.");
return;
}
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());
log.info("First save Profile: {}", profile);
profile = profileRepository.save(profile);
List<Permission> copy = permissions.stream().collect(Collectors.toList());
List<Assignment> assignments = profile.getAssignments();
assignments.forEach(assignment -> {
if (permissions.contains(assignment.getPermission())) {
log.info("Permission {} already assigned", assignment.getPermission());
copy.remove(assignment.getPermission());
} else {
log.info("Role {} has to be removed", matrix.getRole());
authorizationMatrixRepository.delete(matrix);
log.info("Permission {} has to be removed", assignment.getPermission());
assignmentRepository.delete(assignment);
}
});
log.info("remaining roles: {}", copy);
for (Role role : copy) {
AuthorizationMatrix matrix = new AuthorizationMatrix();
matrix.setUser(user);
matrix.setRole(role);
authorizationMatrixRepository.save(matrix);
for (Permission permission : copy) {
Assignment assignment = new Assignment();
assignment.setProfile(profile);
assignment.setPermission(permission);
assignmentRepository.save(assignment);
}
}
public void deleteUser(User user) {
if (user == null) {
log.warn("User is null. Can't delete it.");
public void deleteProfile(Profile profile) {
if (profile == null) {
log.warn("Profile is null. Can't delete it.");
return;
}
log.info("deleteUser: user={}", user);
userRepository.delete(user);
log.info("deleteProfile: profile={}", profile);
profileRepository.delete(profile);
}
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
log.info("loadUserByUsername: userName={}", userName);
User user = userRepository.findByUserName(userName);
if (user == null) {
Profile profile = profileRepository.findByUserName(userName);
if (profile == 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(),
Collection<? extends GrantedAuthority> authorities = getAuthorities(profile);
log.info("Profile {} hat Permissions: {}", userName, authorities);
return new org.springframework.security.core.userdetails.User(profile.getUserName(), profile.getPassword(),
authorities);
}
private Collection<? extends GrantedAuthority> getAuthorities(User user) {
return authorizationMatrixRepository.findByUser(user).stream()
.map(matrix -> matrix.getRole().getName())
private Collection<? extends GrantedAuthority> getAuthorities(Profile profile) {
return assignmentRepository.findByProfile(profile).stream()
.map(assignment -> assignment.getPermission().getName())
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
@@ -122,7 +123,7 @@ public class KontorUserDetailsService implements UserDetailsService {
log.info("removeRememberedUser: id={}", id);
}
public List<Role> findAllRoles() {
return roleRepository.findAll();
public List<Permission> findAllPermissions() {
return permissionRepository.findAll();
}
}
@@ -29,7 +29,7 @@ public class AdminLayout extends AppLayout {
private HorizontalLayout getSecondaryNavigation() {
HorizontalLayout navigation = new HorizontalLayout();
navigation.addClassNames(LumoUtility.JustifyContent.CENTER, LumoUtility.Gap.SMALL, LumoUtility.Height.MEDIUM);
navigation.add(AdminConstants.getUserNavigation(), AdminConstants.getRoleNavigation(),
navigation.add(AdminConstants.getProfileNavigation(), AdminConstants.getPermissionNavigation(),
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.Assignment;
import de.thpeetz.kontor.admin.data.Permission;
import de.thpeetz.kontor.admin.data.Profile;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class AssignmentForm extends FormLayout {
ComboBox<Profile> profile = new ComboBox<>("Proile");
ComboBox<Permission> permission = new ComboBox<>("Permission");
Button save = new Button("Save");
Button delete = new Button("Delete");
Button close = new Button("Cancel");
Binder<Assignment> binder = new BeanValidationBinder<>(Assignment.class);
public AssignmentForm(List<Profile> profiles, List<Permission> permissions) {
addClassName("assignment-form");
binder.bindInstanceFields(this);
profile.setItems(profiles);
profile.setItemLabelGenerator(Profile::getUserName);
permission.setItems(permissions);
permission.setItemLabelGenerator(Permission::getName);
add(profile, permission, 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 setAssignment(Assignment assignment) {
binder.setBean(assignment);
}
public abstract static class AssignmentFormEvent extends ComponentEvent<AssignmentForm> {
private Assignment assignment;
protected AssignmentFormEvent(AssignmentForm source, Assignment assignment) {
super(source, false);
this.assignment = assignment;
}
public Assignment getAssignment() {
return assignment;
}
}
public static class SaveEvent extends AssignmentFormEvent {
SaveEvent(AssignmentForm source, Assignment assignment) {
super(source, assignment);
}
}
public static class DeleteEvent extends AssignmentFormEvent {
DeleteEvent(AssignmentForm source, Assignment assignment) {
super(source, assignment);
}
}
public static class CloseEvent extends AssignmentFormEvent {
CloseEvent(AssignmentForm 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 de.thpeetz.kontor.admin.data.Assignment;
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.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.ASSIGNMENT_ROUTE, layout = MainLayout.class)
@PageTitle("Authorization | Admin | Kontor")
public class AssignmentView extends VerticalLayout {
Grid<Assignment> grid = new Grid<>(Assignment.class);
AssignmentForm form;
AdminService service;
public AssignmentView(AdminService service) {
this.service = service;
addClassName("assignment-view");
setSizeFull();
configureGrid();
configureForm();
add(getToolbar(), getContent());
updateList();
}
private void configureGrid() {
grid.addClassName("assignment-grid");
grid.setSizeFull();
grid.setColumns("profile.userName", "permission.name");
grid.getColumns().forEach(col -> col.setAutoWidth(true));
grid.asSingleSelect().addValueChangeListener(event -> editAssignment(event.getValue()));
}
private void configureForm() {
form = new AssignmentForm(service.findAllProfiles(), service.findAllPermissions());
form.setWidth("25em");
form.addSaveListener(this::saveAssignment);
form.addDeleteListener(this::deleteAssignment);
form.addCloseListener(e -> closeEditor());
}
private void saveAssignment(AssignmentForm.SaveEvent event) {
Assignment assignment = event.getAssignment();
service.saveAssignment(assignment);
updateList();
closeEditor();
}
private void deleteAssignment(AssignmentForm.DeleteEvent event) {
service.deleteAssignment(event.getAssignment());
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 permission", click -> addAssignment());
HorizontalLayout toolbar = new HorizontalLayout(addAuthorizationMaxtrixButton);
toolbar.addClassName("toolbar");
return toolbar;
}
public void editAssignment(Assignment assignment) {
if (assignment == null) {
closeEditor();
} else {
form.setAssignment(assignment);
form.setVisible(true);
addClassName("editing");
}
}
public void closeEditor() {
form.setAssignment(null);
form.setVisible(false);
removeClassName("editing");
}
private void addAssignment() {
grid.asSingleSelect().clear();
editAssignment(new Assignment());
}
private void updateList() {
grid.setItems(service.findAllAssignments());
}
}
@@ -0,0 +1,101 @@
package de.thpeetz.kontor.admin.views;
import com.vaadin.flow.component.ComponentEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.Binder;
import de.thpeetz.kontor.admin.data.Permission;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class PermissionForm extends FormLayout {
TextField name = new TextField("Role name");
Button save = new Button("Save");
Button delete = new Button("Delete");
Button close = new Button("Cancel");
Binder<Permission> binder = new BeanValidationBinder<>(Permission.class);
public PermissionForm() {
addClassName("permission-form");
binder.bindInstanceFields(this);
add(name, createButtonsLayout());
}
private HorizontalLayout createButtonsLayout() {
save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
delete.addThemeVariants(ButtonVariant.LUMO_ERROR);
close.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
save.addClickShortcut(Key.ENTER);
close.addClickShortcut(Key.ESCAPE);
save.addClickListener(event -> validateAndSave());
delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean())));
close.addClickListener(event -> fireEvent(new CloseEvent(this)));
binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid()));
return new HorizontalLayout(save, delete, close);
}
private void validateAndSave() {
if (binder.isValid()) {
fireEvent(new SaveEvent(this, binder.getBean()));
}
}
public void setPermission(Permission permission) {
binder.setBean(permission);
}
public abstract static class PermissionFormEvent extends ComponentEvent<PermissionForm> {
private Permission permission;
protected PermissionFormEvent(PermissionForm source, Permission permission) {
super(source, false);
this.permission = permission;
}
public Permission getPermission() {
return permission;
}
}
public static class SaveEvent extends PermissionFormEvent {
SaveEvent(PermissionForm source, Permission permission) {
super(source, permission);
}
}
public static class DeleteEvent extends PermissionFormEvent {
DeleteEvent(PermissionForm source, Permission permission) {
super(source, permission);
}
}
public static class CloseEvent extends PermissionFormEvent {
CloseEvent(PermissionForm 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,119 @@
package de.thpeetz.kontor.admin.views;
import de.thpeetz.kontor.admin.data.Permission;
import org.springframework.context.annotation.Scope;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import de.thpeetz.kontor.admin.AdminConstants;
import de.thpeetz.kontor.admin.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.PERMISSION_ROUTE, layout = MainLayout.class)
@PageTitle("Permissions | Admin | Kontor")
public class PermissionView extends VerticalLayout {
Grid<Permission> grid = new Grid<>(Permission.class);
TextField filterText = new TextField();
PermissionForm form;
AdminService service;
public PermissionView(AdminService service) {
this.service = service;
addClassName("permission-view");
setSizeFull();
configureGrid();
configureForm();
add(getToolbar(), getContent());
updateList();
}
private void configureGrid() {
grid.addClassName("permission-grid");
grid.setSizeFull();
grid.setColumns("name");
grid.getColumns().forEach(col -> col.setAutoWidth(true));
grid.asSingleSelect().addValueChangeListener(event -> editPermission(event.getValue()));
}
private void configureForm() {
form = new PermissionForm();
form.setWidth("25em");
form.addSaveListener(this::savePermission);
form.addDeleteListener(this::deletePermission);
form.addCloseListener(e -> closeEditor());
}
private void savePermission(PermissionForm.SaveEvent event) {
service.savePermission(event.getPermission());
updateList();
closeEditor();
}
private void deletePermission(PermissionForm.DeleteEvent event) {
service.deletePermission(event.getPermission());
updateList();
closeEditor();
}
private Component getContent() {
HorizontalLayout content = new HorizontalLayout(grid, form);
content.setFlexGrow(2, grid);
content.setFlexGrow(1, form);
content.addClassName("content");
content.setSizeFull();
return content;
}
private HorizontalLayout getToolbar() {
filterText.setPlaceholder("Filter by user name...");
filterText.setClearButtonVisible(true);
filterText.setValueChangeMode(ValueChangeMode.LAZY);
filterText.addValueChangeListener(e -> updateList());
Button addUserButton = new Button("Add user", click -> addPermission());
HorizontalLayout toolbar = new HorizontalLayout(filterText, addUserButton);
toolbar.addClassName("toolbar");
return toolbar;
}
public void editPermission(Permission permission) {
if (permission == null) {
closeEditor();
} else {
form.setPermission(permission);
form.setVisible(true);
addClassName("editing");
}
}
public void closeEditor() {
form.setPermission(null);
form.setVisible(false);
removeClassName("editing");
}
private void addPermission() {
grid.asSingleSelect().clear();
editPermission(new Permission());
}
private void updateList() {
grid.setItems(service.findAllPermissions(filterText.getValue()));
}
}
@@ -0,0 +1,143 @@
package de.thpeetz.kontor.admin.views;
import com.vaadin.flow.component.ComponentEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.checkbox.Checkbox;
import com.vaadin.flow.component.checkbox.CheckboxGroup;
import com.vaadin.flow.component.checkbox.CheckboxGroupVariant;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.textfield.EmailField;
import com.vaadin.flow.component.textfield.PasswordField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.Binder;
import de.thpeetz.kontor.admin.data.Permission;
import de.thpeetz.kontor.admin.data.Profile;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
public class ProfileForm extends FormLayout {
TextField userName = new TextField("User name");
PasswordField password = new PasswordField("Password");
EmailField email = new EmailField("Email");
TextField firstName = new TextField("First name");
TextField lastName = new TextField("Last name");
Checkbox enabled = new Checkbox("Enabled");
String originalPassword;
CheckboxGroup<Permission> permissionList = new CheckboxGroup<>("Permissions");
Button save = new Button("Save");
Button delete = new Button("Delete");
Button close = new Button("Cancel");
Binder<Profile> binder = new BeanValidationBinder<>(Profile.class);
public ProfileForm() {
addClassName("profile-form");
binder.bindInstanceFields(this);
add(userName, password, email, firstName, lastName, enabled, configurePermissionsGroup(), createButtonsLayout());
}
private CheckboxGroup<Permission> configurePermissionsGroup() {
permissionList.addThemeVariants(CheckboxGroupVariant.LUMO_VERTICAL);
permissionList.setItemLabelGenerator(Permission::getName);
permissionList.addValueChangeListener(event -> {
log.debug("permissions changed: {}", event);
});
return permissionList;
}
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 ProfileForm.DeleteEvent(this, binder.getBean())));
close.addClickListener(event -> fireEvent(new ProfileForm.CloseEvent(this)));
binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid()));
return new HorizontalLayout(save, delete, close);
}
private void validateAndSave() {
if (binder.isValid()) {
fireEvent(new ProfileForm.SaveEvent(this, binder.getBean()));
}
}
public void setProfile(Profile profile) {
binder.setBean(profile);
//log.debug("UserForm.setUser: {}", user);
if (profile != null) {
this.originalPassword = profile.getPassword();
} else {
this.originalPassword = null;
}
}
public void setPermissions(List<Permission> permissions, Profile profile) {
permissionList.setItems(permissions);
profile.getAssignments().stream().forEach(assignment -> {
permissionList.select(assignment.getPermission());
});
}
public boolean hasPasswordChanged(Profile profile) {
return !originalPassword.equals(profile.getPassword());
}
public abstract static class ProfileFormEvent extends ComponentEvent<ProfileForm> {
private Profile profile;
protected ProfileFormEvent(ProfileForm source, Profile profile) {
super(source, false);
this.profile = profile;
}
public Profile getProfile() {
return profile;
}
}
public static class SaveEvent extends ProfileForm.ProfileFormEvent {
SaveEvent(ProfileForm source, Profile profile) {
super(source, profile);
}
}
public static class DeleteEvent extends ProfileForm.ProfileFormEvent {
DeleteEvent(ProfileForm source, Profile profile) {
super(source, profile);
}
}
public static class CloseEvent extends ProfileForm.ProfileFormEvent {
CloseEvent(ProfileForm source) {
super(source, null);
}
}
public void addDeleteListener(ComponentEventListener<ProfileForm.DeleteEvent> listener) {
addListener(ProfileForm.DeleteEvent.class, listener);
}
public void addSaveListener(ComponentEventListener<ProfileForm.SaveEvent> listener) {
addListener(ProfileForm.SaveEvent.class, listener);
}
public void addCloseListener(ComponentEventListener<ProfileForm.CloseEvent> listener) {
addListener(ProfileForm.CloseEvent.class, listener);
}
}
@@ -0,0 +1,135 @@
package de.thpeetz.kontor.admin.views;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import de.thpeetz.kontor.admin.data.Permission;
import de.thpeetz.kontor.admin.data.Profile;
import de.thpeetz.kontor.admin.services.KontorUserDetailsService;
import de.thpeetz.kontor.common.views.MainLayout;
import jakarta.annotation.security.RolesAllowed;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@SpringComponent
@Scope("prototype")
@RolesAllowed("ROLE_ADMIN")
@Route(value = "admin/profile", layout = MainLayout.class)
@PageTitle("Profile | Admin | Kontor")
public class ProfileView extends VerticalLayout {
Grid<Profile> grid = new Grid<>(Profile.class);
TextField filterText = new TextField();
ProfileForm form;
KontorUserDetailsService service;
@Autowired
PasswordEncoder passwordEncoder;
public ProfileView(KontorUserDetailsService service) {
this.service = service;
addClassName("profile-view");
setSizeFull();
configureGrid();
configureForm();
add(getToolbar(), getContent());
updateList();
}
private void configureGrid() {
grid.addClassName("profile-grid");
grid.setSizeFull();
grid.setColumns("userName", "email", "firstName", "lastName", "enabled");
grid.getColumns().forEach(col -> col.setAutoWidth(true));
grid.asSingleSelect().addValueChangeListener(event -> editProfile(event.getValue()));
}
private void configureForm() {
form = new ProfileForm();
form.setWidth("25em");
form.setVisible(false);
form.addSaveListener(this::saveProfile);
form.addDeleteListener(this::deleteProfile);
form.addCloseListener(e -> closeEditor());
}
private void saveProfile(ProfileForm.SaveEvent event) {
Profile profile = event.getProfile();
log.debug("ProfileView.saveProfile: {}", profile);
List<Permission> permissions = form.permissionList.getSelectedItems().stream().collect(Collectors.toList());
log.info("selected permissions: {}", permissions);
if (form.hasPasswordChanged(profile)) {
profile.setPassword(passwordEncoder.encode(profile.getPassword()));
log.debug("password changed for profile {}", profile);
}
service.saveProfile(profile, permissions);
updateList();
closeEditor();
}
private void deleteProfile(ProfileForm.DeleteEvent event) {
service.deleteProfile(event.getProfile());
updateList();
closeEditor();
}
private Component getContent() {
HorizontalLayout content = new HorizontalLayout(grid, form);
content.setFlexGrow(2, grid);
content.setFlexGrow(1, form);
content.addClassName("content");
content.setSizeFull();
return content;
}
private HorizontalLayout getToolbar() {
filterText.setPlaceholder("Filter by user name...");
filterText.setClearButtonVisible(true);
filterText.setValueChangeMode(ValueChangeMode.LAZY);
filterText.addValueChangeListener(e -> updateList());
Button addProfileButton = new Button("Add profile", click -> addProfile());
HorizontalLayout toolbar = new HorizontalLayout(filterText, addProfileButton);
toolbar.addClassName("toolbar");
return toolbar;
}
public void editProfile(Profile profile) {
if (profile == null) {
closeEditor();
} else {
form.setProfile(profile);
form.setPermissions(service.findAllPermissions(), profile);
form.setVisible(true);
addClassName("editing");
}
}
public void closeEditor() {
form.setProfile(null);
form.setVisible(false);
removeClassName("editing");
}
private void addProfile() {
grid.asSingleSelect().clear();
editProfile(new Profile());
}
private void updateList() {
grid.setItems(service.findAllProfiles(filterText.getValue()));
}
}
@@ -10,7 +10,7 @@ import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import de.thpeetz.kontor.admin.data.User;
import de.thpeetz.kontor.admin.data.Profile;
import de.thpeetz.kontor.admin.services.AdminService;
import de.thpeetz.kontor.common.views.MainLayout;
import de.thpeetz.kontor.security.SecurityService;
@@ -32,7 +32,7 @@ public class UserProfileView extends VerticalLayout {
Button save = new Button("Save");
Button close = new Button("Cancel");
Binder<User> binder = new BeanValidationBinder<>(User.class);
Binder<Profile> binder = new BeanValidationBinder<>(Profile.class);
public UserProfileView(AdminService adminService, SecurityService securityService) {
this.adminService = adminService;
@@ -42,7 +42,7 @@ public class UserProfileView extends VerticalLayout {
add(firstName, lastName, createButtonsLayout());
securityService.getAuthenticatedUser().ifPresent(user -> {
log.info("UserProfileView: {}", user.getUsername());
binder.setBean(adminService.getUser(user.getUsername()));
binder.setBean(adminService.getProfile(user.getUsername()));
});
}
@@ -32,7 +32,7 @@ public class AvatarMenuBar extends Div {
log.info("AdminService: {}", adminService);
if (user != null) {
String userName = user.getUsername();
String fullName = adminService.getUserFullName(userName);
String fullName = adminService.getProfileFullName(userName);
avatar.setName(fullName);
}
});
@@ -23,7 +23,7 @@ public class SetupModuleMedia implements ApplicationListener<ContextRefreshedEve
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
adminService.addRole(MediaConstants.MEDIA_ROLE);
adminService.addPermission(MediaConstants.MEDIA_ROLE);
if (alreadySetup) {
log.info("SetupModuleMedia already executed, skipping");
return;
+5
View File
@@ -0,0 +1,5 @@
*
!build/*-runner
!build/*-runner.jar
!build/lib/*
!build/quarkus-app/*
+36
View File
@@ -0,0 +1,36 @@
# Gradle
.gradle/
build/
# Eclipse
.project
.classpath
.settings/
bin/
# IntelliJ
.idea
*.ipr
*.iml
*.iws
# NetBeans
nb-configuration.xml
# Visual Studio Code
.vscode
.factorypath
# OSX
.DS_Store
# Vim
*.swp
*.swo
# patch
*.orig
*.rej
# Local environment
.env
+78
View File
@@ -0,0 +1,78 @@
# kontor Project
This project uses Quarkus, the Supersonic Subatomic Java Framework.
If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ .
## Running the application in dev mode
You can run your application in dev mode that enables live coding using:
```shell script
./gradlew quarkusDev
```
> **_NOTE:_** Quarkus now ships with a Dev UI, which is available in dev mode only at http://localhost:8080/q/dev/.
## Packaging and running the application
The application can be packaged using:
```shell script
./gradlew build
```
It produces the `quarkus-run.jar` file in the `build/quarkus-app/` directory.
Be aware that its not an _über-jar_ as the dependencies are copied into the `build/quarkus-app/lib/` directory.
The application is now runnable using `java -jar build/quarkus-app/quarkus-run.jar`.
If you want to build an _über-jar_, execute the following command:
```shell script
./gradlew build -Dquarkus.package.type=uber-jar
```
The application, packaged as an _über-jar_, is now runnable using `java -jar build/*-runner.jar`.
## Creating a native executable
You can create a native executable using:
```shell script
./gradlew build -Dquarkus.package.type=native
```
Or, if you don't have GraalVM installed, you can run the native executable build in a container using:
```shell script
./gradlew build -Dquarkus.package.type=native -Dquarkus.native.container-build=true
```
You can then execute your native executable with: `./build/kontor-1.0.0-SNAPSHOT-runner`
If you want to learn more about building native executables, please consult https://quarkus.io/guides/gradle-tooling.
## Related Guides
- JDBC Driver - H2 ([guide](https://quarkus.io/guides/datasource)): Connect to the H2 database via JDBC
- Hibernate ORM with Panache and Kotlin ([guide](https://quarkus.io/guides/hibernate-orm-panache-kotlin)): Define your persistent model in Hibernate ORM with Panache
- SmallRye OpenAPI ([guide](https://quarkus.io/guides/openapi-swaggerui)): Document your REST APIs with OpenAPI - comes with Swagger UI
- YAML Configuration ([guide](https://quarkus.io/guides/config#yaml)): Use YAML to configure your Quarkus application
- SmallRye Health ([guide](https://quarkus.io/guides/microprofile-health)): Monitor service health
## Provided Code
### YAML Config
Configure your application with YAML
[Related guide section...](https://quarkus.io/guides/config-reference#configuration-examples)
The Quarkus application configuration is located in `src/main/resources/application.yml`.
### RESTEasy Reactive
Easily start your Reactive RESTful Web Services
[Related guide section...](https://quarkus.io/guides/getting-started-reactive#reactive-jax-rs-resources)
### SmallRye Health
Monitor your application's health using SmallRye Health
[Related guide section...](https://quarkus.io/guides/smallrye-health)
+45
View File
@@ -0,0 +1,45 @@
plugins {
java
id("io.quarkus")
}
repositories {
mavenCentral()
mavenLocal()
}
val quarkusPlatformGroupId: String by project
val quarkusPlatformArtifactId: String by project
val quarkusPlatformVersion: String by project
dependencies {
implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}"))
implementation("io.quarkus:quarkus-resteasy-reactive-jackson")
implementation("io.quarkus:quarkus-jdbc-h2")
implementation("io.quarkus:quarkus-hibernate-orm-panache-kotlin")
implementation("io.quarkus:quarkus-smallrye-openapi")
implementation("io.quarkus:quarkus-config-yaml")
implementation("io.quarkus:quarkus-hibernate-reactive-panache-kotlin")
implementation("io.quarkus:quarkus-smallrye-health")
implementation("io.quarkus:quarkus-arc")
implementation("io.quarkus:quarkus-resteasy-reactive")
testImplementation("io.quarkus:quarkus-junit5")
testImplementation("io.rest-assured:rest-assured")
}
group = "de.thpeetz.kontor"
version = "1.0.0-SNAPSHOT"
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
tasks.withType<Test> {
systemProperty("java.util.logging.manager", "org.jboss.logmanager.LogManager")
}
tasks.withType<JavaCompile> {
options.encoding = "UTF-8"
options.compilerArgs.add("-parameters")
}
+6
View File
@@ -0,0 +1,6 @@
#Gradle properties
quarkusPluginId=io.quarkus
quarkusPluginVersion=2.15.2.Final
quarkusPlatformGroupId=io.quarkus.platform
quarkusPlatformArtifactId=quarkus-bom
quarkusPlatformVersion=2.15.2.Final
Binary file not shown.
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+185
View File
@@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or 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 UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# 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"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# 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
;;
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"
which java >/dev/null 2>&1 || 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
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"
+104
View File
@@ -0,0 +1,104 @@
@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=.
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%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
: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 %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="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!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+13
View File
@@ -0,0 +1,13 @@
pluginManagement {
val quarkusPluginVersion: String by settings
val quarkusPluginId: String by settings
repositories {
mavenCentral()
gradlePluginPortal()
mavenLocal()
}
plugins {
id(quarkusPluginId) version quarkusPluginVersion
}
}
rootProject.name="kontor"
@@ -0,0 +1,94 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
#
# Before building the container image run:
#
# ./gradlew build
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/kontor-jvm .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/kontor-jvm
#
# If you want to include the debug port into your docker image
# you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005
#
# Then run the container using :
#
# docker run -i --rm -p 8080:8080 quarkus/kontor-jvm
#
# This image uses the `run-java.sh` script to run the application.
# This scripts computes the command line to execute your Java application, and
# includes memory/GC tuning.
# You can configure the behavior using the following environment properties:
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
# in JAVA_OPTS (example: "-Dsome.property=foo")
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
# used to calculate a default maximal heap memory based on a containers restriction.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
# of the container available memory as set here. The default is `50` which means 50%
# of the available memory is used as an upper boundary. You can skip this mechanism by
# setting this value to `0` in which case no `-Xmx` option is added.
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
# is used to calculate a default initial heap memory based on the maximum heap memory.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
# is used as the initial heap size. You can skip this mechanism by setting this value
# to `0` in which case no `-Xms` option is added (example: "25")
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
# This is used to calculate the maximum value of the initial heap memory. If used in
# a container without any memory constraints for the container then this option has
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
# here. The default is 4096MB which means the calculated value of `-Xms` never will
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
# when things are happening. This option, if set to true, will set
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
# true").
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
# (example: "20")
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
# (example: "40")
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
# (example: "4")
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
# previous GC times. (example: "90")
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
# contain the necessary JRE command-line options to specify the required GC, which
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
FROM registry.access.redhat.com/ubi8/openjdk-11:1.14
ENV LANGUAGE='en_US:en'
# We make four distinct layers so if there are application changes the library layers can be re-used
COPY --chown=185 build/quarkus-app/lib/ /deployments/lib/
COPY --chown=185 build/quarkus-app/*.jar /deployments/
COPY --chown=185 build/quarkus-app/app/ /deployments/app/
COPY --chown=185 build/quarkus-app/quarkus/ /deployments/quarkus/
EXPOSE 8080
USER 185
ENV AB_JOLOKIA_OFF=""
ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
@@ -0,0 +1,90 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
#
# Before building the container image run:
#
# ./gradlew build -Dquarkus.package.type=legacy-jar
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/kontor-legacy-jar .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/kontor-legacy-jar
#
# If you want to include the debug port into your docker image
# you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005
#
# Then run the container using :
#
# docker run -i --rm -p 8080:8080 quarkus/kontor-legacy-jar
#
# This image uses the `run-java.sh` script to run the application.
# This scripts computes the command line to execute your Java application, and
# includes memory/GC tuning.
# You can configure the behavior using the following environment properties:
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
# in JAVA_OPTS (example: "-Dsome.property=foo")
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
# used to calculate a default maximal heap memory based on a containers restriction.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
# of the container available memory as set here. The default is `50` which means 50%
# of the available memory is used as an upper boundary. You can skip this mechanism by
# setting this value to `0` in which case no `-Xmx` option is added.
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
# is used to calculate a default initial heap memory based on the maximum heap memory.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
# is used as the initial heap size. You can skip this mechanism by setting this value
# to `0` in which case no `-Xms` option is added (example: "25")
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
# This is used to calculate the maximum value of the initial heap memory. If used in
# a container without any memory constraints for the container then this option has
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
# here. The default is 4096MB which means the calculated value of `-Xms` never will
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
# when things are happening. This option, if set to true, will set
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
# true").
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
# (example: "20")
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
# (example: "40")
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
# (example: "4")
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
# previous GC times. (example: "90")
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
# contain the necessary JRE command-line options to specify the required GC, which
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
FROM registry.access.redhat.com/ubi8/openjdk-11:1.14
ENV LANGUAGE='en_US:en'
COPY build/lib/* /deployments/lib/
COPY build/*-runner.jar /deployments/quarkus-run.jar
EXPOSE 8080
USER 185
ENV AB_JOLOKIA_OFF=""
ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
@@ -0,0 +1,27 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
#
# Before building the container image run:
#
# ./gradlew build -Dquarkus.package.type=native
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.native -t quarkus/kontor .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/kontor
#
###
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
&& chown 1001:root /work
COPY --chown=1001:root build/*-runner /work/application
EXPOSE 8080
USER 1001
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
@@ -0,0 +1,30 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
# It uses a micro base image, tuned for Quarkus native executables.
# It reduces the size of the resulting container image.
# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image.
#
# Before building the container image run:
#
# ./gradlew build -Dquarkus.package.type=native
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/kontor .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/kontor
#
###
FROM quay.io/quarkus/quarkus-micro-image:1.0
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
&& chown 1001:root /work
COPY --chown=1001:root build/*-runner /work/application
EXPOSE 8080
USER 1001
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
greeting:
message: "hello"
@@ -0,0 +1,8 @@
package de.thpeetz.kontor;
import io.quarkus.test.junit.QuarkusIntegrationTest;
@QuarkusIntegrationTest
public class GreetingResourceIT extends GreetingResourceTest {
// Execute the same tests but in packaged mode.
}
+37
View File
@@ -0,0 +1,37 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
+92
View File
@@ -0,0 +1,92 @@
# Kontor Kotlin
## Getting started
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
## Add your files
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
```
cd existing_repo
git remote add origin https://gitlab.thpeetz.de/kontor/kontor-kotlin.git
git branch -M main
git push -uf origin main
```
## Integrate with your tools
- [ ] [Set up project integrations](https://gitlab.thpeetz.de/kontor/kontor-kotlin/-/settings/integrations)
## Collaborate with your team
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
## Test and Deploy
Use the built-in continuous integration in GitLab.
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
***
# Editing this README
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
## License
For open source projects, say how it is licensed.
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
+58
View File
@@ -0,0 +1,58 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id 'org.springframework.boot' version '2.7.7'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id 'org.jetbrains.kotlin.jvm' version '1.7.21'
id 'org.jetbrains.kotlin.plugin.spring' version '1.7.21'
id 'org.jetbrains.kotlin.plugin.jpa' version '1.7.21'
id 'org.jetbrains.kotlin.plugin.allopen' version '1.7.21'
id 'org.jetbrains.kotlin.kapt' version '1.7.21'
}
group = 'de.thpeetz.kontor'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-mustache'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.fasterxml.jackson.module:jackson-module-kotlin'
implementation 'org.jetbrains.kotlin:kotlin-reflect'
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
kapt 'org.springframework.boot:spring-boot-configuration-processor'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'org.springframework.boot:spring-boot-devtools'
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
exclude module: 'mockito-core'
}
testImplementation 'org.junit.jupiter:junit-jupiter-api'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
testImplementation 'com.ninja-squad:springmockk:3.1.1'
}
tasks.withType(KotlinCompile) {
kotlinOptions {
freeCompilerArgs = ['-Xjsr305=strict']
jvmTarget = '11'
}
}
tasks.named('test') {
useJUnitPlatform()
}
allOpen {
annotation("javax.persistence.Entity")
annotation("javax.persistence.Embeddable")
annotation("javax.persistence.MappedSuperclass")
}
Binary file not shown.
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+240
View File
@@ -0,0 +1,240 @@
#!/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/master/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
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# 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"'
# 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
which java >/dev/null 2>&1 || 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
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
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
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
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" "$@"
+91
View File
@@ -0,0 +1,91 @@
@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=.
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.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
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
+1
View File
@@ -0,0 +1 @@
rootProject.name = 'kontor'
@@ -0,0 +1,16 @@
package de.thpeetz.kontor
import org.springframework.boot.Banner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.boot.runApplication
@SpringBootApplication
@EnableConfigurationProperties(KontorProperties::class)
class KontorApplication
fun main(args: Array<String>) {
runApplication<KontorApplication>(*args) {
setBannerMode(Banner.Mode.OFF)
}
}
@@ -0,0 +1,79 @@
package de.thpeetz.kontor
import de.thpeetz.kontor.comics.*
import org.springframework.boot.ApplicationRunner
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class KontorConfiguration {
@Bean
fun databaseInitializer(artistRepository: ArtistRepository,
publisherRepository: PublisherRepository,
comicRepository: ComicRepository,
issueRepository: IssueRepository
) = ApplicationRunner {
artistRepository.save(Artist("Turner, Michael"))
artistRepository.save(Artist("Bendis, Brian Michael"))
artistRepository.save(Artist("Land, Greg"))
artistRepository.save(Artist("Whedon, Joss"))
val marvel = publisherRepository.save(Publisher("Marvel"))
val aspen = publisherRepository.save(Publisher("Aspen"))
publisherRepository.save(Publisher("DC"))
val de = publisherRepository.save(Publisher("Dynamite Entertainment"))
val wildstorm = publisherRepository.save(Publisher("WildStorm"))
val bongo = publisherRepository.save(Publisher("Bongo"))
val image = publisherRepository.save(Publisher("Image"))
val darkHorse = publisherRepository.save(Publisher("Dark Horse Comics"))
val cliffhanger = publisherRepository.save(Publisher("Cliffhanger"))
comicRepository.save(Comic(title = "X-Men", publisher = marvel, currentOrder = false, completed = false))
val redSonja = comicRepository.save(Comic(title = "Red Sonja", publisher = de, currentOrder = false, completed = false))
val x23 = comicRepository.save(Comic(title = "X-23", publisher = marvel, currentOrder = false, completed = false))
comicRepository.save(Comic(title = "Simpsons Comics", publisher = bongo, currentOrder = false, completed = false))
comicRepository.save(Comic(title = "Futurama Comics", publisher = bongo, currentOrder = false, completed = false))
comicRepository.save(Comic(title = "Bomb Queen III: The Good, The Bad and The Lovely", publisher = image, currentOrder = false, completed = false))
comicRepository.save(Comic(title = "Bomb Queen IV: Suicide Bomber", publisher = image, currentOrder = false, completed = false))
comicRepository.save(Comic(title = "Gen13", publisher = wildstorm, currentOrder = false, completed = false))
val bombqueen = comicRepository.save(Comic(title = "Bomb Queen II: Queen of Hearts", publisher = image, currentOrder = false, completed = false))
comicRepository.save(Comic(title = "Iron & The Maiden",publisher = aspen,currentOrder = false,completed = false))
comicRepository.save(Comic(title = "Fathom",publisher = aspen,currentOrder = false,completed = false))
comicRepository.save(Comic(title = "Soulfire",publisher = aspen,currentOrder = false,completed = false))
comicRepository.save(Comic(title = "Star Wars: Rebellion",publisher = darkHorse,currentOrder = false,completed = false))
comicRepository.save(Comic(title = "Star Wars: Rebellion",publisher = darkHorse,currentOrder = false,completed = false))
comicRepository.save(Comic(title = "Star Wars: Knights of the Old Republic",publisher = darkHorse,currentOrder = false,completed = false))
comicRepository.save(Comic(title = "Star Wars: Legacy",publisher = darkHorse,currentOrder = false,completed = false))
comicRepository.save(Comic(title = "Star Wars: Dark Times", publisher = darkHorse, currentOrder = false, completed = false))
comicRepository.save(Comic(title = "Samurai: Heaven and Earth", publisher = darkHorse, currentOrder = false, completed = false))
val battlpope = comicRepository.save(Comic(title = "Battle Pope", publisher = image,currentOrder = false, completed = false))
comicRepository.save(Comic(title = "Danger Girl", publisher = cliffhanger, currentOrder = false, completed = false))
val marville = comicRepository.save(Comic(title = "Marville", publisher = marvel, currentOrder = false, completed = false))
issueRepository.save(Issue(number = "0", comic = redSonja, gelesen = true))
issueRepository.save(Issue(number = "1", comic = redSonja, gelesen = true))
issueRepository.save(Issue(number = "2", comic = redSonja, gelesen = true))
issueRepository.save(Issue(number = "1", comic = x23, gelesen = false))
issueRepository.save(Issue(number = "1", comic = bombqueen, gelesen = false))
issueRepository.save(Issue(number = "2", comic = bombqueen, gelesen = false))
issueRepository.save(Issue(number = "3", comic = bombqueen, gelesen = false))
issueRepository.save(Issue(number = "4", comic = bombqueen, gelesen = false))
issueRepository.save(Issue(number = "1", comic = battlpope, gelesen = false))
issueRepository.save(Issue(number = "2", comic = battlpope, gelesen = false))
issueRepository.save(Issue(number = "3", comic = battlpope, gelesen = false))
issueRepository.save(Issue(number = "4", comic = battlpope, gelesen = false))
issueRepository.save(Issue(number = "5", comic = battlpope, gelesen = false))
issueRepository.save(Issue(number = "6", comic = battlpope, gelesen = false))
issueRepository.save(Issue(number = "7", comic = battlpope, gelesen = false))
issueRepository.save(Issue(number = "8", comic = battlpope, gelesen = false))
issueRepository.save(Issue(number = "9", comic = battlpope, gelesen = false))
issueRepository.save(Issue(number = "10", comic = battlpope, gelesen = false))
issueRepository.save(Issue(number = "11", comic = battlpope, gelesen = false))
issueRepository.save(Issue(number = "12", comic = battlpope, gelesen = false))
issueRepository.save(Issue(number = "1", comic = marville, gelesen = false))
issueRepository.save(Issue(number = "2", comic = marville, gelesen = false))
issueRepository.save(Issue(number = "3", comic = marville, gelesen = false))
issueRepository.save(Issue(number = "4", comic = marville, gelesen = false))
issueRepository.save(Issue(number = "5", comic = marville, gelesen = false))
issueRepository.save(Issue(number = "6", comic = marville, gelesen = false))
issueRepository.save(Issue(number = "7", comic = marville, gelesen = false))
}
}
@@ -0,0 +1,24 @@
package de.thpeetz.kontor
import de.thpeetz.kontor.KontorProperties
import de.thpeetz.kontor.Navigation
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.ui.set
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
@Controller
@RequestMapping("/")
class KontorController(val properties: KontorProperties, val navigation: Navigation) {
@GetMapping("/")
fun blog(model: Model): String {
model["title"] = properties.title
model["title"] = "Kontor"
model["banner"] = properties.banner
model["version"] = properties.version
model["navigation"] = navigation.links()
model["login"] = navigation.login()
return "kontor"
}
}
@@ -0,0 +1,10 @@
package de.thpeetz.kontor
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.ConstructorBinding
@ConstructorBinding
@ConfigurationProperties("kontor")
data class KontorProperties(var title: String, var version: String = "unknown", val banner: Banner) {
data class Banner(val title: String? = null, val content: String)
}
@@ -0,0 +1,24 @@
package de.thpeetz.kontor
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class Navigation {
@Bean
fun links(): Iterable<Link> = listOf(
Link("/comics", "Comics"),
Link("/library","Library"),
Link("/office", "HomeOffice"),
Link("/tradingcards", "Trading Cards"),
Link("/tysc","TradeYourSportsCards"),
Link("/user/login", "<span class=\"glyphicon glyphicon-off\" aria-hidden=\"true\"></span>"),
Link("/admin/","Admin")
)
@Bean
fun login(): Iterable<Link> = listOf(Link("/user/login", "Login"))
}
data class Link(val link: String, val title: String)
@@ -0,0 +1,25 @@
package de.thpeetz.kontor
import org.slf4j.LoggerFactory
import javax.servlet.Filter
import org.springframework.stereotype.Component
import javax.servlet.FilterChain
import javax.servlet.ServletRequest
import javax.servlet.ServletResponse
@Component
class RequestLoggingFilter: Filter {
val loggerFactory = LoggerFactory.getLogger("Kontor Logger")
override fun doFilter(
request: ServletRequest,
response: ServletResponse,
filterChain: FilterChain
) {
val requestString = request.servletContext.contextPath.toString()
//val attributeNames = request.attributeNames
//attributeNames.toList().forEach { loggerFactory.info("Attribute: ${it.toString()}") }
loggerFactory.info("Logging request: $requestString")
filterChain.doFilter(request, response)
}
}
@@ -0,0 +1,25 @@
package de.thpeetz.kontor.comics
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("api/")
class ComicsApiController(val artistRepository: ArtistRepository,
val publisherRepository: PublisherRepository,
val comicRepository: ComicRepository,
val issueRepository: IssueRepository) {
@GetMapping("/artist")
fun artistList() = artistRepository.findAll()
@GetMapping("/publisher")
fun publisherList() = publisherRepository.findAll()
@GetMapping("/comics")
fun comicsList() = comicRepository.findAll()
@GetMapping("/issues")
fun issuesList() = issueRepository.findAll()
}
@@ -0,0 +1,29 @@
package de.thpeetz.kontor.comics
import de.thpeetz.kontor.KontorProperties
import de.thpeetz.kontor.Navigation
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.ui.set
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
@Controller
@RequestMapping("/comics")
class ComicsController(private val properties: KontorProperties,
val navigation: Navigation,
val comicRepository: ComicRepository
) {
@GetMapping("/")
fun blog(model: Model): String {
model["title"] = properties.title
model["title"] = "Comics"
model["banner"] = properties.banner
model["navigation"] = navigation.links()
model["comics"] = comicRepository.findAll().map { it }
return "comics"
}
}
@@ -0,0 +1,36 @@
package de.thpeetz.kontor.comics
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.Id
import javax.persistence.ManyToOne
@Entity
class Artist(
val name: String,
@Id @GeneratedValue val id: Long? = null
)
@Entity
class Publisher(
val name: String,
@Id @GeneratedValue val id: Long? = null
)
@Entity
class Comic(
val title: String,
@ManyToOne val publisher: Publisher,
val currentOrder: Boolean,
val completed: Boolean,
@Id @GeneratedValue val id: Long? = null
)
@Entity
class Issue(
val number: String,
@ManyToOne val comic: Comic,
val gelesen: Boolean,
@Id @GeneratedValue val id: Long? = null
)
@@ -0,0 +1,22 @@
package de.thpeetz.kontor.comics
import org.springframework.data.repository.CrudRepository
interface ArtistRepository: CrudRepository<Artist, String> {
override fun findAll(): Iterable<Artist>
}
interface PublisherRepository: CrudRepository<Publisher, String> {
override fun findAll(): Iterable<Publisher>
}
interface ComicRepository: CrudRepository<Comic, String> {
override fun findAll(): Iterable<Comic>
}
interface IssueRepository: CrudRepository<Issue, String> {
override fun findAll(): Iterable<Issue>
}
@@ -0,0 +1,6 @@
spring.jpa.properties.hibernate.globally_quoted_identifiers=true
spring.jpa.properties.hibernate.globally_quoted_identifiers_skip_column_definitions = true
kontor.title=Kontor
kontor.version=1.0.0-SNAPSHOT
kontor.banner.title=Warning
kontor.banner.content=The blog will be down tomorrow.
@@ -0,0 +1,23 @@
{{> header}}
{{> menu}}
<ul class="nav nav-tabs">
<li role="presentation" class="active"><a href="/comics/comic">Comics</a></li>
<li role="presentation"><a href="/comics/publisher">Publishers</a></li>
<li role="presentation"><a href="/comics/artist">Artists</a></li>
</ul>
<div class="panel panel-default">
<table class="table">
<caption>List of Comics</caption>
<tr><th scope="col">Name</th></tr>
{{#comics}}
<tr><td><a href="/comics/comic/view/{{id}}">{{title}}</a></td></tr>
{{/comics}}
</table>
<div class="panel-body">
<a href="/comics/comic/create">Add entry</a>
</div>
</div>
{{> footer}}
@@ -0,0 +1,22 @@
<nav class="navbar navbar-default navbar-fixed-bottom">
<div class="container">
<div class="row">
<p class="col-lg-12">
<ul class="list-inline">
<li><a href="/">Kontor</a></li>
<li class="footer-menu-divider">⋅</li>
{{#login}}
<li><a href="{{link}}">{{title}}</a></li>
{{/login}}
{{^login}}
<li><a href="/user/logout">Logout</a></li>
{{/login}}
</ul>
<p class="copyright text-muted small">Version {{version}} Copyright © 2018. All Rights Reserved</p>
</div>
</div>
</div>
</nav>
</body>
</html>
@@ -0,0 +1,15 @@
<!doctype html>
<html lang="de">
<head>
<!--Use the `title` variable to set the title of the page-->
<title>{{title}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<!--Use bootstrap to make the application look decent-->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script async src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
</head>
<body class="container">
@@ -0,0 +1,18 @@
{{> header}}
{{> menu}}
<div>
{{#banner.title}}
<section>
<header class="banner">
<h2 class="banner-title">{{banner.title}}</h2>
</header>
<div class="banner-content">
{{banner.content}}
</div>
</section>
{{/banner.title}}
</div>
{{> footer}}
@@ -0,0 +1,23 @@
<nav class="navbar navbar-default">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Kontor</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
{{#navigation}}
<li><a href="{{link}}">{{title}}</a></li>
{{/navigation}}
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
@@ -0,0 +1 @@
junit.jupiter.testinstance.lifecycle.default = per_class
+26
View File
@@ -0,0 +1,26 @@
# This workflow will build a Java project with Gradle
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
name: Java CI with Gradle
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
+7
View File
@@ -0,0 +1,7 @@
.gradle/
build/
bin/
.classpath
.project
.settings/
.asciidoctorconfig.adoc
+36
View File
@@ -0,0 +1,36 @@
variables:
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
before_script:
- source "/home/gitlab-runner/.sdkman/bin/sdkman-init.sh"
- sdk u java 11.0.12-open
stages:
- build
- test
- analysis
- publish
Build Application:
stage: build
script: ./gradlew build
Create Documentation:
stage: build
script: ./gradlew asciidoctorPdf
Test Application:
stage: test
script: ./gradlew check
sonarqube-check:
stage: analysis
variables:
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar" # Defines the location of the analysis task cache
GIT_DEPTH: "0" # Tells git to fetch all the branches of the project, required by the analysis task
script: ./gradlew sonarqube
allow_failure: true
Publish Artifacts:
stage: publish
script: ./gradlew publish
+4
View File
@@ -0,0 +1,4 @@
![Java CI with Gradle](https://github.com/tpeetz/kontor-wicket/workflows/Java%20CI%20with%20Gradle/badge.svg)
# kontor-wicket
Kontor Application with Apache Wicket
+61
View File
@@ -0,0 +1,61 @@
plugins {
id 'war'
id 'jacoco-report-aggregation'
alias(versions.plugins.asciidoctorConvention)
alias(versions.plugins.javaConvention)
alias(versions.plugins.sonarqube)
}
final BUILD_DATE = new Date().format('dd.MM.yyyy').toString()
dependencies {
implementation versions.slf4j
implementation versions.commonscli
testImplementation versions.junit
implementation versions.bundles.logback
spotbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.12.0'
implementation 'org.apache.wicket:wicket:9.9.1'
implementation 'org.apache.wicket:wicket-extensions:9.9.1'
testImplementation 'org.eclipse.jetty:jetty-webapp:9.4.44.v20210927'
testImplementation 'org.eclipse.jetty:jetty-jmx:9.4.44.v20210927'
}
publishing {
publications {
application(MavenPublication) {
groupId = group
from components.java
}
}
}
jacocoTestReport {
reports {
xml.enabled true
}
}
test.finalizedBy jacocoTestReport
spotbugs {
ignoreFailures = true
}
sonarqube {
properties {
property "sonar.projectKey", "kontor_kontor-wicket_AYCAQ47WRCd9N673sSSD"
property "sonar.host.url", "https://sonar.thpeetz.de"
property "sonar.login", "7cf604ab1ebf48f7dc60d942c8196a132b228a6d"
property "sonar.qualitygate.wait", true
property "sonar.sourceEncoding", "UTF-8"
}
}
tasks.named('sonarqube').configure {
dependsOn test
}
wrapper {
gradleVersion = "7.5"
}
+3
View File
@@ -0,0 +1,3 @@
description='Anwendung Kontor mit Apache Wicket'
group=de.thpeetz
version=1.0.0-SNAPSHOT
Binary file not shown.
+5
View File
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+240
View File
@@ -0,0 +1,240 @@
#!/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/master/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
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# 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"'
# 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
which java >/dev/null 2>&1 || 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
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
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
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
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" "$@"
+91
View File
@@ -0,0 +1,91 @@
@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=.
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.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
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
+1
View File
@@ -0,0 +1 @@
rootProject.name = "kontor-wicket"
+179
View File
@@ -0,0 +1,179 @@
= Projektbeschreibung kontor-wicket: 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-wicket und der Erstellung der Dokumentation.
=== Verwendete Tools
==== Gitlab
Für die Verwaltung des Sourcecode kommt ((Gitlab))<<gitlab>> zum Einsatz.
Mit Gitlab werden auch die Projektaufgaben verwaltet.
Das Projekt und das dazugehörige Git Repository sind unter der Adresse
https://gitlab.ingenieurbuero-peetz.de/kontor/kontor-wicket
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))<<3>> 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
==== 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
- [[[gitlab]]] http://www.gitlab.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,18 @@
package de.thpeetz.kontor;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.WebPage;
public class HomePage extends WebPage {
private static final long serialVersionUID = 1L;
public HomePage(final PageParameters parameters) {
super(parameters);
add(new Label("version", getApplication().getFrameworkSettings().getVersion()));
// TODO Add your page's components here
}
}
@@ -0,0 +1,41 @@
package de.thpeetz.kontor;
import org.apache.wicket.csp.CSPDirective;
import org.apache.wicket.csp.CSPDirectiveSrcValue;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.protocol.http.WebApplication;
/**
* Application object for your web application.
* If you want to run this application without deploying, run the Start class.
*
* @see de.thpeetz.kontor.Start#main(String[])
*/
public class KontorApplication extends WebApplication
{
/**
* @see org.apache.wicket.Application#getHomePage()
*/
@Override
public Class<? extends WebPage> getHomePage()
{
return HomePage.class;
}
/**
* @see org.apache.wicket.Application#init()
*/
@Override
public void init()
{
super.init();
// needed for the styling used by the quickstart
getCspSettings().blocking()
.add(CSPDirective.STYLE_SRC, CSPDirectiveSrcValue.SELF)
.add(CSPDirective.STYLE_SRC, "https://fonts.googleapis.com/css")
.add(CSPDirective.FONT_SRC, "https://fonts.gstatic.com");
// add your configuration here
}
}
@@ -0,0 +1,62 @@
<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">
<head>
<meta charset="utf-8" />
<title>Apache Wicket Quickstart</title>
<link href='https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz:regular,bold' rel='stylesheet' type='text/css' />
<link rel="stylesheet" href="style.css" type="text/css" media="screen" title="Stylesheet" />
</head>
<body>
<div id="hd">
<div id="logo">
<img src="logo.png" width="50px" height="50px" alt="Wicket Logo" />
<h1>Apache Wicket</h1>
</div>
</div>
<div id="bd">
<h2>Congratulations!</h2>
<p>
Your quick start works! This project is especially useful to
start developing your Wicket application or to create a test
case for a bug report.
</p>
<h3>Get started</h3>
<p>
You can even <a href="https://localhost:8443">switch to HTTPS</a>!
</p>
<p>
From here you can start hacking away at your application and
wow your clients:
</p>
<ul>
<li>work through <a href="https://wicket.apache.org/learn/examples" target="_blank" title="Hello world and friends">some examples</a></li>
<li>read <a href="https://wicket.apache.org/learn/books" target="_blank" title="Books about Wicket in English, German and Japanese">some books</a></li>
</ul>
<h3>Get help</h3>
<p>
We are here to help!
</p>
<ul>
<li>join us on IRC on <a href="irc:%23%23wicket@irc.freenode.net">&#35;&#35;wicket@irc.freenode.net</a></li>
<li><a href="http://wicket-users.markmail.org/" target="_blank" title="Search the mailing list archives">search</a> our mailing list archives</li>
<li>ask a question on the <a href="https://wicket.apache.org/help/email.html">users list</a></li>
</ul>
<h3>Reporting a bug</h3>
<p>
Help us help you:
</p>
<ol>
<li>reproduce the bug with the <strong>least</strong> amount of code</li>
<li>create a unit test that shows the bug</li>
<li>fix the bug and create a patch</li>
<li>attach the result of step 1, 2 or 3 to a <a href="https://issues.apache.org/jira/browse/WICKET" target="_blank">JIRA issue</a></li>
<li>profit!</li>
</ol>
<p>
Please mention the correct Wicket version: <wicket:container wicket:id="version">1.5-SNAPSHOT</wicket:container>.
</p>
</div>
<div id="ft">
</div>
</body>
</html>
+32
View File
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://www.oracle.com/webfolder/technetwork/jsc/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>kontor-wicket</display-name>
<!--
There are three means to configure Wickets configuration mode and they
are tested in the order given.
1) A system property: -Dwicket.configuration
2) servlet specific <init-param>
3) context specific <context-param>
The value might be either "development" (reloading when templates change) or
"deployment". If no configuration is found, "development" is the default. -->
<filter>
<filter-name>wicket.kontor-wicket</filter-name>
<filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
<init-param>
<param-name>applicationClassName</param-name>
<param-value>de.thpeetz.kontor.KontorApplication</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>wicket.kontor-wicket</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+68
View File
@@ -0,0 +1,68 @@
body, p, li, a { font-family: georgia, times, serif;font-size:13pt;}
h1, h2, h3 { font-family: 'Yanone Kaffeesatz', arial, serif; }
body { margin:0;padding:0;}
#hd {
width : 100%;
height : 87px;
background-color : #092E67;
margin-top : 0;
padding-top : 10px;
border-bottom : 1px solid #888;
z-index : 0;
}
#ft {
position : absolute;
bottom : 0;
width : 100%;
height : 99px;
background-color : #6493D2;
border-top : 1px solid #888;
z-index : 0;
}
#logo,#bd {
width : 650px;
margin: 0 auto;
padding: 25px 50px 0 50px;
}
#logo h1 {
color : white;
font-size:36pt;
display: inline;
}
#logo img {
display:inline;
vertical-align: bottom;
margin-left : 50px;
margin-right : 5px;
}
body { margin-top : 0; padding-top : 0;}
#logo, #logo h1 { margin-top : 0; padding-top : 0;}
#bd {
position : absolute;
top : 75px;
bottom : 75px;
left : 50%;
margin-left : -325px;
z-index : 1;
overflow: auto;
background-color : #fff;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
-moz-box-shadow: 0px 0px 10px #888;
-webkit-box-shadow: 0px 0px 10px #888;
box-shadow: 0px 0px 10px #888;
}
a, a:visited, a:hover, a:active {
color : #6493D2;
}
h2 {
padding : 0; margin:0;
font-size:36pt;
color:#FF5500;
}
h3 {
padding : 0; margin:0;
font-size:24pt;
color:#092E67;
}
@@ -0,0 +1,106 @@
package de.thpeetz.kontor;
import java.lang.management.ManagementFactory;
import javax.management.MBeanServer;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.webapp.WebAppContext;
/**
* Separate startup class for people that want to run the examples directly. Use parameter
* -Dcom.sun.management.jmxremote to startup JMX (and e.g. connect with jconsole).
*/
public class Start
{
/**
* Main function, starts the jetty server.
*
* @param args
*/
public static void main(String[] args)
{
System.setProperty("wicket.configuration", "development");
Server server = new Server();
HttpConfiguration http_config = new HttpConfiguration();
http_config.setSecureScheme("https");
http_config.setSecurePort(8443);
http_config.setOutputBufferSize(32768);
ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(http_config));
http.setPort(8080);
http.setIdleTimeout(1000 * 60 * 60);
server.addConnector(http);
Resource keystore = Resource.newClassPathResource("/keystore.p12");
if (keystore != null && keystore.exists())
{
// if a keystore for a SSL certificate is available, start a SSL
// connector on port 8443.
// By default, the quickstart comes with a Apache Wicket Quickstart
// Certificate that expires about half way september 2031. Do not
// use this certificate anywhere important as the passwords are
// available in the source.
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStoreResource(keystore);
sslContextFactory.setKeyStorePassword("wicket");
sslContextFactory.setKeyManagerPassword("wicket");
HttpConfiguration https_config = new HttpConfiguration(http_config);
https_config.addCustomizer(new SecureRequestCustomizer());
ServerConnector https = new ServerConnector(server, new SslConnectionFactory(
sslContextFactory, "http/1.1"), new HttpConnectionFactory(https_config));
https.setPort(8443);
https.setIdleTimeout(500000);
server.addConnector(https);
System.out.println("SSL access to the examples has been enabled on port 8443");
System.out
.println("You can access the application using SSL on https://localhost:8443");
System.out.println();
}
WebAppContext bb = new WebAppContext();
bb.setServer(server);
bb.setContextPath("/");
bb.setWar("src/main/webapp");
// uncomment the next two lines if you want to start Jetty with WebSocket (JSR-356) support
// you need org.apache.wicket:wicket-native-websocket-javax in the classpath!
// ServerContainer serverContainer = WebSocketServerContainerInitializer.configureContext(bb);
// serverContainer.addEndpoint(new WicketServerEndpointConfig());
// uncomment next line if you want to test with JSESSIONID encoded in the urls
// ((AbstractSessionManager)
// bb.getSessionHandler().getSessionManager()).setUsingCookies(false);
server.setHandler(bb);
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
MBeanContainer mBeanContainer = new MBeanContainer(mBeanServer);
server.addEventListener(mBeanContainer);
server.addBean(mBeanContainer);
try
{
server.start();
server.join();
} catch (Exception e) {
e.printStackTrace();
System.exit(100);
}
}
}
@@ -0,0 +1,29 @@
package de.thpeetz.kontor;
import org.apache.wicket.util.tester.WicketTester;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* Simple test using the WicketTester
*/
public class TestHomePage
{
private WicketTester tester;
@BeforeEach
public void setUp()
{
tester = new WicketTester(new KontorApplication());
}
@Test
public void homepageRendersSuccessfully()
{
//start and render the test page
tester.startPage(HomePage.class);
//assert rendered page class
tester.assertRenderedPage(HomePage.class);
}
}
@@ -0,0 +1,38 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<!-- ============================================================= -->
<!-- Configure the Jetty Server instance with an ID "Server" -->
<!-- by adding a HTTP connector. -->
<!-- This configuration must be used in conjunction with jetty.xml -->
<!-- ============================================================= -->
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<!-- =========================================================== -->
<!-- Add a HTTP Connector. -->
<!-- Configure an o.e.j.server.ServerConnector with a single -->
<!-- HttpConnectionFactory instance using the common httpConfig -->
<!-- instance defined in jetty.xml -->
<!-- -->
<!-- Consult the javadoc of o.e.j.server.ServerConnector and -->
<!-- o.e.j.server.HttpConnectionFactory for all configuration -->
<!-- that may be set here. -->
<!-- =========================================================== -->
<Call name="addConnector">
<Arg>
<New class="org.eclipse.jetty.server.ServerConnector">
<Arg name="server"><Ref refid="Server" /></Arg>
<Arg name="factories">
<Array type="org.eclipse.jetty.server.ConnectionFactory">
<Item>
<New class="org.eclipse.jetty.server.HttpConnectionFactory">
<Arg name="config"><Ref refid="httpConfig" /></Arg>
</New>
</Item>
</Array>
</Arg>
<Set name="host"><Property name="jetty.host" /></Set>
<Set name="port"><Property name="jetty.port" default="8080" /></Set>
<Set name="idleTimeout"><Property name="http.timeout" default="30000"/></Set>
</New>
</Arg>
</Call>
</Configure>
@@ -0,0 +1,45 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<!-- ============================================================= -->
<!-- Configure a HTTPS connector. -->
<!-- This configuration must be used in conjunction with jetty.xml -->
<!-- and jetty-ssl.xml. -->
<!-- ============================================================= -->
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<!-- =========================================================== -->
<!-- Add a HTTPS Connector. -->
<!-- Configure an o.e.j.server.ServerConnector with connection -->
<!-- factories for TLS (aka SSL) and HTTP to provide HTTPS. -->
<!-- All accepted TLS connections are wired to a HTTP connection.-->
<!-- -->
<!-- Consult the javadoc of o.e.j.server.ServerConnector, -->
<!-- o.e.j.server.SslConnectionFactory and -->
<!-- o.e.j.server.HttpConnectionFactory for all configuration -->
<!-- that may be set here. -->
<!-- =========================================================== -->
<Call id="httpsConnector" name="addConnector">
<Arg>
<New class="org.eclipse.jetty.server.ServerConnector">
<Arg name="server"><Ref refid="Server" /></Arg>
<Arg name="factories">
<Array type="org.eclipse.jetty.server.ConnectionFactory">
<Item>
<New class="org.eclipse.jetty.server.SslConnectionFactory">
<Arg name="next">http/1.1</Arg>
<Arg name="sslContextFactory"><Ref refid="sslContextFactory"/></Arg>
</New>
</Item>
<Item>
<New class="org.eclipse.jetty.server.HttpConnectionFactory">
<Arg name="config"><Ref refid="sslHttpConfig"/></Arg>
</New>
</Item>
</Array>
</Arg>
<Set name="host"><Property name="jetty.host" /></Set>
<Set name="port"><Property name="jetty.https.port" default="8443" /></Set>
<Set name="idleTimeout">30000</Set>
</New>
</Arg>
</Call>
</Configure>
@@ -0,0 +1,57 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<!-- ============================================================= -->
<!-- Configure a TLS (SSL) Context Factory                         -->
<!-- This configuration must be used in conjunction with jetty.xml -->
<!-- and either jetty-https.xml or jetty-spdy.xml (but not both)   -->
<!-- ============================================================= -->
<Configure id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory">
<Set name="KeyStorePath"><Property name="maven.project.build.directory.test-classes" default="." />/<Property name="jetty.keystore" default="keystore"/></Set>
<Set name="KeyStorePassword"><Property name="jetty.keystore.password" default="wicket"/></Set>
<Set name="KeyManagerPassword"><Property name="jetty.keymanager.password" default="wicket"/></Set>
<Set name="EndpointIdentificationAlgorithm"></Set>
<Set name="ExcludeCipherSuites">
<Array type="String">
<Item>SSL_RSA_WITH_DES_CBC_SHA</Item>
<Item>SSL_DHE_RSA_WITH_DES_CBC_SHA</Item>
<Item>SSL_DHE_DSS_WITH_DES_CBC_SHA</Item>
<Item>SSL_RSA_EXPORT_WITH_RC4_40_MD5</Item>
<Item>SSL_RSA_EXPORT_WITH_DES40_CBC_SHA</Item>
<Item>SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA</Item>
<Item>SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA</Item>
<Item>TLS_RSA_WITH_AES_256_GCM_SHA384</Item>
<Item>TLS_RSA_WITH_AES_128_GCM_SHA256</Item>
<Item>TLS_RSA_WITH_AES_256_CBC_SHA256</Item>
<Item>TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA</Item>
<Item>TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA</Item>
<Item>TLS_RSA_WITH_AES_256_CBC_SHA</Item>
<Item>TLS_RSA_WITH_AES_256_CBC_SHA</Item>
<Item>TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA</Item>
<Item>TLS_ECDH_RSA_WITH_AES_256_CBC_SHA</Item>
<Item>TLS_DHE_RSA_WITH_AES_256_CBC_SHA</Item>
<Item>TLS_DHE_DSS_WITH_AES_256_CBC_SHA</Item>
<Item>TLS_RSA_WITH_AES_128_CBC_SHA256</Item>
<Item>TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA</Item>
<Item>TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA</Item>
<Item>TLS_RSA_WITH_AES_128_CBC_SHA</Item>
<Item>TLS_RSA_WITH_AES_128_CBC_SHA</Item>
<Item>TLS_RSA_WITH_AES_128_CBC_SHA</Item>
<Item>TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA</Item>
<Item>TLS_ECDH_RSA_WITH_AES_128_CBC_SHA</Item>
<Item>TLS_DHE_RSA_WITH_AES_128_CBC_SHA</Item>
<Item>TLS_DHE_DSS_WITH_AES_128_CBC_SHA</Item>
</Array>
</Set>
<!-- =========================================================== -->
<!-- Create a TLS specific HttpConfiguration based on the -->
<!-- common HttpConfiguration defined in jetty.xml -->
<!-- Add a SecureRequestCustomizer to extract certificate and -->
<!-- session information -->
<!-- =========================================================== -->
<New id="sslHttpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
<Arg><Ref refid="httpConfig"/></Arg>
<Call name="addCustomizer">
<Arg><New class="org.eclipse.jetty.server.SecureRequestCustomizer"/></Arg>
</Call>
</New>
</Configure>
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<!-- ============================================================= -->
<!-- Configure a HTTP connector.                                  -->
<!-- ============================================================= -->
<Configure>
<New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
<Set name="secureScheme">https</Set>
<Set name="securePort">
<Property name="jetty.secure.port" default="8443" />
</Set>
<Set name="outputBufferSize">32768</Set>
<Set name="requestHeaderSize">8192</Set>
<Set name="responseHeaderSize">8192</Set>
<Set name="sendServerVersion">true</Set>
<Set name="sendDateHeader">false</Set>
<Set name="headerCacheSize">512</Set>
<!-- Uncomment to enable handling of X-Forwarded- style headers <Call name="addCustomizer"> -->
<!-- <Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg> -->
<!-- </Call> -->
</New>
</Configure>
Binary file not shown.