✨ Add membership feature
This commit is contained in:
parent
23955b33df
commit
f1751250a1
47
Jenkinsfile
vendored
Executable file
47
Jenkinsfile
vendored
Executable file
@ -0,0 +1,47 @@
|
||||
pipeline {
|
||||
agent any
|
||||
|
||||
stages {
|
||||
stage('Set Image Name') {
|
||||
steps {
|
||||
script {
|
||||
env.TAG = "${env.BUILD_NUMBER}"
|
||||
env.REFERENCE = "harbor.dinauer.dev/tavolio/iam-backend"
|
||||
env.IMAGE = "${env.REFERENCE}:${env.BUILD_NUMBER}";
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Build Quarkus application') {
|
||||
steps {
|
||||
script {
|
||||
sh './gradlew build'
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Build Docker Image') {
|
||||
steps {
|
||||
script {
|
||||
sh "docker build --no-cache -t ${env.IMAGE} -f src/main/docker/Dockerfile.jvm ."
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Push Image to Docker Hub') {
|
||||
steps {
|
||||
script {
|
||||
withCredentials([usernamePassword(credentialsId: 'harbor', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) {
|
||||
sh 'echo ${PASSWORD} | docker login harbor.dinauer.dev -u ${USERNAME} --password-stdin'
|
||||
sh "docker push ${env.IMAGE}"
|
||||
sh "docker logout"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Remove image from host') {
|
||||
steps {
|
||||
script {
|
||||
sh "docker image rm --force ${env.IMAGE}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
pom.xml
8
pom.xml
@ -63,6 +63,14 @@
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-hibernate-validator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-smallrye-jwt</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-smallrye-jwt-build</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-junit5</artifactId>
|
||||
|
||||
93
src/main/java/de/tavolio/AuthenticationService.java
Normal file
93
src/main/java/de/tavolio/AuthenticationService.java
Normal file
@ -0,0 +1,93 @@
|
||||
package de.tavolio;
|
||||
|
||||
import de.tavolio.account.AccountEntity;
|
||||
import de.tavolio.account.AccountRepo;
|
||||
import io.quarkus.security.UnauthorizedException;
|
||||
import io.vertx.core.http.HttpServerRequest;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
import jakarta.ws.rs.core.SecurityContext;
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Base64;
|
||||
|
||||
@ApplicationScoped
|
||||
public class AuthenticationService
|
||||
{
|
||||
@Inject
|
||||
Logger LOG;
|
||||
|
||||
@Inject
|
||||
AccountRepo accountRepo;
|
||||
|
||||
@Inject
|
||||
SecurityContext securityContext;
|
||||
|
||||
@Inject
|
||||
HttpServerRequest request;
|
||||
|
||||
@ConfigProperty(name = "iam.user.name")
|
||||
String superuserName;
|
||||
|
||||
@ConfigProperty(name = "iam.user.password")
|
||||
String superuserPassword;
|
||||
|
||||
public boolean isSuperUser()
|
||||
{
|
||||
String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
if (authHeader != null && !authHeader.isBlank())
|
||||
{
|
||||
String[] sections = authHeader.split("\\s+");
|
||||
if (sections.length == 2 && sections[0].equals("Basic"))
|
||||
{
|
||||
String value = new String(Base64.getDecoder().decode(sections[1]));
|
||||
String[] parts = value.split(":");
|
||||
if (parts.length == 2)
|
||||
{
|
||||
String username = parts[0];
|
||||
String password = parts[1];
|
||||
boolean isSuperuser = username.equals(superuserName) && password.equals(superuserPassword);
|
||||
if (isSuperuser)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
{
|
||||
LOG.errorf("Invalid username or password", getRequestPath());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.errorf("Invalid base64 credentials %s", getRequestPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public AccountEntity requireUser()
|
||||
{
|
||||
Principal principal = securityContext.getUserPrincipal();
|
||||
if(principal != null)
|
||||
{
|
||||
AccountEntity accountEntity = accountRepo.findById(principal.getName());
|
||||
if(accountEntity != null)
|
||||
{
|
||||
return accountEntity;
|
||||
}
|
||||
LOG.warnf("No account found for request %s", getRequestPath());
|
||||
throw new NotFoundException();
|
||||
}
|
||||
LOG.warnf("Unauthorized request %s", getRequestPath());
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
private String getRequestPath()
|
||||
{
|
||||
return String.format("[%s, %s]", request.method().name(), request.path());
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,10 @@
|
||||
package de.tavolio.account;
|
||||
|
||||
import de.tavolio.member.MembershipEntity;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@ -22,6 +22,12 @@ public class AccountEntity extends PanacheEntityBase
|
||||
|
||||
private String password;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
private AccountStatus status;
|
||||
|
||||
@OneToMany(mappedBy = "account")
|
||||
private Set<MembershipEntity> memberships;
|
||||
|
||||
public static AccountEntity init()
|
||||
{
|
||||
return new AccountEntity().setId(UUID.randomUUID().toString());
|
||||
@ -81,4 +87,26 @@ public class AccountEntity extends PanacheEntityBase
|
||||
this.password = password;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Set<MembershipEntity> getMemberships()
|
||||
{
|
||||
return memberships;
|
||||
}
|
||||
|
||||
public AccountEntity setMemberships(Set<MembershipEntity> memberships)
|
||||
{
|
||||
this.memberships = memberships;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountStatus getStatus()
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
public AccountEntity setStatus(AccountStatus status)
|
||||
{
|
||||
this.status = status;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,13 +2,12 @@ package de.tavolio.account;
|
||||
|
||||
import de.tavolio.account.dto.Account;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.ws.rs.ApplicationPath;
|
||||
|
||||
@ApplicationScoped
|
||||
public class AccountMapper
|
||||
{
|
||||
public Account map(AccountEntity accountEntity)
|
||||
{
|
||||
return new Account(accountEntity.getId());
|
||||
return new Account(accountEntity.getId(), accountEntity.getFirstname(), accountEntity.getLastname(), accountEntity.getEmail(), accountEntity.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,16 @@
|
||||
package de.tavolio.account;
|
||||
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
|
||||
import io.quarkus.panache.common.Parameters;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@ApplicationScoped
|
||||
public class AccountRepo implements PanacheRepositoryBase<AccountEntity, String>
|
||||
{
|
||||
public Optional<AccountEntity> findOptionalByEmail(String email)
|
||||
{
|
||||
return find("email = :email", Parameters.with("email", email)).firstResultOptional();
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,8 +7,8 @@ import jakarta.validation.Valid;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.reactive.common.NotImplementedYet;
|
||||
|
||||
@Path("/accounts")
|
||||
public class AccountResource
|
||||
@ -28,8 +28,9 @@ public class AccountResource
|
||||
}
|
||||
|
||||
@GET
|
||||
public Account get()
|
||||
@Path("/{id}")
|
||||
public Account get(@PathParam("id") String id)
|
||||
{
|
||||
throw new NotImplementedYet();
|
||||
return accountService.getUser(id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,21 +1,30 @@
|
||||
package de.tavolio.account;
|
||||
|
||||
import de.tavolio.AuthenticationService;
|
||||
import de.tavolio.account.dto.Account;
|
||||
import de.tavolio.account.dto.AccountCreation;
|
||||
import io.quarkus.elytron.security.common.BcryptUtil;
|
||||
import io.quarkus.security.UnauthorizedException;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
@ApplicationScoped
|
||||
public class AccountService
|
||||
{
|
||||
@Inject
|
||||
Logger LOG;
|
||||
|
||||
@Inject
|
||||
AccountRepo accountRepo;
|
||||
|
||||
@Inject
|
||||
AccountMapper accountMapper;
|
||||
|
||||
@Inject
|
||||
AuthenticationService authenticationService;
|
||||
|
||||
@Transactional
|
||||
public Account create(AccountCreation account)
|
||||
{
|
||||
@ -23,8 +32,21 @@ public class AccountService
|
||||
accountEntity.setEmail(account.email())
|
||||
.setFirstname(account.firstname())
|
||||
.setLastname(account.lastname())
|
||||
.setPassword(BcryptUtil.bcryptHash(account.password()));
|
||||
.setPassword(BcryptUtil.bcryptHash(account.password()))
|
||||
.setStatus(AccountStatus.INIT);
|
||||
accountRepo.persist(accountEntity);
|
||||
return accountMapper.map(accountEntity);
|
||||
}
|
||||
|
||||
public Account getUser(String id)
|
||||
{
|
||||
AccountEntity account = authenticationService.requireUser();
|
||||
AccountEntity requestedAccount = accountRepo.findById(id);
|
||||
if (requestedAccount != null && requestedAccount.getId().equals(account.getId()))
|
||||
{
|
||||
return accountMapper.map(authenticationService.requireUser());
|
||||
}
|
||||
LOG.errorf("Cannot access account");
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
}
|
||||
|
||||
6
src/main/java/de/tavolio/account/AccountStatus.java
Normal file
6
src/main/java/de/tavolio/account/AccountStatus.java
Normal file
@ -0,0 +1,6 @@
|
||||
package de.tavolio.account;
|
||||
|
||||
public enum AccountStatus
|
||||
{
|
||||
INIT, REGISTERED
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
package de.tavolio.account.dto;
|
||||
|
||||
public record Account(String id)
|
||||
import de.tavolio.account.AccountStatus;
|
||||
|
||||
public record Account(String id, String firstname, String lastname, String email, AccountStatus status)
|
||||
{
|
||||
}
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
package de.tavolio.member;
|
||||
|
||||
import de.tavolio.AuthenticationService;
|
||||
import de.tavolio.account.AccountEntity;
|
||||
import de.tavolio.member.dto.AccountMemberships;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
|
||||
@ApplicationScoped
|
||||
@Path("/memberships")
|
||||
public class AccountMembershipResource
|
||||
{
|
||||
@Inject
|
||||
MembershipRepo membershipRepo;
|
||||
|
||||
@Inject
|
||||
AuthenticationService authenticationService;
|
||||
|
||||
@Inject
|
||||
MembershipMapper membershipMapper;
|
||||
|
||||
@GET
|
||||
public AccountMemberships get()
|
||||
{
|
||||
AccountEntity account = authenticationService.requireUser();
|
||||
return new AccountMemberships(
|
||||
membershipMapper.map(membershipRepo.findByTenantTypeAndAccount(TenantType.ORGANISATION, account)),
|
||||
membershipMapper.map(membershipRepo.findByTenantTypeAndAccount(TenantType.RESTAURANT, account))
|
||||
);
|
||||
}
|
||||
}
|
||||
105
src/main/java/de/tavolio/member/MembershipEntity.java
Normal file
105
src/main/java/de/tavolio/member/MembershipEntity.java
Normal file
@ -0,0 +1,105 @@
|
||||
package de.tavolio.member;
|
||||
|
||||
import de.tavolio.account.AccountEntity;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "membership")
|
||||
public class MembershipEntity extends PanacheEntityBase
|
||||
{
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
@Column(name = "tenant_type")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private TenantType tenantType;
|
||||
|
||||
@Column(name = "tenant_id")
|
||||
private String tenantId;
|
||||
|
||||
@Column(name = "member_role")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private MembershipRole role;
|
||||
|
||||
@Column(name = "member_since")
|
||||
private ZonedDateTime memberSince;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "account_id")
|
||||
private AccountEntity account;
|
||||
|
||||
public static MembershipEntity init()
|
||||
{
|
||||
return new MembershipEntity().setId(UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
public String getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public MembershipEntity setId(String id)
|
||||
{
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TenantType getTenantType()
|
||||
{
|
||||
return tenantType;
|
||||
}
|
||||
|
||||
public MembershipEntity setTenantType(TenantType tenantType)
|
||||
{
|
||||
this.tenantType = tenantType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getTenantId()
|
||||
{
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
public MembershipEntity setTenantId(String tenantId)
|
||||
{
|
||||
this.tenantId = tenantId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MembershipRole getRole()
|
||||
{
|
||||
return role;
|
||||
}
|
||||
|
||||
public MembershipEntity setRole(MembershipRole role)
|
||||
{
|
||||
this.role = role;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ZonedDateTime getMemberSince()
|
||||
{
|
||||
return memberSince;
|
||||
}
|
||||
|
||||
public MembershipEntity setMemberSince(ZonedDateTime memberSince)
|
||||
{
|
||||
this.memberSince = memberSince;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountEntity getAccount()
|
||||
{
|
||||
return account;
|
||||
}
|
||||
|
||||
public MembershipEntity setAccount(AccountEntity account)
|
||||
{
|
||||
this.account = account;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
25
src/main/java/de/tavolio/member/MembershipMapper.java
Normal file
25
src/main/java/de/tavolio/member/MembershipMapper.java
Normal file
@ -0,0 +1,25 @@
|
||||
package de.tavolio.member;
|
||||
|
||||
import de.tavolio.account.AccountMapper;
|
||||
import de.tavolio.member.dto.Membership;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ApplicationScoped
|
||||
public class MembershipMapper
|
||||
{
|
||||
@Inject
|
||||
AccountMapper accountMapper;
|
||||
|
||||
public Membership map(MembershipEntity membership)
|
||||
{
|
||||
return new Membership(membership.getId(), membership.getTenantType(), membership.getTenantId(), membership.getRole(), accountMapper.map(membership.getAccount()));
|
||||
}
|
||||
|
||||
public List<Membership> map(List<MembershipEntity> memberships)
|
||||
{
|
||||
return memberships.stream().map(this::map).toList();
|
||||
}
|
||||
}
|
||||
22
src/main/java/de/tavolio/member/MembershipRepo.java
Normal file
22
src/main/java/de/tavolio/member/MembershipRepo.java
Normal file
@ -0,0 +1,22 @@
|
||||
package de.tavolio.member;
|
||||
|
||||
import de.tavolio.account.AccountEntity;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
|
||||
import io.quarkus.panache.common.Parameters;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ApplicationScoped
|
||||
public class MembershipRepo implements PanacheRepositoryBase<MembershipEntity, String>
|
||||
{
|
||||
public List<MembershipEntity> findByTenantTypeAndAccount(TenantType tenantType, AccountEntity account)
|
||||
{
|
||||
return list("tenantType = :tenantType AND account = :account", Parameters.with("tenantType", tenantType).and("account", account));
|
||||
}
|
||||
|
||||
public List<MembershipEntity> findByTenantTypeAndTenantId(TenantType tenantType, String tenantId)
|
||||
{
|
||||
return list("tenantType = :tenantType AND tenantId = :tenantId", Parameters.with("tenantType", tenantType).and("tenantId", tenantId));
|
||||
}
|
||||
}
|
||||
6
src/main/java/de/tavolio/member/MembershipRole.java
Normal file
6
src/main/java/de/tavolio/member/MembershipRole.java
Normal file
@ -0,0 +1,6 @@
|
||||
package de.tavolio.member;
|
||||
|
||||
public enum MembershipRole
|
||||
{
|
||||
OWNER, ADMIN, MEMBER
|
||||
}
|
||||
87
src/main/java/de/tavolio/member/MembershipService.java
Normal file
87
src/main/java/de/tavolio/member/MembershipService.java
Normal file
@ -0,0 +1,87 @@
|
||||
package de.tavolio.member;
|
||||
|
||||
import de.tavolio.AuthenticationService;
|
||||
import de.tavolio.account.AccountEntity;
|
||||
import de.tavolio.account.AccountRepo;
|
||||
import de.tavolio.account.AccountStatus;
|
||||
import de.tavolio.member.dto.Membership;
|
||||
import de.tavolio.member.dto.MembershipCreation;
|
||||
import io.quarkus.security.UnauthorizedException;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@ApplicationScoped
|
||||
public class MembershipService
|
||||
{
|
||||
@Inject
|
||||
Logger LOG;
|
||||
|
||||
@Inject
|
||||
AuthenticationService authenticationService;
|
||||
|
||||
@Inject
|
||||
MembershipRepo membershipRepo;
|
||||
|
||||
@Inject
|
||||
MembershipMapper membershipMapper;
|
||||
@Inject
|
||||
AccountRepo accountRepo;
|
||||
|
||||
@Transactional
|
||||
public Membership create(TenantType tenantType, String tenantId, MembershipCreation membershipCreation)
|
||||
{
|
||||
switch (tenantType)
|
||||
{
|
||||
case ORGANISATION ->
|
||||
{
|
||||
if (membershipCreation.role().equals(MembershipRole.OWNER))
|
||||
{
|
||||
if (authenticationService.isSuperUser())
|
||||
{
|
||||
AccountEntity account = accountRepo.findById(membershipCreation.accountId());
|
||||
|
||||
MembershipEntity membership = MembershipEntity.init();
|
||||
membership.setAccount(account);
|
||||
membership.setRole(membershipCreation.role());
|
||||
membership.setTenantType(TenantType.ORGANISATION);
|
||||
membership.setTenantId(tenantId);
|
||||
membership.setMemberSince(ZonedDateTime.now());
|
||||
membershipRepo.persist(membership);
|
||||
|
||||
account.setStatus(AccountStatus.REGISTERED);
|
||||
accountRepo.persist(account);
|
||||
|
||||
return membershipMapper.map(membership);
|
||||
}
|
||||
LOG.errorf("Membership with role 'Owner' cannot be created without superuser permissions");
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
}
|
||||
case RESTAURANT ->
|
||||
{
|
||||
|
||||
}
|
||||
default ->
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
throw new BadRequestException();
|
||||
}
|
||||
|
||||
public List<Membership> findByTenantType(TenantType tenantType)
|
||||
{
|
||||
return membershipMapper.map(membershipRepo.findByTenantTypeAndAccount(tenantType, authenticationService.requireUser()));
|
||||
}
|
||||
|
||||
public List<Membership> findByTenantTypeAndTenantId(TenantType tenantType, String tenantId)
|
||||
{
|
||||
return membershipMapper.map(membershipRepo.findByTenantTypeAndTenantId(tenantType, tenantId));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,80 @@
|
||||
package de.tavolio.member;
|
||||
|
||||
import de.tavolio.member.dto.Membership;
|
||||
import de.tavolio.member.dto.MembershipCreation;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.reactive.common.NotImplementedYet;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Path("/{tenant-type}")
|
||||
public class TenantMembershipResource
|
||||
{
|
||||
@Inject
|
||||
Logger LOG;
|
||||
|
||||
@Inject
|
||||
MembershipService membershipService;
|
||||
|
||||
@POST
|
||||
@Path("/{tenant-id}/memberships")
|
||||
public Membership post(@PathParam("tenant-type") String tenantType, @PathParam("tenant-id") String tenantId, MembershipCreation membershipCreation)
|
||||
{
|
||||
switch (tenantType)
|
||||
{
|
||||
case "organisations" ->
|
||||
{
|
||||
Membership membership = membershipService.create(TenantType.ORGANISATION, tenantId, membershipCreation);
|
||||
LOG.infof("Created membership for organisation %s", tenantId);
|
||||
return membership;
|
||||
}
|
||||
case "restaurants" ->
|
||||
{
|
||||
Membership membership = membershipService.create(TenantType.RESTAURANT, tenantId, membershipCreation);
|
||||
LOG.infof("Created membership for restaurant %s", tenantId);
|
||||
return membership;
|
||||
}
|
||||
}
|
||||
throw new BadRequestException();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{tenant-id}/memberships")
|
||||
public List<Membership> get(@PathParam("tenant-type") String tenantType, @PathParam("tenant-id") String tenantId)
|
||||
{
|
||||
switch (tenantType)
|
||||
{
|
||||
case "organisations" ->
|
||||
{
|
||||
return membershipService.findByTenantTypeAndTenantId(TenantType.ORGANISATION, tenantId);
|
||||
}
|
||||
case "restaurants" ->
|
||||
{
|
||||
throw new NotImplementedYet();
|
||||
}
|
||||
}
|
||||
LOG.errorf("Unknown tenant type %s", tenantType);
|
||||
throw new BadRequestException();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/memberships")
|
||||
public List<Membership> get(@PathParam("tenant-type") String tenantType)
|
||||
{
|
||||
switch (tenantType)
|
||||
{
|
||||
case "organisations" ->
|
||||
{
|
||||
return membershipService.findByTenantType(TenantType.ORGANISATION);
|
||||
}
|
||||
case "restaurants" ->
|
||||
{
|
||||
return membershipService.findByTenantType(TenantType.RESTAURANT);
|
||||
}
|
||||
}
|
||||
LOG.errorf("Unknown tenant type %s", tenantType);
|
||||
throw new BadRequestException();
|
||||
}
|
||||
}
|
||||
6
src/main/java/de/tavolio/member/TenantType.java
Normal file
6
src/main/java/de/tavolio/member/TenantType.java
Normal file
@ -0,0 +1,6 @@
|
||||
package de.tavolio.member;
|
||||
|
||||
public enum TenantType
|
||||
{
|
||||
ORGANISATION, RESTAURANT
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package de.tavolio.member.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record AccountMemberships(List<Membership> organisations, List<Membership> restaurants)
|
||||
{
|
||||
}
|
||||
9
src/main/java/de/tavolio/member/dto/Membership.java
Normal file
9
src/main/java/de/tavolio/member/dto/Membership.java
Normal file
@ -0,0 +1,9 @@
|
||||
package de.tavolio.member.dto;
|
||||
|
||||
import de.tavolio.account.dto.Account;
|
||||
import de.tavolio.member.MembershipRole;
|
||||
import de.tavolio.member.TenantType;
|
||||
|
||||
public record Membership(String id, TenantType tenantType, String tenantId, MembershipRole role, Account account)
|
||||
{
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package de.tavolio.member.dto;
|
||||
|
||||
import de.tavolio.member.MembershipRole;
|
||||
|
||||
public record MembershipCreation(String accountId, MembershipRole role)
|
||||
{
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
package de.tavolio.organisation.member;
|
||||
|
||||
public class OrganisationMemberResource
|
||||
{
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
package de.tavolio.organisation.member;
|
||||
|
||||
public class OrganisationMembershipRepo
|
||||
{
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
package de.tavolio.restaurant.member;
|
||||
|
||||
public class OrganisationMemberResource
|
||||
{
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
package de.tavolio.restaurant.member;
|
||||
|
||||
public class RestaurantMembershipRepo
|
||||
{
|
||||
}
|
||||
@ -1,15 +1,26 @@
|
||||
package de.tavolio.session;
|
||||
|
||||
import de.tavolio.session.dto.SessionCreation;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import org.jboss.resteasy.reactive.common.NotImplementedYet;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
@Path("/sessions")
|
||||
public class SessionResource
|
||||
{
|
||||
@Inject
|
||||
Logger LOG;
|
||||
|
||||
@Inject
|
||||
SessionService sessionService;
|
||||
|
||||
@POST
|
||||
public String get()
|
||||
public String get(@Valid SessionCreation sessionCreation)
|
||||
{
|
||||
throw new NotImplementedYet();
|
||||
String token = sessionService.generateBySessionCreation(sessionCreation);
|
||||
LOG.infof("Generated token for email %s", sessionCreation.email());
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
39
src/main/java/de/tavolio/session/SessionService.java
Normal file
39
src/main/java/de/tavolio/session/SessionService.java
Normal file
@ -0,0 +1,39 @@
|
||||
package de.tavolio.session;
|
||||
|
||||
import de.tavolio.account.AccountEntity;
|
||||
import de.tavolio.account.AccountRepo;
|
||||
import de.tavolio.session.dto.SessionCreation;
|
||||
import io.quarkus.elytron.security.common.BcryptUtil;
|
||||
import io.smallrye.jwt.build.Jwt;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Optional;
|
||||
|
||||
@ApplicationScoped
|
||||
public class SessionService
|
||||
{
|
||||
@Inject
|
||||
AccountRepo accountRepo;
|
||||
|
||||
public String generateBySessionCreation(SessionCreation sessionCreation)
|
||||
{
|
||||
Optional<AccountEntity> accountEntityOptional = accountRepo.findOptionalByEmail(sessionCreation.email());
|
||||
if (accountEntityOptional.isPresent())
|
||||
{
|
||||
AccountEntity accountEntity = accountEntityOptional.get();
|
||||
if (BcryptUtil.matches(sessionCreation.password(), accountEntity.getPassword()))
|
||||
{
|
||||
return generateToken(accountEntity.getId());
|
||||
}
|
||||
}
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
private String generateToken(String upn)
|
||||
{
|
||||
return Jwt.upn(upn).expiresAt(ZonedDateTime.now().plusYears(1).toInstant()).issuer("https://tavolio.de").sign();
|
||||
}
|
||||
}
|
||||
10
src/main/java/de/tavolio/session/dto/SessionCreation.java
Normal file
10
src/main/java/de/tavolio/session/dto/SessionCreation.java
Normal file
@ -0,0 +1,10 @@
|
||||
package de.tavolio.session.dto;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
public record SessionCreation(
|
||||
@Email String email,
|
||||
@NotBlank String password)
|
||||
{
|
||||
}
|
||||
@ -5,6 +5,14 @@ quarkus.http.test-port=9089
|
||||
|
||||
quarkus.hibernate-orm.schema-management.strategy=drop-and-create
|
||||
|
||||
smallrye.jwt.sign.key.location=private.key
|
||||
mp.jwt.verify.publickey.location=public.crt
|
||||
mp.jwt.verify.issuer=https://tavolio.de
|
||||
|
||||
%dev.quarkus.datasource.username=postgres
|
||||
%dev.quarkus.datasource.password=${DB_PASSWORD}
|
||||
%dev.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/postgres?currentSchema=iam
|
||||
%dev.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/postgres?currentSchema=iam
|
||||
|
||||
# IAM Superuser
|
||||
%dev.iam.user.name=tavolio
|
||||
%dev.iam.user.password=tavolio
|
||||
@ -3,4 +3,10 @@
|
||||
-- insert into myentity (id, field) values(1, 'field-1');
|
||||
-- insert into myentity (id, field) values(2, 'field-2');
|
||||
-- insert into myentity (id, field) values(3, 'field-3');
|
||||
-- alter sequence myentity_seq restart with 4;
|
||||
-- alter sequence myentity_seq restart with 4;
|
||||
|
||||
INSERT INTO account (id, firstname, lastname, email, password, status)
|
||||
VALUES ('66b261fe-4c5a-4728-9857-67717f02d4e1', 'Andreas', 'Dinauer', 'andreas.j.dinauer@gmail.com', '$2a$12$cdrzIY4sMFAXiz29uo9Ul.MPy0RN0FGS2yjVzb5BTe6bSijn4eGQy', 'REGISTERED');
|
||||
|
||||
INSERT INTO membership(id, tenant_type, tenant_id, member_role, member_since, account_id)
|
||||
VALUES ('cd20d271-6e76-48c4-9cfb-a67f8892d46c', 'ORGANISATION', 'b3912be6-7503-4a13-b8ab-5d65af036742', 'OWNER', '2025-08-20 15:30:12.123456+02', '66b261fe-4c5a-4728-9857-67717f02d4e1');
|
||||
@ -1,23 +1,34 @@
|
||||
package de.tavolio.account;
|
||||
|
||||
import io.quarkus.deployment.dev.testing.TestConfig;
|
||||
import de.tavolio.utils.Database;
|
||||
import io.quarkus.elytron.security.common.BcryptUtil;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.restassured.RestAssured;
|
||||
import io.quarkus.test.security.TestSecurity;
|
||||
import io.restassured.http.ContentType;
|
||||
import io.restassured.response.Response;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.restassured.RestAssured.given;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static io.restassured.RestAssured.when;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@QuarkusTest
|
||||
public class AccountResourceTest
|
||||
{
|
||||
@Inject
|
||||
Database database;
|
||||
|
||||
@Inject
|
||||
AccountRepo accountRepo;
|
||||
|
||||
@AfterEach
|
||||
void afterEach()
|
||||
{
|
||||
database.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInsert()
|
||||
{
|
||||
@ -40,4 +51,35 @@ public class AccountResourceTest
|
||||
|
||||
assertEquals(1, accountRepo.count());
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestSecurity(user = "66609092-6c98-4466-af52-9a3e9d633108")
|
||||
void testRetrieval()
|
||||
{
|
||||
// given
|
||||
database.setup(() -> {
|
||||
AccountEntity account = new AccountEntity()
|
||||
.setId("66609092-6c98-4466-af52-9a3e9d633108")
|
||||
.setFirstname("Andreas")
|
||||
.setLastname("Dinauer")
|
||||
.setEmail("andreas.j.dinauer@gmail.com")
|
||||
.setPassword(BcryptUtil.bcryptHash("pw"));
|
||||
accountRepo.persist(account);
|
||||
});
|
||||
|
||||
// when
|
||||
Response response = when()
|
||||
.get("accounts")
|
||||
.then()
|
||||
.extract()
|
||||
.response();
|
||||
|
||||
// then
|
||||
String body = response.getBody().prettyPrint();
|
||||
assertFalse(body.isEmpty());
|
||||
assertTrue(body.contains("66609092-6c98-4466-af52-9a3e9d633108"));
|
||||
assertTrue(body.contains("Andreas"));
|
||||
assertTrue(body.contains("Dinauer"));
|
||||
assertTrue(body.contains("andreas.j.dinauer@gmail.com"));
|
||||
}
|
||||
}
|
||||
|
||||
67
src/test/java/de/tavolio/session/SessionResourceTest.java
Normal file
67
src/test/java/de/tavolio/session/SessionResourceTest.java
Normal file
@ -0,0 +1,67 @@
|
||||
package de.tavolio.session;
|
||||
|
||||
import de.tavolio.account.AccountEntity;
|
||||
import de.tavolio.account.AccountRepo;
|
||||
import de.tavolio.utils.Database;
|
||||
import io.quarkus.elytron.security.common.BcryptUtil;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.restassured.http.ContentType;
|
||||
import io.restassured.response.Response;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.restassured.RestAssured.given;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@QuarkusTest
|
||||
public class SessionResourceTest
|
||||
{
|
||||
@Inject
|
||||
Database database;
|
||||
|
||||
@Inject
|
||||
AccountRepo accountRepo;
|
||||
|
||||
@AfterEach
|
||||
void afterEach()
|
||||
{
|
||||
database.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGet()
|
||||
{
|
||||
// given
|
||||
database.setup(() -> {
|
||||
AccountEntity accountEntity = AccountEntity.init()
|
||||
.setEmail("andreas.j.dinauer@gmail.com")
|
||||
.setFirstname("Andreas")
|
||||
.setLastname("Dinauer")
|
||||
.setPassword(BcryptUtil.bcryptHash("pw"));
|
||||
accountRepo.persist(accountEntity);
|
||||
});
|
||||
|
||||
String loginRequest = """
|
||||
{
|
||||
"email": "andreas.j.dinauer@gmail.com",
|
||||
"password": "pw"
|
||||
}
|
||||
""";
|
||||
|
||||
// when
|
||||
Response response = given()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(loginRequest)
|
||||
.when()
|
||||
.post("/sessions")
|
||||
.then()
|
||||
.extract()
|
||||
.response();
|
||||
|
||||
// then
|
||||
assertEquals(200, response.statusCode());
|
||||
assertTrue(response.getBody().prettyPrint().startsWith("ey"));
|
||||
}
|
||||
}
|
||||
67
src/test/java/de/tavolio/utils/Database.java
Normal file
67
src/test/java/de/tavolio/utils/Database.java
Normal file
@ -0,0 +1,67 @@
|
||||
package de.tavolio.utils;
|
||||
|
||||
import io.agroal.api.AgroalDataSource;
|
||||
import io.quarkus.narayana.jta.QuarkusTransaction;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@ApplicationScoped
|
||||
public class Database {
|
||||
|
||||
@Inject
|
||||
AgroalDataSource dataSource;
|
||||
|
||||
public void setup(Runnable setup) {
|
||||
QuarkusTransaction.begin();
|
||||
setup.run();
|
||||
QuarkusTransaction.commit();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
forceForeignKeys(false);
|
||||
getAllTables().forEach(this::truncateTable);
|
||||
forceForeignKeys(true);
|
||||
}
|
||||
|
||||
private List<String> getAllTables() {
|
||||
try(Connection connection = dataSource.getConnection()) {
|
||||
ResultSet rs = connection.getMetaData().getTables(null, "public", "%", new String[] {"TABLE"});
|
||||
connection.close();
|
||||
List<String> tables = new LinkedList<>();
|
||||
while(rs.next()) {
|
||||
tables.add(rs.getString(3));
|
||||
}
|
||||
return tables;
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
private void forceForeignKeys(boolean force) {
|
||||
if(force) {
|
||||
execute("SET session_replication_role = replica");
|
||||
} else {
|
||||
execute("SET session_replication_role = DEFAULT");
|
||||
}
|
||||
}
|
||||
|
||||
private void truncateTable(String table) {
|
||||
execute("TRUNCATE TABLE public." + table + " RESTART IDENTITY CASCADE");
|
||||
}
|
||||
|
||||
private void execute(String sql) {
|
||||
try(Connection connection = dataSource.getConnection(); Statement statement = connection.createStatement()) {
|
||||
statement.execute(sql);
|
||||
} catch (SQLException e) {
|
||||
System.out.println(sql + ": Error executing SQL.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user