diff --git a/.github/changelog.yaml b/.github/changelog.yaml new file mode 100644 index 0000000..0a5526e --- /dev/null +++ b/.github/changelog.yaml @@ -0,0 +1,13 @@ +sections: + - title: Major changes + labels: + - "release/super-feature" + - title: Complete changelog + labels: + - "bug" + - "enhancement" + - "dependencies" +template: | + {{ range $section := .Sections }}{{ if $section.Items }}### {{ $section.GetTitle }}{{ range $item := $section.Items }} + * [#{{ $item.GetID }}]({{ $item.GetURL }}) - {{ $item.GetTitle }}{{ end }}{{ end }} + {{ end }} \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..64412da --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: maven + directory: "/" + schedule: + interval: daily + labels: + - dependencies \ No newline at end of file diff --git a/.github/workflows/build-branch.yml b/.github/workflows/build-branch.yml new file mode 100644 index 0000000..7270213 --- /dev/null +++ b/.github/workflows/build-branch.yml @@ -0,0 +1,13 @@ +name: Build Feature Branch + +on: + push: + branches: + - '**' + - '!main' + - '!fix/[0-9]+.[0-9]+.x' + +jobs: + branch: + uses: onecx/ci-quarkus/.github/workflows/build-branch.yml@v1 + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml new file mode 100644 index 0000000..acff155 --- /dev/null +++ b/.github/workflows/build-pr.yml @@ -0,0 +1,9 @@ +name: Build Pull Request + +on: + pull_request: + +jobs: + pr: + uses: onecx/ci-quarkus/.github/workflows/build-pr.yml@v1 + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml new file mode 100644 index 0000000..b04a59a --- /dev/null +++ b/.github/workflows/build-release.yml @@ -0,0 +1,9 @@ +name: Build Release +on: + push: + tags: + - '**' +jobs: + release: + uses: onecx/ci-quarkus/.github/workflows/build-release.yml@v1 + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..efe79be --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,23 @@ +name: Build + +on: + workflow_dispatch: + push: + branches: + - 'main' + - 'fix/[0-9]+.[0-9]+.x' + paths-ignore: + - '.gitignore' + - 'CODEOWNERS' + - 'LICENSE' + - '*.md' + - '*.adoc' + - '*.txt' + - '.all-contributorsrc' + +jobs: + build: + uses: onecx/ci-quarkus/.github/workflows/build.yml@v1 + secrets: inherit + with: + helmEventTargetRepository: onecx/onecx-permission diff --git a/.github/workflows/create-fix-branch.yml b/.github/workflows/create-fix-branch.yml new file mode 100644 index 0000000..92af624 --- /dev/null +++ b/.github/workflows/create-fix-branch.yml @@ -0,0 +1,7 @@ +name: Create Fix Branch +on: + workflow_dispatch: +jobs: + fix: + uses: onecx/ci-common/.github/workflows/create-fix-branch.yml@v1 + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml new file mode 100644 index 0000000..c97eb42 --- /dev/null +++ b/.github/workflows/create-release.yml @@ -0,0 +1,7 @@ +name: Create Release Version +on: + workflow_dispatch: +jobs: + release: + uses: onecx/ci-common/.github/workflows/create-release.yml@v1 + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 0000000..e43d7dc --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,10 @@ +name: Update documentation +on: + push: + branches: [ main ] + paths: + - 'docs/**' +jobs: + release: + uses: onecx/ci-common/.github/workflows/documentation.yml@v1 + secrets: inherit diff --git a/.github/workflows/sonar-pr.yml b/.github/workflows/sonar-pr.yml new file mode 100644 index 0000000..02c3e1e --- /dev/null +++ b/.github/workflows/sonar-pr.yml @@ -0,0 +1,12 @@ +name: Sonar Pull Request + +on: + workflow_run: + workflows: ["Build Pull Request"] + types: + - completed + +jobs: + pr: + uses: onecx/ci-quarkus/.github/workflows/quarkus-pr-sonar.yml@v1 + secrets: inherit \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b2b2d1f --- /dev/null +++ b/.gitignore @@ -0,0 +1,44 @@ +#Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +release.properties +.flattened-pom.xml + +# 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 + +# Plugin directory +/.quarkus/cli/plugins/ + diff --git a/README.md b/README.md index 37fc34f..542678e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ # onecx-permission-svc + OneCx permission service + +## Configuration + +```properties + +``` \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..775eacd --- /dev/null +++ b/pom.xml @@ -0,0 +1,190 @@ + + + + 4.0.0 + + + io.github.onecx + onecx-quarkus3-parent + 0.25.0 + + + onecx-permission-svc + 999-SNAPSHOT + + + + io.github.onecx.quarkus + onecx-core + + + io.github.onecx.quarkus + onecx-tenant + + + + org.tkit.quarkus.lib + tkit-quarkus-rest-context + + + org.tkit.quarkus.lib + tkit-quarkus-jpa-tenant + + + org.tkit.quarkus.lib + tkit-quarkus-jpa + + + org.tkit.quarkus.lib + tkit-quarkus-log-cdi + + + org.tkit.quarkus.lib + tkit-quarkus-log-rs + + + org.tkit.quarkus.lib + tkit-quarkus-log-json + + + org.tkit.quarkus.lib + tkit-quarkus-rest + + + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-liquibase + + + com.github.blagerweij + liquibase-sessionlock + + + io.quarkus + quarkus-smallrye-health + + + io.quarkus + quarkus-micrometer-registry-prometheus + + + io.quarkus + quarkus-hibernate-orm + + + io.quarkus + quarkus-resteasy-reactive + + + io.quarkus + quarkus-resteasy-reactive-jackson + + + io.quarkus + quarkus-jdbc-postgresql + + + io.quarkus + quarkus-smallrye-context-propagation + + + io.quarkus + quarkus-smallrye-openapi + + + io.quarkus + quarkus-hibernate-validator + + + io.quarkus + quarkus-opentelemetry + + + + + org.projectlombok + lombok + + + org.mapstruct + mapstruct + + + + + io.quarkus + quarkus-junit5 + test + + + io.quarkus + quarkus-junit5-mockito + test + + + io.rest-assured + rest-assured + test + + + org.tkit.quarkus.lib + tkit-quarkus-test-db-import + test + + + + + + + org.openapitools + openapi-generator-maven-plugin + + jaxrs-spec + DTO + false + false + false + false + false + true + quarkus + + / + false + true + true + true + true + true + java8 + true + true + false + true + + + + + internal + + generate + + + src/main/openapi/onecx-permission-internal-openapi.yaml + gen.io.github.onecx.permission.rs.internal + gen.io.github.onecx.permission.rs.internal.model + DTO + + + + + + + + diff --git a/src/main/docker/Dockerfile.jvm b/src/main/docker/Dockerfile.jvm new file mode 100644 index 0000000..a554421 --- /dev/null +++ b/src/main/docker/Dockerfile.jvm @@ -0,0 +1,13 @@ +FROM registry.access.redhat.com/ubi9/openjdk-17:1.15 + +ENV LANGUAGE='en_US:en' + +COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/ +COPY --chown=185 target/quarkus-app/*.jar /deployments/ +COPY --chown=185 target/quarkus-app/app/ /deployments/app/ +COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/ + +EXPOSE 8080 +USER 185 +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" \ No newline at end of file diff --git a/src/main/docker/Dockerfile.native b/src/main/docker/Dockerfile.native new file mode 100644 index 0000000..c97f217 --- /dev/null +++ b/src/main/docker/Dockerfile.native @@ -0,0 +1,11 @@ +FROM registry.access.redhat.com/ubi9/ubi-minimal:9.2 +WORKDIR /work/ +RUN chown 1001 /work \ + && chmod "g+rwX" /work \ + && chown 1001:root /work +COPY --chown=1001:root target/*-runner /work/application + +EXPOSE 8080 +USER 1001 + +CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] \ No newline at end of file diff --git a/src/main/helm/Chart.yaml b/src/main/helm/Chart.yaml new file mode 100644 index 0000000..2af1aab --- /dev/null +++ b/src/main/helm/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +name: onecx-permission-svc +version: 0.0.0 +appVersion: 0.0.0 +description: Onecx permission service +keywords: + - permission +sources: + - https://github.com/onecx/onecx-permission-svc +maintainers: + - name: Tkit Developer + email: tkit_dev@1000kit.org +dependencies: + - name: helm-quarkus-app + alias: app + version: ^0 + repository: oci://ghcr.io/onecx/charts \ No newline at end of file diff --git a/src/main/helm/values.yaml b/src/main/helm/values.yaml new file mode 100644 index 0000000..44d921d --- /dev/null +++ b/src/main/helm/values.yaml @@ -0,0 +1,6 @@ +app: + name: svc + image: + repository: "onecx/onecx-permission-svc" + db: + enabled: true \ No newline at end of file diff --git a/src/main/java/io/github/onecx/permission/domain/criteria/PermissionSearchCriteria.java b/src/main/java/io/github/onecx/permission/domain/criteria/PermissionSearchCriteria.java new file mode 100644 index 0000000..254cd09 --- /dev/null +++ b/src/main/java/io/github/onecx/permission/domain/criteria/PermissionSearchCriteria.java @@ -0,0 +1,16 @@ +package io.github.onecx.permission.domain.criteria; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class PermissionSearchCriteria { + + private String appId; + private String name; + private String object; + private String action; + private Integer pageNumber; + private Integer pageSize; +} diff --git a/src/main/java/io/github/onecx/permission/domain/daos/PermissionDAO.java b/src/main/java/io/github/onecx/permission/domain/daos/PermissionDAO.java new file mode 100644 index 0000000..2d548ec --- /dev/null +++ b/src/main/java/io/github/onecx/permission/domain/daos/PermissionDAO.java @@ -0,0 +1,57 @@ +package io.github.onecx.permission.domain.daos; + +import java.util.ArrayList; +import java.util.List; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.persistence.criteria.Predicate; + +import org.tkit.quarkus.jpa.daos.AbstractDAO; +import org.tkit.quarkus.jpa.daos.Page; +import org.tkit.quarkus.jpa.daos.PageResult; +import org.tkit.quarkus.jpa.exceptions.DAOException; +import org.tkit.quarkus.jpa.utils.QueryCriteriaUtil; + +import io.github.onecx.permission.domain.criteria.PermissionSearchCriteria; +import io.github.onecx.permission.domain.models.Permission; +import io.github.onecx.permission.domain.models.Permission_; + +@ApplicationScoped +public class PermissionDAO extends AbstractDAO { + + public PageResult findByCriteria(PermissionSearchCriteria criteria) { + try { + var cb = this.getEntityManager().getCriteriaBuilder(); + var cq = cb.createQuery(Permission.class); + var root = cq.from(Permission.class); + + List predicates = new ArrayList<>(); + + if (criteria.getName() != null && !criteria.getName().isBlank()) { + predicates.add(cb.like(root.get(Permission_.name), QueryCriteriaUtil.wildcard(criteria.getName()))); + } + if (criteria.getAction() != null && !criteria.getAction().isBlank()) { + predicates.add(cb.like(root.get(Permission_.action), QueryCriteriaUtil.wildcard(criteria.getAction()))); + } + if (criteria.getAppId() != null && !criteria.getAppId().isBlank()) { + predicates.add(cb.like(root.get(Permission_.appId), QueryCriteriaUtil.wildcard(criteria.getAppId()))); + } + if (criteria.getObject() != null && !criteria.getObject().isBlank()) { + predicates.add(cb.like(root.get(Permission_.object), QueryCriteriaUtil.wildcard(criteria.getObject()))); + } + + if (!predicates.isEmpty()) { + cq.where(predicates.toArray(new Predicate[] {})); + } + + return createPageQuery(cq, Page.of(criteria.getPageNumber(), criteria.getPageSize())).getPageResult(); + } catch (Exception ex) { + throw new DAOException(ErrorKeys.ERROR_FIND_PERMISSION_BY_CRITERIA, ex); + } + } + + public enum ErrorKeys { + + ERROR_FIND_PERMISSION_BY_CRITERIA; + } +} diff --git a/src/main/java/io/github/onecx/permission/domain/models/Permission.java b/src/main/java/io/github/onecx/permission/domain/models/Permission.java new file mode 100644 index 0000000..2c0debf --- /dev/null +++ b/src/main/java/io/github/onecx/permission/domain/models/Permission.java @@ -0,0 +1,49 @@ +package io.github.onecx.permission.domain.models; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; + +import org.tkit.quarkus.jpa.models.TraceableEntity; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Entity +@Table(name = "PERMISSION", uniqueConstraints = { + @UniqueConstraint(name = "PERMISSION_KEY", columnNames = { "APP_ID", "OBJECT", "ACTION" }) +}) +@SuppressWarnings("squid:S2160") +public class Permission extends TraceableEntity { + + @Column(name = "APP_ID") + private String appId; + + /** + * The permission action. + */ + @Column(name = "ACTION") + private String action; + + /** + * The permission object. + */ + @Column(name = "OBJECT") + private String object; + + /** + * The permission name. + */ + @Column(name = "NAME") + private String name; + + /** + * The permission description. + */ + @Column(name = "DESCRIPTION") + private String description; + +} diff --git a/src/main/java/io/github/onecx/permission/rs/internal/controllers/PermissionRestController.java b/src/main/java/io/github/onecx/permission/rs/internal/controllers/PermissionRestController.java new file mode 100644 index 0000000..fe73a2a --- /dev/null +++ b/src/main/java/io/github/onecx/permission/rs/internal/controllers/PermissionRestController.java @@ -0,0 +1,43 @@ +package io.github.onecx.permission.rs.internal.controllers; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.validation.ConstraintViolationException; +import jakarta.ws.rs.core.Response; + +import org.jboss.resteasy.reactive.RestResponse; +import org.jboss.resteasy.reactive.server.ServerExceptionMapper; +import org.tkit.quarkus.log.cdi.LogService; + +import gen.io.github.onecx.permission.rs.internal.PermissionInternalApi; +import gen.io.github.onecx.permission.rs.internal.model.PermissionSearchCriteriaDTO; +import gen.io.github.onecx.permission.rs.internal.model.ProblemDetailResponseDTO; +import io.github.onecx.permission.domain.daos.PermissionDAO; +import io.github.onecx.permission.rs.internal.mappers.ExceptionMapper; +import io.github.onecx.permission.rs.internal.mappers.PermissionMapper; + +@LogService +@ApplicationScoped +public class PermissionRestController implements PermissionInternalApi { + + @Inject + ExceptionMapper exceptionMapper; + + @Inject + PermissionMapper mapper; + + @Inject + PermissionDAO dao; + + @Override + public Response searchPermissions(PermissionSearchCriteriaDTO permissionSearchCriteriaDTO) { + var criteria = mapper.map(permissionSearchCriteriaDTO); + var result = dao.findByCriteria(criteria); + return Response.ok(mapper.map(result)).build(); + } + + @ServerExceptionMapper + public RestResponse constraint(ConstraintViolationException ex) { + return exceptionMapper.constraint(ex); + } +} diff --git a/src/main/java/io/github/onecx/permission/rs/internal/log/InternalLogParam.java b/src/main/java/io/github/onecx/permission/rs/internal/log/InternalLogParam.java new file mode 100644 index 0000000..9cddc46 --- /dev/null +++ b/src/main/java/io/github/onecx/permission/rs/internal/log/InternalLogParam.java @@ -0,0 +1,23 @@ +package io.github.onecx.permission.rs.internal.log; + +import java.util.List; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.tkit.quarkus.log.cdi.LogParam; + +import gen.io.github.onecx.permission.rs.internal.model.PermissionSearchCriteriaDTO; + +@ApplicationScoped +public class InternalLogParam implements LogParam { + + @Override + public List getClasses() { + return List.of( + item(10, PermissionSearchCriteriaDTO.class, x -> { + PermissionSearchCriteriaDTO d = (PermissionSearchCriteriaDTO) x; + return PermissionSearchCriteriaDTO.class.getSimpleName() + "[" + d.getPageNumber() + "," + d.getPageSize() + + "]"; + })); + } +} diff --git a/src/main/java/io/github/onecx/permission/rs/internal/mappers/ExceptionMapper.java b/src/main/java/io/github/onecx/permission/rs/internal/mappers/ExceptionMapper.java new file mode 100644 index 0000000..9e3fe83 --- /dev/null +++ b/src/main/java/io/github/onecx/permission/rs/internal/mappers/ExceptionMapper.java @@ -0,0 +1,64 @@ +package io.github.onecx.permission.rs.internal.mappers; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.Path; +import jakarta.ws.rs.core.Response; + +import org.jboss.resteasy.reactive.RestResponse; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper; + +import gen.io.github.onecx.permission.rs.internal.model.ProblemDetailInvalidParamDTO; +import gen.io.github.onecx.permission.rs.internal.model.ProblemDetailParamDTO; +import gen.io.github.onecx.permission.rs.internal.model.ProblemDetailResponseDTO; + +@Mapper(uses = { OffsetDateTimeMapper.class }) +public abstract class ExceptionMapper { + + public RestResponse constraint(ConstraintViolationException ex) { + var dto = exception(ErrorKeys.CONSTRAINT_VIOLATIONS.name(), ex.getMessage()); + dto.setInvalidParams(createErrorValidationResponse(ex.getConstraintViolations())); + return RestResponse.status(Response.Status.BAD_REQUEST, dto); + } + + @Mapping(target = "removeParamsItem", ignore = true) + @Mapping(target = "params", ignore = true) + @Mapping(target = "invalidParams", ignore = true) + @Mapping(target = "removeInvalidParamsItem", ignore = true) + public abstract ProblemDetailResponseDTO exception(String errorCode, String detail); + + public List map(Map params) { + if (params == null) { + return List.of(); + } + return params.entrySet().stream().map(e -> { + var item = new ProblemDetailParamDTO(); + item.setKey(e.getKey()); + if (e.getValue() != null) { + item.setValue(e.getValue().toString()); + } + return item; + }).toList(); + } + + public abstract List createErrorValidationResponse( + Set> constraintViolation); + + @Mapping(target = "name", source = "propertyPath") + @Mapping(target = "message", source = "message") + public abstract ProblemDetailInvalidParamDTO createError(ConstraintViolation constraintViolation); + + public String mapPath(Path path) { + return path.toString(); + } + + public enum ErrorKeys { + CONSTRAINT_VIOLATIONS; + } +} diff --git a/src/main/java/io/github/onecx/permission/rs/internal/mappers/PermissionMapper.java b/src/main/java/io/github/onecx/permission/rs/internal/mappers/PermissionMapper.java new file mode 100644 index 0000000..fc576c5 --- /dev/null +++ b/src/main/java/io/github/onecx/permission/rs/internal/mappers/PermissionMapper.java @@ -0,0 +1,24 @@ +package io.github.onecx.permission.rs.internal.mappers; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.tkit.quarkus.jpa.daos.PageResult; +import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper; + +import gen.io.github.onecx.permission.rs.internal.model.PermissionDTO; +import gen.io.github.onecx.permission.rs.internal.model.PermissionPageResultDTO; +import gen.io.github.onecx.permission.rs.internal.model.PermissionSearchCriteriaDTO; +import io.github.onecx.permission.domain.criteria.PermissionSearchCriteria; +import io.github.onecx.permission.domain.models.Permission; + +@Mapper(uses = { OffsetDateTimeMapper.class }) +public interface PermissionMapper { + + PermissionSearchCriteria map(PermissionSearchCriteriaDTO dto); + + @Mapping(target = "removeStreamItem", ignore = true) + PermissionPageResultDTO map(PageResult page); + + @Mapping(target = "_object", source = "object") + PermissionDTO map(Permission data); +} diff --git a/src/main/openapi/onecx-permission-internal-openapi.yaml b/src/main/openapi/onecx-permission-internal-openapi.yaml new file mode 100644 index 0000000..a3a374c --- /dev/null +++ b/src/main/openapi/onecx-permission-internal-openapi.yaml @@ -0,0 +1,139 @@ +--- +openapi: 3.0.3 +info: + title: onecx-permission internal service + version: 1.0.0 +servers: + - url: "http://onecx-permission-svc:8080" +tags: + - name: permissionInternal +paths: + /internal/permissions/search: + post: + tags: + - permissionInternal + description: Search for permissions + operationId: searchPermissions + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PermissionSearchCriteria' + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PermissionPageResult' + "400": + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetailResponse' +components: + schemas: + PermissionSearchCriteria: + type: object + properties: + appId: + type: string + name: + type: string + object: + type: string + action: + type: string + pageNumber: + format: int32 + description: The number of page. + default: 0 + type: integer + pageSize: + format: int32 + description: The size of page + default: 10 + type: integer + PermissionPageResult: + type: object + properties: + totalElements: + format: int64 + description: The total elements in the resource. + type: integer + number: + format: int32 + type: integer + size: + format: int32 + type: integer + totalPages: + format: int64 + type: integer + stream: + type: array + items: + $ref: '#/components/schemas/Permission' + Permission: + type: object + properties: + modificationCount: + format: int32 + type: integer + creationDate: + $ref: '#/components/schemas/OffsetDateTime' + creationUser: + type: string + modificationDate: + $ref: '#/components/schemas/OffsetDateTime' + modificationUser: + type: string + id: + type: string + appId: + type: string + name: + type: string + object: + type: string + action: + type: string + description: + type: string + OffsetDateTime: + format: date-time + type: string + example: 2022-03-10T12:15:50-04:00 + ProblemDetailResponse: + type: object + properties: + errorCode: + type: string + detail: + type: string + params: + type: array + items: + $ref: '#/components/schemas/ProblemDetailParam' + invalidParams: + type: array + items: + $ref: '#/components/schemas/ProblemDetailInvalidParam' + ProblemDetailParam: + type: object + properties: + key: + type: string + value: + type: string + ProblemDetailInvalidParam: + type: object + properties: + name: + type: string + message: + type: string \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..4cfda36 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,44 @@ +# DEFAULT +quarkus.datasource.db-kind=postgresql +quarkus.datasource.jdbc.max-size=30 +quarkus.datasource.jdbc.min-size=10 + +quarkus.hibernate-orm.database.generation=validate +quarkus.hibernate-orm.multitenant=DISCRIMINATOR +quarkus.liquibase.migrate-at-start=true +quarkus.liquibase.validate-on-migrate=true + +# enable or disable multi-tenancy support +tkit.rs.context.tenant-id.enabled=true + +# PROD +%prod.quarkus.datasource.jdbc.url=${DB_URL:jdbc:postgresql://postgresdb:5432/onecx-permission?sslmode=disable} +%prod.quarkus.datasource.username=${DB_USER:onecx-permission} +%prod.quarkus.datasource.password=${DB_PWD:onecx-permission} +# DEV +%dev.tkit.rs.context.tenant-id.enabled=true +%dev.tkit.rs.context.tenant-id.mock.enabled=true +%dev.tkit.rs.context.tenant-id.mock.default-tenant=test +%dev.tkit.rs.context.tenant-id.mock.data.org1=tenant100 + +# TEST +%test.tkit.rs.context.tenant-id.enabled=true +%test.tkit.rs.context.tenant-id.mock.enabled=true +%test.tkit.rs.context.tenant-id.mock.default-tenant=default +%test.tkit.rs.context.tenant-id.mock.claim-org-id=orgId +%test.tkit.rs.context.tenant-id.mock.token-header-param=apm-principal-token +%test.tkit.rs.context.tenant-id.mock.data.org1=tenant-100 +%test.tkit.rs.context.tenant-id.mock.data.org2=tenant-200 + +# TEST-IT +quarkus.test.integration-test-profile=test-it +%test-it.tkit.log.json.enabled=false +%test-it.tkit.rs.context.tenant-id.mock.enabled=true +%test-it.tkit.rs.context.tenant-id.mock.default-tenant=default +%test-it.tkit.rs.context.tenant-id.mock.claim-org-id=orgId +%test-it.tkit.rs.context.tenant-id.mock.token-header-param=apm-principal-token +%test-it.tkit.rs.context.tenant-id.mock.data.org1=tenant-100 +%test-it.tkit.rs.context.tenant-id.mock.data.org2=tenant-200 + +# PIPE CONFIG + diff --git a/src/main/resources/db/changeLog.xml b/src/main/resources/db/changeLog.xml new file mode 100644 index 0000000..44c297b --- /dev/null +++ b/src/main/resources/db/changeLog.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/db/v1/2024-01-04-create-tables.xml b/src/main/resources/db/v1/2024-01-04-create-tables.xml new file mode 100644 index 0000000..fee0490 --- /dev/null +++ b/src/main/resources/db/v1/2024-01-04-create-tables.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/io/github/onecx/permission/domain/daos/PermissionDAOTest.java b/src/test/java/io/github/onecx/permission/domain/daos/PermissionDAOTest.java new file mode 100644 index 0000000..c3b665c --- /dev/null +++ b/src/test/java/io/github/onecx/permission/domain/daos/PermissionDAOTest.java @@ -0,0 +1,41 @@ +package io.github.onecx.permission.domain.daos; + +import jakarta.inject.Inject; +import jakarta.persistence.EntityManager; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.mockito.Mockito; +import org.tkit.quarkus.jpa.exceptions.DAOException; + +import io.github.onecx.permission.test.AbstractTest; +import io.quarkus.test.InjectMock; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class PermissionDAOTest extends AbstractTest { + + @Inject + PermissionDAO dao; + + @InjectMock + EntityManager em; + + @BeforeEach + void beforeAll() { + Mockito.when(em.getCriteriaBuilder()).thenThrow(new RuntimeException("Test technical error exception")); + } + + @Test + void methodExceptionTests() { + methodExceptionTests(() -> dao.findByCriteria(null), + PermissionDAO.ErrorKeys.ERROR_FIND_PERMISSION_BY_CRITERIA); + } + + void methodExceptionTests(Executable fn, Enum key) { + var exc = Assertions.assertThrows(DAOException.class, fn); + Assertions.assertEquals(key, exc.key); + } +} diff --git a/src/test/java/io/github/onecx/permission/rs/internal/controllers/PermissionRestControllerExceptionTest.java b/src/test/java/io/github/onecx/permission/rs/internal/controllers/PermissionRestControllerExceptionTest.java new file mode 100644 index 0000000..2b304f9 --- /dev/null +++ b/src/test/java/io/github/onecx/permission/rs/internal/controllers/PermissionRestControllerExceptionTest.java @@ -0,0 +1,53 @@ +package io.github.onecx.permission.rs.internal.controllers; + +import static io.restassured.RestAssured.given; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.jboss.resteasy.reactive.RestResponse.Status.INTERNAL_SERVER_ERROR; +import static org.mockito.ArgumentMatchers.any; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.tkit.quarkus.jpa.exceptions.DAOException; + +import gen.io.github.onecx.permission.rs.internal.model.PermissionSearchCriteriaDTO; +import io.github.onecx.permission.domain.daos.PermissionDAO; +import io.github.onecx.permission.test.AbstractTest; +import io.quarkus.test.InjectMock; +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +@TestHTTPEndpoint(PermissionRestController.class) +class PermissionRestControllerExceptionTest extends AbstractTest { + + @InjectMock + PermissionDAO dao; + + @BeforeEach + void beforeAll() { + Mockito.when(dao.findByCriteria(any())) + .thenThrow(new RuntimeException("Test technical error exception")) + .thenThrow(new DAOException(PermissionDAO.ErrorKeys.ERROR_FIND_PERMISSION_BY_CRITERIA, + new RuntimeException("Test"))); + } + + @Test + void exceptionTest() { + var criteria = new PermissionSearchCriteriaDTO(); + given() + .contentType(APPLICATION_JSON) + .body(criteria) + .post() + .then() + .statusCode(INTERNAL_SERVER_ERROR.getStatusCode()); + + given() + .contentType(APPLICATION_JSON) + .body(criteria) + .post() + .then() + .statusCode(INTERNAL_SERVER_ERROR.getStatusCode()); + + } +} diff --git a/src/test/java/io/github/onecx/permission/rs/internal/controllers/PermissionRestControllerTest.java b/src/test/java/io/github/onecx/permission/rs/internal/controllers/PermissionRestControllerTest.java new file mode 100644 index 0000000..72db785 --- /dev/null +++ b/src/test/java/io/github/onecx/permission/rs/internal/controllers/PermissionRestControllerTest.java @@ -0,0 +1,101 @@ +package io.github.onecx.permission.rs.internal.controllers; + +import static io.restassured.RestAssured.given; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.assertj.core.api.Assertions.assertThat; +import static org.jboss.resteasy.reactive.RestResponse.Status.BAD_REQUEST; +import static org.jboss.resteasy.reactive.RestResponse.Status.OK; + +import org.junit.jupiter.api.Test; +import org.tkit.quarkus.test.WithDBData; + +import gen.io.github.onecx.permission.rs.internal.model.PermissionPageResultDTO; +import gen.io.github.onecx.permission.rs.internal.model.PermissionSearchCriteriaDTO; +import gen.io.github.onecx.permission.rs.internal.model.ProblemDetailResponseDTO; +import io.github.onecx.permission.rs.internal.mappers.ExceptionMapper; +import io.github.onecx.permission.test.AbstractTest; +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +@TestHTTPEndpoint(PermissionRestController.class) +@WithDBData(value = "data/test-internal.xml", deleteBeforeInsert = true, deleteAfterTest = true, rinseAndRepeat = true) +class PermissionRestControllerTest extends AbstractTest { + + @Test + void searchTest() { + var criteria = new PermissionSearchCriteriaDTO(); + + var data = given() + .contentType(APPLICATION_JSON) + .body(criteria) + .post() + .then() + .statusCode(OK.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract() + .as(PermissionPageResultDTO.class); + + assertThat(data).isNotNull(); + assertThat(data.getTotalElements()).isEqualTo(7); + assertThat(data.getStream()).isNotNull().hasSize(7); + + criteria.setAppId(" "); + criteria.setName(" "); + criteria.setObject(" "); + criteria.setAction(" "); + + data = given() + .contentType(APPLICATION_JSON) + .body(criteria) + .post() + .then() + .statusCode(OK.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract() + .as(PermissionPageResultDTO.class); + + assertThat(data).isNotNull(); + assertThat(data.getTotalElements()).isEqualTo(7); + assertThat(data.getStream()).isNotNull().hasSize(7); + } + + @Test + void searchCriteriaTest() { + var criteria = new PermissionSearchCriteriaDTO(); + criteria.setAppId("app1"); + criteria.setName("n1"); + criteria.setObject("o1"); + criteria.setAction("a1"); + + var data = given() + .contentType(APPLICATION_JSON) + .body(criteria) + .post() + .then() + .statusCode(OK.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract() + .as(PermissionPageResultDTO.class); + + assertThat(data).isNotNull(); + assertThat(data.getTotalElements()).isEqualTo(1); + assertThat(data.getStream()).isNotNull().hasSize(1); + } + + @Test + void searchNoBodyTest() { + var exception = given() + .contentType(APPLICATION_JSON) + .post() + .then() + .statusCode(BAD_REQUEST.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract() + .as(ProblemDetailResponseDTO.class); + + assertThat(exception).isNotNull(); + assertThat(exception.getErrorCode()).isEqualTo(ExceptionMapper.ErrorKeys.CONSTRAINT_VIOLATIONS.name()); + assertThat(exception.getDetail()).isEqualTo("searchPermissions.permissionSearchCriteriaDTO: must not be null"); + } +} diff --git a/src/test/java/io/github/onecx/permission/rs/internal/controllers/PermissionRestControllerTestIT.java b/src/test/java/io/github/onecx/permission/rs/internal/controllers/PermissionRestControllerTestIT.java new file mode 100644 index 0000000..199d388 --- /dev/null +++ b/src/test/java/io/github/onecx/permission/rs/internal/controllers/PermissionRestControllerTestIT.java @@ -0,0 +1,8 @@ +package io.github.onecx.permission.rs.internal.controllers; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +class PermissionRestControllerTestIT extends PermissionRestControllerTest { + +} diff --git a/src/test/java/io/github/onecx/permission/test/AbstractTest.java b/src/test/java/io/github/onecx/permission/test/AbstractTest.java new file mode 100644 index 0000000..43defa9 --- /dev/null +++ b/src/test/java/io/github/onecx/permission/test/AbstractTest.java @@ -0,0 +1,25 @@ +package io.github.onecx.permission.test; + +import static com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS; +import static io.restassured.RestAssured.config; +import static io.restassured.config.ObjectMapperConfig.objectMapperConfig; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +import io.restassured.config.RestAssuredConfig; + +@SuppressWarnings("java:S2187") +public class AbstractTest { + + static { + config = RestAssuredConfig.config().objectMapperConfig( + objectMapperConfig().jackson2ObjectMapperFactory( + (cls, charset) -> { + var objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.configure(WRITE_DATES_AS_TIMESTAMPS, false); + return objectMapper; + })); + } +} diff --git a/src/test/resources/data/test-internal.xml b/src/test/resources/data/test-internal.xml new file mode 100644 index 0000000..3fc27d3 --- /dev/null +++ b/src/test/resources/data/test-internal.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file