Compare commits

...

7 Commits

353 changed files with 42847 additions and 10470 deletions

3
.gitignore vendored
View File

@ -62,3 +62,6 @@ __pycache__
back/env_dev/data/ back/env_dev/data/
*.sql *.sql
public_key* public_key*
back/env_dev/data
front/tsconfig.tsbuildinfo
back/pom.xml.versionsBackup

View File

@ -3,22 +3,30 @@
## buyilding-end install applications: ## buyilding-end install applications:
## ##
###################################################################################### ######################################################################################
FROM archlinux:base-devel AS builder FROM archlinux:base-devel AS common
# update system # update system
RUN pacman -Syu --noconfirm && pacman-db-upgrade \ RUN pacman -Syu --noconfirm && pacman-db-upgrade \
&& pacman -S --noconfirm jdk-openjdk maven pnpm \ && pacman -S --noconfirm jdk-openjdk wget\
&& pacman -Scc --noconfirm && pacman -Scc --noconfirm
ENV PATH /workspace/node_modules/.bin:$PATH WORKDIR /tmp
WORKDIR /workspace
FROM common AS builder
# update system
RUN pacman -Syu --noconfirm && pacman-db-upgrade \
&& pacman -S --noconfirm maven npm pnpm \
&& pacman -Scc --noconfirm
ENV PATH /tmp/node_modules/.bin:$PATH
###################################################################################### ######################################################################################
## ##
## Build back: ## Build back:
## ##
###################################################################################### ######################################################################################
FROM builder AS buildBack FROM builder AS build_back
COPY back/pom.xml back/Formatter.xml ./ COPY back/pom.xml ./
COPY back/Formatter.xml ./
COPY back/src ./src/ COPY back/src ./src/
RUN mvn clean compile assembly:single RUN mvn clean compile assembly:single
@ -27,27 +35,43 @@ RUN mvn clean compile assembly:single
## Build front: ## Build front:
## ##
###################################################################################### ######################################################################################
FROM builder AS buildFront FROM builder AS dependency_front
RUN echo "@kangaroo-and-rabbit:registry=https://gitea.atria-soft.org/api/packages/kangaroo-and-rabbit/npm/" > /root/.npmrc RUN echo "@kangaroo-and-rabbit:registry=https://gitea.atria-soft.org/api/packages/kangaroo-and-rabbit/npm/" > /root/.npmrc
ADD front/package.json \ ADD front/package.json \
front/karma.conf.js \ front/pnpm-lock.yaml \
front/protractor.conf.js \
./ ./
ADD front/src/theme ./src/theme
# install and cache app dependencies # install and cache app dependencies
RUN pnpm install RUN pnpm install --prod=false
ADD front/e2e \ ###############################################################
front/tsconfig.json \ ## Install sources
front/tslint.json \ ###############################################################
front/angular.json \ FROM dependency_front AS load_sources_front
./
ADD front/src ./src
# generate build # JUST to get the version of the application and his sha...
RUN ng build --output-path=dist --configuration=production --base-href=/karso/ --deploy-url=/karso/ karso COPY \
front/tsconfig.json \
front/tsconfig.node.json \
front/vite.config.mts \
front/index.html \
./
COPY front/public ./public
COPY front/src ./src
#We are not in prod mode ==> we need to overwrite the production env.
ARG env=front/.env.production
COPY ${env} .env
###############################################################
## Build the sources
###############################################################
FROM load_sources_front AS build_front
# build in bundle mode all the application
RUN pnpm static:build
###################################################################################### ######################################################################################
## ##
@ -55,21 +79,31 @@ RUN ng build --output-path=dist --configuration=production --base-href=/karso/ -
## ##
###################################################################################### ######################################################################################
FROM bellsoft/liberica-openjdk-alpine:latest #FROM bellsoft/liberica-openjdk-alpine:latest
# add wget to manage the health check... ## add wget to manage the health check...
RUN apk add --no-cache wget #RUN apk add --no-cache wget
FROM common
ENV LANG=C.UTF-8 #FROM archlinux:base
#RUN pacman -Syu --noconfirm && pacman-db-upgrade
## install package
#RUN pacman -S --noconfirm jdk-openjdk wget
## intall npm
#RUN pacman -S --noconfirm npm
## clean all the caches Need only on the release environment
#RUN pacman -Scc --noconfirm
COPY --from=buildBack /workspace/out/maven/*.jar /application/application.jar ENV LANG C.UTF-8
COPY --from=buildFront /workspace/dist /application/front/
COPY --from=build_back /tmp/out/maven/*.jar /application/application.jar
COPY --from=build_front /tmp/dist /application/front/
WORKDIR /application/ WORKDIR /application/
EXPOSE 80 EXPOSE 80
# To verify health-check: docker inspect --format "{{json .State.Health }}" YOUR_SERVICE_NAME | jq # To verify health-check: docker inspect --format "{{json .State.Health }}" YOUR_SERVICE_NAME | jq
HEALTHCHECK --start-period=30s --start-interval=5s --interval=30s --timeout=5s --retries=10 \ HEALTHCHECK --start-period=10s --start-interval=2s --interval=30s --timeout=5s --retries=10 \
CMD wget --no-verbose --tries=1 --spider http://localhost:80/karso/api/health_check || exit 1 CMD wget --no-verbose --tries=1 --spider http://localhost:80/api/health_check || exit 1
CMD ["java", "-Xms64M", "-Xmx1G", "-cp", "/application/application.jar", "org.kar.karso.WebLauncher"] CMD ["java", "-Xms64M", "-Xmx1G", "-cp", "/application/application.jar", "org.kar.karso.WebLauncher"]

183
README.md
View File

@ -1,103 +1,128 @@
Karauth Karso
======= =====
This repository manage 3 elements: **K**angaroo **A**nd **R**abbit (S)SO is a simple SSO interface that manage multiple application authentication.
- The SSO backend: jersey REST server
- The SSO front-end: Angular login interface (KARSO)
- The adminitrator of the SSO: Angular adminitration interface of the SSO (KARSO)
To build the docker image:
-------------------------- Run in local:
=============
Start tools
-----------
Start the server basic interfaces: (DB(mySQL), Adminer)
```{.bash} ```{.bash}
gitea.atria-soft.org/kangaroo-and-rabbit/karso:latest # start the Bdd interface (no big data > 50Mo)
docker compose -f env_dev/docker-compose.yaml up -d
``` ```
MySql manage multiple users: Start the Back-end:
---------------------------- -------------------
It is better to not use root user and mange user for each service. backend is developed in JAVA
Add a new user The first step is configuring your JAVA version (or select the JVM with the OS)
```sql ```bash
CREATE USER 'karso'@'%' IDENTIFIED BY 'base_db_password'; export PATH=$(ls -d --color=never /usr/lib/jvm/java-2*-openjdk)/bin:$PATH
GRANT ALL PRIVILEGES ON `karso`.* TO 'karso'@'%';
FLUSH PRIVILEGES;
``` ```
> **_Note_** the `base_db_password` with the production password. this one is for development environment
Install the dependency:
```bash
mvn install
```
To start the service Run the test
```bash
mvn test
```
Install it for external use
```bash
mvn install
```
Execute the local server:
```bash
mvn exec:java@dev-mode
```
Start the Front-end:
-------------------- --------------------
```{.bash} backend is developed in JAVA
docker-compose up -d ```bash
cd front
pnpm install
pnpm dev
``` ```
**Note:** you can manage a single Docker interface to manage all the KAR engine Display the result:
-------------------
docker login gitea.atria-soft.org [show the webpage: http://localhost:4200](http://localhost:4200)
docker pull archlinux:base-devel
docker pull bellsoft/liberica-openjdk-alpine:latest
docker build -t gitea.atria-soft.org/kangaroo-and-rabbit/karso:latest .
docker push gitea.atria-soft.org/kangaroo-and-rabbit/karso:latest
npx playwright test
Runs the end-to-end tests.
npx playwright test --project=firefox
Runs the tests only on Desktop Chrome.
npx playwright test example
Runs the tests in a specific file.
npx playwright test --debug
Runs the tests in debug mode.
npx playwright codegen
Auto generate tests with Codegen.
We suggest that you begin by typing:
npx playwright test
Action a faire d'urgence:
- Mettre un jenkins en place
- dev sur develop et non master
- concevoir un système de migration et d'initialisation.
- ajouter une interface a sqlWrapper pour supporter sqlite ==> plus facile pour les tests
- mettre des test unitaire sur les API REST:
- securité des interfaces
- viole des token
- Fin de validité d'un tocken
- Addaque d-DOS
- Publier toutes les semaine une version a jour
- faire de la documentation pour l'aide
- revoir l'interface des paramètre pour la ganérisé
- Mettre en place une meilleur page d'acceuil
- mettre en config le nom du site
- faire un tool qui permet de savoir si le serveur est UP et si ce n'est pas le cas, bloquer l'affichage
- mettre en place les coors pour les pages d'administration ==> ca vas ètre coton...
- améliorer la gestion et l'affichages des erreur
- mettre en place des pop-up de détection d'erreur
- mise en place d'envoie d'email pour faire une autentification plus sérieuse.
- mettre un place un back-end LDAP
Some other dev tools:
=====================
Format code:
------------
```bash ```bash
export PATH=$(ls -d --color=never /usr/lib/jvm/java-2*-openjdk)/bin:$PATH export PATH=$(ls -d --color=never /usr/lib/jvm/java-2*-openjdk)/bin:$PATH
mvn formatter:format mvn formatter:format
mvn test mvn test
``` ```
Tools in production mode
========================
Changing the Log Level
----------------------
In a production environment, you can adjust the log level to help diagnose bugs more effectively.
The available log levels are:
| **Log Level Tag** | **org.kar.karso** | **org.kar.archidata** | **other** |
| ----------------- | ------------------- | --------------------- | --------- |
| `prod` | INFO | INFO | INFO |
| `prod-debug` | DEBUG | INFO | INFO |
| `prod-trace` | TRACE | DEBUG | INFO |
| `prod-trace-full` | TRACE | TRACE | INFO |
| `dev` | TRACE | DEBUG | INFO |
Manual set in production:
=========================
Connect on the registry
------------------------
To log-in and log-out from the registry:
```bash
export REGISTRY_ADDRESS=gitea.atria-soft.org
docker login -u <<YOUR_USER_NAME>> ${REGISTRY_ADDRESS}
docker logout ${REGISTRY_ADDRESS}
```
pull the root image of dockers
------------------------------
```bash
docker pull archlinux:base-devel
docker pull bellsoft/liberica-openjdk-alpine:latest
```
Create the version
------------------
Execute in the local folder: (use ```dev``` for development and ```latest``` for production release)
```bash
export TAG_DOCKER=latest
export REGISTRY_ADDRESS=gitea.atria-soft.org
docker build -t ${REGISTRY_ADDRESS}/kangaroo-and-rabbit/karso:${TAG_DOCKER} .
docker push ${REGISTRY_ADDRESS}/kangaroo-and-rabbit/karso:${TAG_DOCKER}
```

View File

@ -11,12 +11,12 @@
</arguments> </arguments>
</buildCommand> </buildCommand>
<buildCommand> <buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name> <name>edu.umd.cs.findbugs.plugin.eclipse.findbugsBuilder</name>
<arguments> <arguments>
</arguments> </arguments>
</buildCommand> </buildCommand>
<buildCommand> <buildCommand>
<name>edu.umd.cs.findbugs.plugin.eclipse.findbugsBuilder</name> <name>org.eclipse.m2e.core.maven2Builder</name>
<arguments> <arguments>
</arguments> </arguments>
</buildCommand> </buildCommand>

View File

@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.kar</groupId> <groupId>org.kar</groupId>
<artifactId>karso</artifactId> <artifactId>karso</artifactId>
<version>0.8.0</version> <version>0.8.0-SNAPSHOT</version>
<properties> <properties>
<maven.compiler.version>3.1</maven.compiler.version> <maven.compiler.version>3.1</maven.compiler.version>
<maven.compiler.source>21</maven.compiler.source> <maven.compiler.source>21</maven.compiler.source>
@ -20,17 +20,29 @@
<dependency> <dependency>
<groupId>kangaroo-and-rabbit</groupId> <groupId>kangaroo-and-rabbit</groupId>
<artifactId>archidata</artifactId> <artifactId>archidata</artifactId>
<version>0.11.0</version> <version>0.23.6</version>
</dependency> </dependency>
<!-- Loopback of logger JDK logging API to SLF4J -->
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId> <artifactId>jul-to-slf4j</artifactId>
<version>2.1.0-alpha1</version> <version>2.0.9</version>
</dependency>
<!-- generic logger of SLF4J to console (in color) -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.11</version>
</dependency>
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>2.12.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.datatype</groupId> <groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId> <artifactId>jackson-datatype-jsr310</artifactId>
<version>2.17.1</version> <version>2.18.0-rc1</version>
</dependency> </dependency>
<!-- <!--
************************************************************ ************************************************************
@ -40,15 +52,25 @@
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId> <artifactId>junit-jupiter-api</artifactId>
<version>5.11.0-M2</version> <version>5.11.0</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId> <artifactId>junit-jupiter-engine</artifactId>
<version>5.11.0-M2</version> <version>5.11.0</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<version>2.24.1</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.5.0</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
<sourceDirectory>src</sourceDirectory> <sourceDirectory>src</sourceDirectory>
@ -166,6 +188,23 @@
<nohelp>true</nohelp> <nohelp>true</nohelp>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>exec-application</id>
<phase>package</phase>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>org.kar.karso.WebLauncher</mainClass>
</configuration>
</plugin>
<!-- Check the style of the code --> <!-- Check the style of the code -->
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
@ -207,20 +246,20 @@
</executions> </executions>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.codehaus.mojo</groupId> <groupId>com.github.spotbugs</groupId>
<artifactId>exec-maven-plugin</artifactId> <artifactId>spotbugs-maven-plugin</artifactId>
<version>3.1.0</version> <version>4.8.5.0</version>
<executions>
<execution>
<id>exec-application</id>
<phase>package</phase>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration> <configuration>
<mainClass>org.kar.karso.WebLauncher</mainClass> <includeFilterFile>spotbugs-security-include.xml</includeFilterFile>
<excludeFilterFile>spotbugs-security-exclude.xml</excludeFilterFile>
<!--<plugins>
<plugin>
<groupId>com.h3xstream.findsecbugs</groupId>
<artifactId>findsecbugs-plugin</artifactId>
<version>1.12.0</version>
</plugin>
</plugins>
-->
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>

View File

@ -0,0 +1,17 @@
package org.kar.karso;
import org.kar.karso.migration.Initialization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class GenerateApi {
private final static Logger LOGGER = LoggerFactory.getLogger(GenerateApi.class);
private GenerateApi() {}
public static void main(final String[] args) throws Exception {
LOGGER.info("Generate API");
Initialization.generateObjects();
LOGGER.info("STOP the REST server.");
}
}

View File

@ -7,18 +7,18 @@ import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.jackson.JacksonFeature; import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.media.multipart.MultiPartFeature; import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.server.ResourceConfig;
import org.kar.archidata.GlobalConfiguration;
import org.kar.archidata.UpdateJwtPublicKey; import org.kar.archidata.UpdateJwtPublicKey;
import org.kar.archidata.api.DataResource; import org.kar.archidata.api.DataResource;
import org.kar.archidata.backup.BackupEngine; import org.kar.archidata.backup.BackupEngine;
import org.kar.archidata.backup.BackupEngine.StoreMode; import org.kar.archidata.backup.BackupEngine.StoreMode;
import org.kar.archidata.catcher.GenericCatcher; import org.kar.archidata.catcher.GenericCatcher;
import org.kar.archidata.db.DBConfig; import org.kar.archidata.db.DbConfig;
import org.kar.archidata.filter.CORSFilter; import org.kar.archidata.filter.CORSFilter;
import org.kar.archidata.filter.OptionFilter; import org.kar.archidata.filter.OptionFilter;
import org.kar.archidata.migration.MigrationEngine; import org.kar.archidata.migration.MigrationEngine;
import org.kar.archidata.migration.model.Migration; import org.kar.archidata.migration.model.Migration;
import org.kar.archidata.tools.ConfigBaseVariable; import org.kar.archidata.tools.ConfigBaseVariable;
import org.kar.archidata.tools.ContextGenericTools;
import org.kar.archidata.tools.JWTWrapper; import org.kar.archidata.tools.JWTWrapper;
import org.kar.karso.api.ApplicationResource; import org.kar.karso.api.ApplicationResource;
import org.kar.karso.api.ApplicationTokenResource; import org.kar.karso.api.ApplicationTokenResource;
@ -33,6 +33,7 @@ import org.kar.karso.migration.Initialization;
import org.kar.karso.migration.Migration20231015; import org.kar.karso.migration.Migration20231015;
import org.kar.karso.migration.Migration20231126; import org.kar.karso.migration.Migration20231126;
import org.kar.karso.migration.Migration20240515; import org.kar.karso.migration.Migration20240515;
import org.kar.karso.migration.Migration20250204;
import org.kar.karso.model.Application; import org.kar.karso.model.Application;
import org.kar.karso.model.ApplicationToken; import org.kar.karso.model.ApplicationToken;
import org.kar.karso.model.Right; import org.kar.karso.model.Right;
@ -47,7 +48,6 @@ import jakarta.ws.rs.core.UriBuilder;
public class WebLauncher { public class WebLauncher {
private static final Logger LOGGER = LoggerFactory.getLogger(WebLauncher.class); private static final Logger LOGGER = LoggerFactory.getLogger(WebLauncher.class);
public static DBConfig dbConfig;
protected UpdateJwtPublicKey keyUpdater = null; protected UpdateJwtPublicKey keyUpdater = null;
protected HttpServer server = null; protected HttpServer server = null;
protected BackupEngine backupEngine = new BackupEngine("./backup", StoreMode.JSON); protected BackupEngine backupEngine = new BackupEngine("./backup", StoreMode.JSON);
@ -77,8 +77,9 @@ public class WebLauncher {
migrationEngine.add(new Migration20231015()); migrationEngine.add(new Migration20231015());
migrationEngine.add(new Migration20231126()); migrationEngine.add(new Migration20231126());
migrationEngine.add(new Migration20240515()); migrationEngine.add(new Migration20240515());
migrationEngine.add(new Migration20250204());
WebLauncher.LOGGER.info("Migrate the DB [START]"); WebLauncher.LOGGER.info("Migrate the DB [START]");
migrationEngine.migrateWaitAdmin(GlobalConfiguration.dbConfig); migrationEngine.migrateWaitAdmin(new DbConfig());
WebLauncher.LOGGER.info("Migrate the DB [STOP]"); WebLauncher.LOGGER.info("Migrate the DB [STOP]");
} }
@ -130,11 +131,13 @@ public class WebLauncher {
rc.register(HealthCheck.class); rc.register(HealthCheck.class);
rc.register(Front.class); rc.register(Front.class);
ContextGenericTools.addJsr310(rc);
// add jackson to be discover when we are ins stand-alone server // add jackson to be discover when we are ins stand-alone server
rc.register(JacksonFeature.class); rc.register(JacksonFeature.class);
// enable this to show low level request // enable this to show low level request
//rc.property(LoggingFeature.LOGGING_FEATURE_LOGGER_LEVEL_SERVER, Level.WARNING.getName()); //rc.property(LoggingFeature.LOGGING_FEATURE_LOGGER_LEVEL_SERVER, Level.WARNING.getName());
LOGGER.info("Start http Server: {}", getBaseURI());
this.server = GrizzlyHttpServerFactory.createHttpServer(getBaseURI(), rc); this.server = GrizzlyHttpServerFactory.createHttpServer(getBaseURI(), rc);
final HttpServer serverLink = this.server; final HttpServer serverLink = this.server;
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {

View File

@ -1,19 +1,8 @@
package org.kar.karso; package org.kar.karso;
import java.util.List;
import org.kar.archidata.api.DataResource;
import org.kar.archidata.externalRestApi.AnalyzeApi;
import org.kar.archidata.externalRestApi.TsGenerateApi;
import org.kar.archidata.tools.ConfigBaseVariable; import org.kar.archidata.tools.ConfigBaseVariable;
import org.kar.karso.api.ApplicationResource; import org.kar.karso.migration.Initialization;
import org.kar.karso.api.ApplicationTokenResource;
import org.kar.karso.api.Front;
import org.kar.karso.api.PublicKeyResource;
import org.kar.karso.api.RightResource;
import org.kar.karso.api.SystemConfigResource;
import org.kar.karso.api.UserResource;
import org.kar.karso.util.ConfigVariable; import org.kar.karso.util.ConfigVariable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -23,19 +12,8 @@ public class WebLauncherLocal extends WebLauncher {
private WebLauncherLocal() {} private WebLauncherLocal() {}
public static void generateObjects() throws Exception {
LOGGER.info("Generate APIs");
final List<Class<?>> listOfResources = List.of(Front.class, DataResource.class, ApplicationResource.class,
ApplicationTokenResource.class, PublicKeyResource.class, RightResource.class, UserResource.class,
SystemConfigResource.class);
final AnalyzeApi api = new AnalyzeApi();
api.addAllApi(listOfResources);
TsGenerateApi.generateApi(api, "../front/src/back-api/");
LOGGER.info("Generate APIs (DONE)");
}
public static void main(final String[] args) throws Exception { public static void main(final String[] args) throws Exception {
generateObjects(); Initialization.generateObjects();
final WebLauncherLocal launcher = new WebLauncherLocal(); final WebLauncherLocal launcher = new WebLauncherLocal();
launcher.process(); launcher.process();
LOGGER.info("end-configure the server & wait finish process:"); LOGGER.info("end-configure the server & wait finish process:");

View File

@ -6,14 +6,16 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import org.kar.archidata.annotation.AsyncType; import org.kar.archidata.annotation.AsyncType;
import org.kar.archidata.dataAccess.DBAccess;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.QueryAnd; import org.kar.archidata.dataAccess.QueryAnd;
import org.kar.archidata.dataAccess.QueryCondition; import org.kar.archidata.dataAccess.QueryCondition;
import org.kar.archidata.dataAccess.addOn.AddOnManyToMany; import org.kar.archidata.dataAccess.addOnSQL.AddOnManyToMany;
import org.kar.archidata.dataAccess.options.Condition; import org.kar.archidata.dataAccess.options.Condition;
import org.kar.archidata.exception.InputException; import org.kar.archidata.exception.InputException;
import org.kar.archidata.exception.SystemException; import org.kar.archidata.exception.SystemException;
import org.kar.archidata.filter.GenericContext; import org.kar.archidata.filter.GenericContext;
import org.kar.archidata.filter.PartRight;
import org.kar.archidata.tools.JWTWrapper; import org.kar.archidata.tools.JWTWrapper;
import org.kar.karso.model.AddUserData; import org.kar.karso.model.AddUserData;
import org.kar.karso.model.Application; import org.kar.karso.model.Application;
@ -96,7 +98,7 @@ public class ApplicationResource {
LOGGER.debug("getApplications"); LOGGER.debug("getApplications");
// TODO filter with the list of element available in his authorizations ... // TODO filter with the list of element available in his authorizations ...
final List<Application> tmp = DataAccess.gets(Application.class); final List<Application> tmp = DataAccess.gets(Application.class);
if (gc.userByToken.hasRight("ADMIN", true)) { if (sc.isUserInRole("ADMIN")) {
return tmp; return tmp;
} }
final List<Long> regular = getUserListOfApplication(gc.userByToken.id); final List<Long> regular = getUserListOfApplication(gc.userByToken.id);
@ -206,7 +208,9 @@ public class ApplicationResource {
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
public void addUser(@PathParam("id") final Long applicationId, final AddUserData data) throws Exception { public void addUser(@PathParam("id") final Long applicationId, final AddUserData data) throws Exception {
LOGGER.debug("getApplications"); LOGGER.debug("getApplications");
AddOnManyToMany.addLink(UserAuth.class, data.userId, "application", applicationId); try (DBAccess db = DBAccess.createInterface()) {
AddOnManyToMany.addLink(db, UserAuth.class, data.userId, "application", applicationId);
}
} }
@DELETE @DELETE
@ -215,7 +219,9 @@ public class ApplicationResource {
public void removeUser(@PathParam("id") final Long applicationId, @PathParam("userId") final Long userId) public void removeUser(@PathParam("id") final Long applicationId, @PathParam("userId") final Long userId)
throws Exception { throws Exception {
LOGGER.debug("getApplications"); LOGGER.debug("getApplications");
AddOnManyToMany.removeLink(UserAuth.class, userId, "application", applicationId); try (DBAccess db = DBAccess.createInterface()) {
AddOnManyToMany.removeLink(db, UserAuth.class, userId, "application", applicationId);
}
} }
@GET @GET
@ -279,16 +285,20 @@ public class ApplicationResource {
"Authenticate impossible ==> application not accessible '" + applicationName + "'"); "Authenticate impossible ==> application not accessible '" + applicationName + "'");
} }
// Get the USER Right // Get the USER Right
final Map<String, Object> applicationRight = RightResource.getUserRight(gc.userByToken.id, appl.id); final Map<String, PartRight> applicationRight = RightResource.getUserRight(gc.userByToken.id, appl.id);
if (!applicationRight.containsKey("USER")) { if (!applicationRight.containsKey("USER")) {
// If the USER is not override, the system add by default USER // If the USER is not override, the system add by default USER
applicationRight.put("USER", true); applicationRight.put("USER", PartRight.READ_WRITE);
}
final Map<String, Integer> rightForFront = new HashMap<>();
for (final Map.Entry<String, PartRight> entry : applicationRight.entrySet()) {
rightForFront.put(entry.getKey(), entry.getValue().getValue());
} }
final Map<String, Object> outRight = new HashMap<>(); final Map<String, Object> outRight = new HashMap<>();
// we set the right in the under map to manage multiple application group right. // we set the right in the under map to manage multiple application group right.
// and in some application user can see other user or all user of the // and in some application user can see other user or all user of the
// application // application
outRight.put(applicationName, applicationRight); outRight.put(applicationName, rightForFront);
final String ret = JWTWrapper.generateJWToken(gc.userByToken.id, gc.userByToken.name, "KarAuth", final String ret = JWTWrapper.generateJWToken(gc.userByToken.id, gc.userByToken.name, "KarAuth",
applicationName, outRight, -appl.ttl); applicationName, outRight, -appl.ttl);
// logger.debug(" ==> generate token: {}", ret); // logger.debug(" ==> generate token: {}", ret);

View File

@ -63,7 +63,7 @@ public class ApplicationTokenResource {
@Context final SecurityContext sc, @Context final SecurityContext sc,
@PathParam("applicationId") final Long applicationId, @PathParam("applicationId") final Long applicationId,
@PathParam("tokenId") final Integer tokenId) throws Exception { @PathParam("tokenId") final Integer tokenId) throws Exception {
final int nbRemoved = DataAccess.deleteWhere(ApplicationToken.class, final long nbRemoved = DataAccess.deleteWhere(ApplicationToken.class,
new Condition(new QueryAnd(new QueryCondition("parentId", "=", applicationId), new Condition(new QueryAnd(new QueryCondition("parentId", "=", applicationId),
new QueryCondition("id", "=", tokenId)))); new QueryCondition("id", "=", tokenId))));
if (nbRemoved == 0) { if (nbRemoved == 0) {

View File

@ -9,9 +9,9 @@ import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.QueryAnd; import org.kar.archidata.dataAccess.QueryAnd;
import org.kar.archidata.dataAccess.QueryCondition; import org.kar.archidata.dataAccess.QueryCondition;
import org.kar.archidata.dataAccess.options.Condition; import org.kar.archidata.dataAccess.options.Condition;
import org.kar.archidata.filter.PartRight;
import org.kar.karso.model.Right; import org.kar.karso.model.Right;
import org.kar.karso.model.RightDescription; import org.kar.karso.model.RightDescription;
import org.kar.karso.util.Transform;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -44,8 +44,8 @@ public class RightResource {
// Formatter:on // Formatter:on
} }
public static Map<String, Object> getUserRight(final long userId, final long applicationId) throws Exception { public static Map<String, PartRight> getUserRight(final long userId, final long applicationId) throws Exception {
final Map<String, Object> out = new HashMap<>(); final Map<String, PartRight> out = new HashMap<>();
final List<RightDescription> rightsDescriptions = getApplicationRightDecription(applicationId); final List<RightDescription> rightsDescriptions = getApplicationRightDecription(applicationId);
LOGGER.trace("Get some descriptions: {} applicationId={}", rightsDescriptions.size(), applicationId); LOGGER.trace("Get some descriptions: {} applicationId={}", rightsDescriptions.size(), applicationId);
if (rightsDescriptions != null && rightsDescriptions.size() != 0) { if (rightsDescriptions != null && rightsDescriptions.size() != 0) {
@ -62,8 +62,7 @@ public class RightResource {
if (description == null) { if (description == null) {
continue; continue;
} }
LOGGER.trace(" - id={} key={} type={} default={}", description.id, description.key, description.type, LOGGER.trace(" - id={} key={}", description.id, description.key);
description.defaultValue);
} }
for (final RightDescription description : rightsDescriptions) { for (final RightDescription description : rightsDescriptions) {
if (description == null) { if (description == null) {
@ -72,11 +71,7 @@ public class RightResource {
final Right right = rights.stream().filter(elem -> elem.rightDescriptionId.equals(description.id)) final Right right = rights.stream().filter(elem -> elem.rightDescriptionId.equals(description.id))
.findAny().orElse(null); .findAny().orElse(null);
if (right != null) { if (right != null) {
out.put(description.key, Transform.convertToType(description.type, right.value)); out.put(description.key, right.value);
} else if (description.defaultValue != null) {
out.put(description.key, Transform.convertToType(description.type, description.defaultValue));
} else {
out.put(description.key, null);
} }
} }
} else { } else {
@ -86,7 +81,7 @@ public class RightResource {
return out; return out;
} }
public static void updateUserRight(final long userId, final long applicationId, final Map<String, Object> delta) public static void updateUserRight(final long userId, final long applicationId, final Map<String, PartRight> delta)
throws Exception { throws Exception {
final List<RightDescription> rightsDescriptions = getApplicationRightDecription(applicationId); final List<RightDescription> rightsDescriptions = getApplicationRightDecription(applicationId);
LOGGER.debug("Get some descriptions: {} applicationId={}", rightsDescriptions.size(), applicationId); LOGGER.debug("Get some descriptions: {} applicationId={}", rightsDescriptions.size(), applicationId);
@ -100,15 +95,11 @@ public class RightResource {
// TODO: this is a really strange case to manage later... // TODO: this is a really strange case to manage later...
continue; continue;
} }
final Object newValue = delta.get(description.key); final PartRight newValue = delta.get(description.key);
if (newValue == null) { if (newValue == null) {
//No need to update or create... //No need to update or create...
continue; continue;
} }
final String convertedValue = Transform.convertToStringCheck(description.type, newValue);
if (convertedValue == null) {
throw new IllegalArgumentException("Uncompatible value:'" + description.type + "'");
}
final List<Right> allRights = rights.stream().filter(elem -> elem.rightDescriptionId.equals(description.id)) final List<Right> allRights = rights.stream().filter(elem -> elem.rightDescriptionId.equals(description.id))
.toList(); .toList();
if (allRights.size() > 1) { if (allRights.size() > 1) {
@ -122,7 +113,8 @@ public class RightResource {
final Right right = allRights.get(0); final Right right = allRights.get(0);
// The value exist, we need to update it // The value exist, we need to update it
LOGGER.debug("Request update a knonwn parameter: {} with {}", description.key, newValue); LOGGER.debug("Request update a knonwn parameter: {} with {}", description.key, newValue);
right.value = convertedValue; right.value = newValue;
// TODO: maybe need to remove the value instead of set a new one ...
DataAccess.update(right, right.id, List.of("value")); DataAccess.update(right, right.id, List.of("value"));
} else { } else {
// we need to create it // we need to create it
@ -131,7 +123,7 @@ public class RightResource {
right.applicationId = applicationId; right.applicationId = applicationId;
right.userId = userId; right.userId = userId;
right.rightDescriptionId = description.id; right.rightDescriptionId = description.id;
right.value = convertedValue; right.value = newValue;
DataAccess.insert(right); DataAccess.insert(right);
} }
} }

View File

@ -9,22 +9,33 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.kar.archidata.dataAccess.DBAccess;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.QueryAnd;
import org.kar.archidata.dataAccess.QueryCondition; import org.kar.archidata.dataAccess.QueryCondition;
import org.kar.archidata.dataAccess.addOn.AddOnManyToMany; import org.kar.archidata.dataAccess.addOnSQL.AddOnManyToMany;
import org.kar.archidata.dataAccess.options.AccessDeletedItems;
import org.kar.archidata.dataAccess.options.Condition; import org.kar.archidata.dataAccess.options.Condition;
import org.kar.archidata.dataAccess.options.ReadAllColumn;
import org.kar.archidata.exception.FailException; import org.kar.archidata.exception.FailException;
import org.kar.archidata.exception.InputException;
import org.kar.archidata.exception.SystemException; import org.kar.archidata.exception.SystemException;
import org.kar.archidata.filter.GenericContext; import org.kar.archidata.filter.GenericContext;
import org.kar.archidata.filter.PartRight;
import org.kar.archidata.model.GetToken; import org.kar.archidata.model.GetToken;
import org.kar.archidata.tools.JWTWrapper; import org.kar.archidata.tools.JWTWrapper;
import org.kar.karso.migration.Initialization; import org.kar.karso.migration.Initialization;
import org.kar.karso.model.Application;
import org.kar.karso.model.ChangePassword; import org.kar.karso.model.ChangePassword;
import org.kar.karso.model.ChangePassword.ChangePasswordChecker;
import org.kar.karso.model.DataGetToken; import org.kar.karso.model.DataGetToken;
import org.kar.karso.model.DataGetToken.DataGetTokenChecker;
import org.kar.karso.model.Right;
import org.kar.karso.model.RightDescription;
import org.kar.karso.model.UserAuth; import org.kar.karso.model.UserAuth;
import org.kar.karso.model.UserAuth.UserAuthChecker;
import org.kar.karso.model.UserAuthGet; import org.kar.karso.model.UserAuthGet;
import org.kar.karso.model.UserCreate; import org.kar.karso.model.UserCreate;
import org.kar.karso.model.UserCreate.UserCreateChecker;
import org.kar.karso.util.ConfigVariable; import org.kar.karso.util.ConfigVariable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -50,6 +61,11 @@ import jakarta.ws.rs.core.SecurityContext;
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public class UserResource { public class UserResource {
private static final Logger LOGGER = LoggerFactory.getLogger(UserResource.class); private static final Logger LOGGER = LoggerFactory.getLogger(UserResource.class);
static final UserAuthChecker CHECKER = new UserAuthChecker();
static final DataGetTokenChecker CHECKER_REQUEST_TOKEN = new DataGetTokenChecker();
static final ChangePasswordChecker CHECKER_CHANGE_PASSWORD = new ChangePasswordChecker();
static final UserCreateChecker CHECKER_CREATE_USER = new UserCreateChecker();
public static final String APPLICATION = "karso";
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public static class UserOut { public static class UserOut {
@ -66,16 +82,26 @@ public class UserResource {
@GET @GET
@RolesAllowed("ADMIN") @RolesAllowed("ADMIN")
public List<UserAuthGet> getUsers() throws Exception { public List<UserAuthGet> gets() throws Exception {
return DataAccess.gets(UserAuthGet.class); final List<UserAuthGet> out = DataAccess.gets(UserAuthGet.class);
return out;
} }
@GET @GET
@Path("{id}") @Path("{id}")
@RolesAllowed("ADMIN") @RolesAllowed({ "ADMIN", "USER" })
public UserAuthGet getUser(@Context final SecurityContext sc, @PathParam("id") final long userId) throws Exception { public UserAuthGet get(@Context final SecurityContext sc, @PathParam("id") final long id) throws Exception {
//GenericContext gc = (GenericContext) sc.getUserPrincipal(); if (!sc.isUserInRole("ADMIN")) {
return DataAccess.get(UserAuthGet.class, userId); // in case of user we need to check if it get his own id parameters:
final UserAuthGet ret = DataAccess.get(UserAuthGet.class, id, new AccessDeletedItems());
return ret;
}
final UserAuthGet ret = DataAccess.get(UserAuthGet.class, id);
if (ret != null) {
return ret;
}
// Find element in deleted
return DataAccess.get(UserAuthGet.class, id, new AccessDeletedItems(), new ReadAllColumn());
} }
@POST @POST
@ -87,18 +113,20 @@ public class UserResource {
@PathParam("applicationId") final long applicationId, @PathParam("applicationId") final long applicationId,
final boolean data) throws Exception { final boolean data) throws Exception {
LOGGER.debug("Find typeNode"); LOGGER.debug("Find typeNode");
if (data) { try (DBAccess db = DBAccess.createInterface()) {
AddOnManyToMany.addLink(UserAuth.class, userId, "application", applicationId); if (data) {
} else { AddOnManyToMany.addLink(db, UserAuth.class, userId, "application", applicationId);
AddOnManyToMany.removeLink(UserAuth.class, userId, "application", applicationId); } else {
AddOnManyToMany.removeLink(db, UserAuth.class, userId, "application", applicationId);
}
return db.get(UserAuth.class, userId);
} }
return DataAccess.get(UserAuth.class, userId);
} }
@GET @GET
@Path("{userId}/application/{applicationId}/rights") @Path("{userId}/application/{applicationId}/rights")
@RolesAllowed("ADMIN") @RolesAllowed("ADMIN")
public Map<String, Object> getApplicationRight( public Map<String, PartRight> getApplicationRight(
@Context final SecurityContext sc, @Context final SecurityContext sc,
@PathParam("userId") final long userId, @PathParam("userId") final long userId,
@PathParam("applicationId") final long applicationId) throws Exception { @PathParam("applicationId") final long applicationId) throws Exception {
@ -109,11 +137,11 @@ public class UserResource {
@Path("{userId}/application/{applicationId}/rights") @Path("{userId}/application/{applicationId}/rights")
@RolesAllowed("ADMIN") @RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
public Map<String, Object> patchApplicationRight( public Map<String, PartRight> patchApplicationRight(
@Context final SecurityContext sc, @Context final SecurityContext sc,
@PathParam("userId") final long userId, @PathParam("userId") final long userId,
@PathParam("applicationId") final long applicationId, @PathParam("applicationId") final long applicationId,
final Map<String, Object> data) throws Exception { final Map<String, PartRight> data) throws Exception {
this.LOGGER.info("Patch data from FRONT: {}", data); this.LOGGER.info("Patch data from FRONT: {}", data);
RightResource.updateUserRight(userId, applicationId, data); RightResource.updateUserRight(userId, applicationId, data);
return RightResource.getUserRight(userId, applicationId); return RightResource.getUserRight(userId, applicationId);
@ -126,11 +154,34 @@ public class UserResource {
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
public void setAdmin(@Context final SecurityContext sc, @PathParam("id") final long userId, final boolean data) public void setAdmin(@Context final SecurityContext sc, @PathParam("id") final long userId, final boolean data)
throws Exception { throws Exception {
final UserAuth user = new UserAuth(); try (DBAccess da = DBAccess.createInterface()) {
user.admin = data; final Application appKarso = da.get(Application.class,
final int ret = DataAccess.update(user, userId, List.of("admin")); new Condition(new QueryCondition("name", "=", UserResource.APPLICATION)));
if (ret == 0) { final RightDescription rightsDescription = da.get(RightDescription.class,
throw new FailException(Response.Status.NOT_MODIFIED, "Fail to modify user as an admin."); new Condition(new QueryAnd(new QueryCondition("key", "=", "ADMIN"),
new QueryCondition("applicationId", "=", appKarso.id))));
final Right right = da.get(Right.class,
new Condition(new QueryAnd(new QueryCondition("rightDescriptionId", "=", rightsDescription.id),
new QueryCondition("applicationId", "=", appKarso.id),
new QueryCondition("userId", "=", userId))));
if (right == null) {
final Right newRight = new Right();
newRight.applicationId = appKarso.id;
newRight.userId = userId;
newRight.rightDescriptionId = rightsDescription.id;
newRight.value = PartRight.READ_WRITE;
da.insert(newRight);
return;
} else if (PartRight.READ_WRITE.equals(right.value)) {
return;
}
// TODO: maybe remove ???
right.value = PartRight.READ_WRITE;
final long ret = da.update(right, right.id, List.of("value"));
if (ret == 0) {
throw new FailException(Response.Status.NOT_MODIFIED, "Fail to modify user as an admin.");
}
} }
} }
@ -142,7 +193,7 @@ public class UserResource {
throws Exception { throws Exception {
final UserAuth user = new UserAuth(); final UserAuth user = new UserAuth();
user.blocked = data; user.blocked = data;
final int ret = DataAccess.update(user, userId, List.of("blocked")); final long ret = DataAccess.update(user, userId, List.of("blocked"));
if (ret == 0) { if (ret == 0) {
throw new FailException(Response.Status.NOT_MODIFIED, "Fail to block the User."); throw new FailException(Response.Status.NOT_MODIFIED, "Fail to block the User.");
} }
@ -153,47 +204,31 @@ public class UserResource {
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
public UserAuthGet create(final UserCreate user) throws Exception { public UserAuthGet create(final UserCreate user) throws Exception {
LOGGER.debug("create new User email={} login={}", user.email, user.login); LOGGER.debug("create new User email={} login={}", user.email, user.login);
// verify login or email is correct: try (DBAccess da = DBAccess.createInterface()) {
if (user.login == null || user.login.length() < 6) { CHECKER_CREATE_USER.checkAll(da, "", user, null);
throw new InputException("login", "Authentiocate-method-error (login too small: '" + user.login + "')"); // Check login does not exist
List<UserAuth> out = da.getsWhere(UserAuth.class,
new Condition(new QueryCondition("login", "=", user.login)));
if (out.size() >= 1) {
throw new FailException(Response.Status.BAD_REQUEST, "Login already used !!!");
}
// Check email does not exist
out = da.getsWhere(UserAuth.class, new Condition(new QueryCondition("email", "=", user.email)));
if (out.size() >= 1) {
throw new FailException(Response.Status.BAD_REQUEST, "e-mail already used !!!");
}
// Add new user and return formated data.
final UserAuth newUser = new UserAuth();
newUser.blocked = false;
newUser.avatar = false;
newUser.login = user.login;
newUser.password = user.password;
newUser.email = user.email;
newUser.lastConnection = Timestamp.valueOf(LocalDateTime.now());
final UserAuth tmp = da.insert(newUser);
LOGGER.debug("create new user done with id=={}", tmp.id);
return da.get(UserAuthGet.class, tmp.id);
} }
// TODO: check login format
if (user.email == null || user.email.length() < 6) {
throw new InputException("email", "Authentiocate-method-error (email too small: '" + user.email + "')");
}
// TODO: check email format
if (user.password == null || user.password.length() != 128) {
throw new InputException("password", "null password, or wrong hash size");
}
// TODO: verify if the data are a hash ...
// Check login does not exist
List<UserAuth> out = DataAccess.getsWhere(UserAuth.class,
new Condition(new QueryCondition("login", "=", user.login)));
if (out.size() >= 1) {
throw new FailException(Response.Status.BAD_REQUEST, "Login already used !!!");
}
// Check email does not exist
out = DataAccess.getsWhere(UserAuth.class, new Condition(new QueryCondition("email", "=", user.email)));
if (out.size() >= 1) {
throw new FailException(Response.Status.BAD_REQUEST, "e-mail already used !!!");
}
// Add new user and return formated data.
final UserAuth newUser = new UserAuth();
newUser.admin = false;
newUser.removed = false;
newUser.blocked = false;
newUser.avatar = false;
newUser.login = user.login;
newUser.password = user.password;
newUser.email = user.email;
newUser.lastConnection = Timestamp.valueOf(LocalDateTime.now());
final UserAuth tmp = DataAccess.insert(newUser);
LOGGER.debug("create new user done with id=={}", tmp.id);
return DataAccess.get(UserAuthGet.class, tmp.id);
} }
@GET @GET
@ -214,20 +249,19 @@ public class UserResource {
LOGGER.debug("ChangePassword()"); LOGGER.debug("ChangePassword()");
final GenericContext gc = (GenericContext) sc.getUserPrincipal(); final GenericContext gc = (GenericContext) sc.getUserPrincipal();
LOGGER.debug("== USER ? {}", gc.userByToken); LOGGER.debug("== USER ? {}", gc.userByToken);
try (DBAccess da = DBAccess.createInterface()) {
if (data == null) { CHECKER_CHANGE_PASSWORD.checkAll(da, "", data, null);
throw new InputException("data", "No data set..."); final UserAuth user = checkAuthUser(data.login, data.time, data.password);
if (user == null) {
throw new SystemException("Fail to retrieve the user... (impossible case)");
}
if (!user.id.equals(gc.userByToken.id)) {
throw new SystemException("Try to change the password of an other user !!! You are BAD...");
}
// Process the update:
user.password = data.newPassword;
DataAccess.update(user, user.id, List.of("password"));
} }
if (data.newPassword == null || data.newPassword.length() != 128) {
throw new InputException("newPassword", "null password, or wrong hash size");
}
final UserAuth user = checkAuthUser(data.method, data.login, data.time, data.password);
if (user == null) {
throw new SystemException("Fail to retrieve the user... (impossible case)");
}
// Process the update:
user.password = data.newPassword;
DataAccess.update(user, user.id, List.of("password"));
} }
@GET @GET
@ -251,29 +285,18 @@ public class UserResource {
return out.size() >= 1; return out.size() >= 1;
} }
private UserAuth checkAuthUser(final String method, final String login, final String time, final String password) private UserAuth checkAuthUser(final String login, final String time, final String password) throws Exception {
throws Exception {
// check good version:
if (!"v1".contentEquals(method)) {
throw new InputException("method", "Authentiocate-method-error (wrong version: '" + method + "')");
}
// verify login or email is correct:
if (login.length() < 6) {
throw new InputException("login", "Authentiocate-method-error (email or login too small: '" + login + "')");
}
if (password == null || password.length() != 128) {
throw new InputException("password", "null password, or wrong hash size");
}
// email or login? // email or login?
String query = "login"; String query = "login";
if (login.contains("@")) { if (login.contains("@")) {
query = "email"; query = "email";
} }
final UserAuth user = DataAccess.getWhere(UserAuth.class, new Condition(new QueryCondition(query, "=", login))); final UserAuth user = DataAccess.getWhere(UserAuth.class, new Condition(new QueryCondition(query, "=", login)),
new AccessDeletedItems(), new ReadAllColumn());
if (user == null) { if (user == null) {
throw new FailException(Response.Status.PRECONDITION_FAILED, throw new FailException(Response.Status.PRECONDITION_FAILED,
"FAIL Authentiocate-wrong email/login '" + login + "')"); "FAIL Authenticate-wrong email/login '" + login + "')");
} }
// Check the password: // Check the password:
final String passwordCheck = getSHA512( final String passwordCheck = getSHA512(
@ -281,9 +304,8 @@ public class UserResource {
if (!passwordCheck.contentEquals(password)) { if (!passwordCheck.contentEquals(password)) {
throw new FailException(Response.Status.PRECONDITION_FAILED, "Password error ..."); throw new FailException(Response.Status.PRECONDITION_FAILED, "Password error ...");
} }
LOGGER.debug(" ==> pass nearly all test : admin={} blocked={} removed={}", user.admin, user.blocked, LOGGER.debug(" ==> pass nearly all test: blocked={} deleted={} ", user.blocked, user.deleted);
user.removed); if (user.blocked || (user.deleted != null && user.deleted == true)) {
if (user.blocked || user.removed) {
throw new FailException(Response.Status.UNAUTHORIZED, "FAIL Authentiocate"); throw new FailException(Response.Status.UNAUTHORIZED, "FAIL Authentiocate");
} }
return user; return user;
@ -294,37 +316,40 @@ public class UserResource {
@PermitAll @PermitAll
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
public GetToken getToken(final DataGetToken data) throws Exception { public GetToken getToken(final DataGetToken data) throws Exception {
LOGGER.info("User Authenticate: {}", data.login()); LOGGER.info("User Authenticate: {}", data.login);
final UserAuth user = checkAuthUser(data.method(), data.login(), data.time(), data.password()); try (DBAccess da = DBAccess.createInterface()) {
// at the point the user has been not deleted and not blocked. CHECKER_REQUEST_TOKEN.checkAll(da, "", data, null);
// this authentication is valid only for Karso ==> not for the application final UserAuth user = checkAuthUser(data.login, data.time, data.password);
final int expirationTimeInMinutes = ConfigVariable.getAuthExpirationTime(); // at the point the user has been not deleted and not blocked.
// this authentication is valid only for Karso ==> not for the application
// Get the USER Right (Note: by construction KARSO have application ID = KARSO_INITIALISATION_ID final int expirationTimeInMinutes = ConfigVariable.getAuthExpirationTime();
final Map<String, Object> ssoRight = RightResource.getUserRight(user.id, // Get the USER Right (Note: by construction KARSO have application ID = KARSO_INITIALISATION_ID
Initialization.KARSO_INITIALISATION_ID); final Map<String, PartRight> ssoRight = RightResource.getUserRight(user.id,
if (!ssoRight.containsKey("USER")) { Initialization.KARSO_INITIALISATION_ID);
// If the USER is not override, the system add by default USER if (!ssoRight.containsKey("USER")) {
ssoRight.put("USER", true); // If the USER is not override, the system add by default USER
ssoRight.put("USER", PartRight.READ_WRITE);
}
final Map<String, Integer> rightForFront = new HashMap<>();
for (final Map.Entry<String, PartRight> entry : ssoRight.entrySet()) {
rightForFront.put(entry.getKey(), entry.getValue().getValue());
}
LOGGER.debug("Get new token with right: {}", ssoRight);
final Map<String, Object> outRight = new HashMap<>();
// we set the right in the under map to manage multiple application group right. and in some application user can see other user or all user of the application
outRight.put(UserResource.APPLICATION, rightForFront);
// TODO: maybe correct this get of TTL...
final String ret = JWTWrapper.generateJWToken(user.id, user.login, "KarAuth", UserResource.APPLICATION,
outRight, expirationTimeInMinutes);
if (ret == null) {
throw new SystemException("Missing internal JWT system ==> can not sign anything ...");
}
// Update last connection:
final UserAuth newUser = new UserAuth();
newUser.lastConnection = Timestamp.valueOf(LocalDateTime.now());
da.update(newUser, user.id, List.of("lastConnection"));
return new GetToken(ret);
} }
LOGGER.debug("Get new token with right: {}", ssoRight);
final Map<String, Object> outRight = new HashMap<>();
final String applicationName = "karso";
// we set the right in the under map to manage multiple application group right. and in some application user can see other user or all user of the application
outRight.put(applicationName, ssoRight);
// TODO: maybe correct this get of TTL...
final String ret = JWTWrapper.generateJWToken(user.id, user.login, "KarAuth", applicationName, outRight,
expirationTimeInMinutes);
if (ret == null) {
throw new SystemException("Missing internal JWT system ==> can not sign anything ...");
}
// Update last connection:
final UserAuth newUser = new UserAuth();
newUser.lastConnection = Timestamp.valueOf(LocalDateTime.now());
DataAccess.update(newUser, user.id, List.of("lastConnection"));
//LOGGER.debug(" ==> generate token: {}", ret);
return new GetToken(ret);
} }
public static String bytesToHex(final byte[] bytes) { public static String bytesToHex(final byte[] bytes) {
@ -335,7 +360,7 @@ public class UserResource {
return sb.toString(); return sb.toString();
} }
public String getSHA512(final String passwordToHash) { public static String getSHA512(final String passwordToHash) {
try { try {
final MessageDigest md = MessageDigest.getInstance("SHA-512"); final MessageDigest md = MessageDigest.getInstance("SHA-512");
final byte[] bytes = md.digest(passwordToHash.getBytes(StandardCharsets.UTF_8)); final byte[] bytes = md.digest(passwordToHash.getBytes(StandardCharsets.UTF_8));

View File

@ -2,10 +2,13 @@ package org.kar.karso.filter;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.time.Instant; import java.time.Instant;
import java.util.Map;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.filter.AuthenticationFilter; import org.kar.archidata.filter.AuthenticationFilter;
import org.kar.archidata.filter.PartRight;
import org.kar.archidata.model.UserByToken; import org.kar.archidata.model.UserByToken;
import org.kar.karso.api.UserResource;
import org.kar.karso.model.ApplicationToken; import org.kar.karso.model.ApplicationToken;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -23,7 +26,7 @@ public class KarsoAuthenticationFilter extends AuthenticationFilter {
//curl http://0.0.0.0:15080/karso/api/public_key/pem --output plop.txt -H "Authorization: Zota 1:U0sJM1m@-STSdfg4365fJOFUGbR4kFycBu1qGZPwf7gW6k2WWRBzTPUH7QutCgPw-SDss45_563sSDFdfg@dsf@456" --verbose //curl http://0.0.0.0:15080/karso/api/public_key/pem --output plop.txt -H "Authorization: Zota 1:U0sJM1m@-STSdfg4365fJOFUGbR4kFycBu1qGZPwf7gW6k2WWRBzTPUH7QutCgPw-SDss45_563sSDFdfg@dsf@456" --verbose
public KarsoAuthenticationFilter() { public KarsoAuthenticationFilter() {
super("karso"); super(UserResource.APPLICATION);
} }
@Override @Override
@ -64,7 +67,8 @@ public class KarsoAuthenticationFilter extends AuthenticationFilter {
userByToken.name = value.name; userByToken.name = value.name;
userByToken.parentId = value.parentId; userByToken.parentId = value.parentId;
userByToken.type = UserByToken.TYPE_APPLICATION; userByToken.type = UserByToken.TYPE_APPLICATION;
userByToken.right.put("APPLICATION", true); // TODO: Manage the retrieve of specific rights ...
userByToken.right.put(UserResource.APPLICATION, Map.of("APPLICATION", PartRight.READ_WRITE));
return userByToken; return userByToken;
} }
} }

View File

@ -2,9 +2,21 @@ package org.kar.karso.migration;
import java.util.List; import java.util.List;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.api.DataResource;
import org.kar.archidata.dataAccess.DBAccess;
import org.kar.archidata.dataAccess.addOnSQL.AddOnManyToMany;
import org.kar.archidata.externalRestApi.AnalyzeApi;
import org.kar.archidata.externalRestApi.TsGenerateApi;
import org.kar.archidata.filter.PartRight;
import org.kar.archidata.migration.MigrationSqlStep; import org.kar.archidata.migration.MigrationSqlStep;
import org.kar.archidata.tools.UuidUtils; import org.kar.archidata.model.token.JwtToken;
import org.kar.karso.api.ApplicationResource;
import org.kar.karso.api.ApplicationTokenResource;
import org.kar.karso.api.Front;
import org.kar.karso.api.PublicKeyResource;
import org.kar.karso.api.RightResource;
import org.kar.karso.api.SystemConfigResource;
import org.kar.karso.api.UserResource;
import org.kar.karso.model.Application; import org.kar.karso.model.Application;
import org.kar.karso.model.ApplicationToken; import org.kar.karso.model.ApplicationToken;
import org.kar.karso.model.Right; import org.kar.karso.model.Right;
@ -19,62 +31,101 @@ public class Initialization extends MigrationSqlStep {
public static final int KARSO_INITIALISATION_ID = 1; public static final int KARSO_INITIALISATION_ID = 1;
public static final List<Class<?>> LIST_OF_COMMON_CLASSES = List.of(Settings.class, UserAuth.class, public static final List<Class<?>> CLASSES_BASE = List.of(Settings.class, UserAuth.class, Application.class,
Application.class, ApplicationToken.class, RightDescription.class, Right.class); ApplicationToken.class, RightDescription.class, Right.class);
// created object
private Application app = null;
private UserAuth user = null;
private RightDescription rightDescription;
@Override @Override
public String getName() { public String getName() {
return "Initialization"; return "Initialization";
} }
public Initialization() { public static void generateObjects() throws Exception {
LOGGER.info("Generate APIs");
final List<Class<?>> listOfResources = List.of(Front.class, DataResource.class, ApplicationResource.class,
ApplicationTokenResource.class, PublicKeyResource.class, RightResource.class, UserResource.class,
SystemConfigResource.class);
final AnalyzeApi api = new AnalyzeApi();
api.addAllApi(listOfResources);
api.addModel(JwtToken.class);
TsGenerateApi.generateApi(api, "../front/src/back-api/");
LOGGER.info("Generate APIs (DONE)");
} }
@Override @Override
public void generateStep() throws Exception { public void generateStep() throws Exception {
for (final Class<?> clazz : LIST_OF_COMMON_CLASSES) { for (final Class<?> clazz : CLASSES_BASE) {
addClass(clazz); addClass(clazz);
} }
addAction( addAction((final DBAccess da) -> {
""" final Application app = new Application();
INSERT INTO `application` (`id`, `name`, `description`, `redirect`, `redirectDev`, `notification`, `ttl`) VALUES //app.id = 1L;
(1, 'karso', 'Root SSO interface', 'http://atria-soft/karso', '', '', 666); app.name = "karso";
"""); app.description = "Root SSO interface";
app.redirect = "https://atria-soft.org/karso/sso";
app.redirectDev = "http://localhost:4003/karso/sso";
app.notification = "";
app.ttl = 666;
this.app = da.insert(app);
});
// default admin: "karadmin" password: "adminA@666" // default admin: "karadmin" password: "adminA@666"
addAction( addAction((final DBAccess da) -> {
""" final UserAuth user = new UserAuth();
INSERT INTO `user` (`id`, `login`, `password`, `email`, `admin`) VALUES //user.id = 1L;
(1, 'karadmin', '0ddcac5ede3f1300a1ce5948ab15112f2810130531d578ab8bc4dc131652d7cf7a3ff6e827eb957bff43bc2c65a6a1d46722e5b3a2343ac3176a33ea7250080b', user.login = "karadmin";
'admin@admin.ZZZ', 1); user.password = UserResource.getSHA512("adminA@666");
"""); user.email = "admin@admin.ZZZ";
final String data = UuidUtils.nextUUID().toString(); this.user = da.insert(user);
addAction(""" });
INSERT INTO `user_link_application` (`uuid`, `object1Id`, `object2Id`) addAction((final DBAccess da) -> {
VALUES (UUID_TO_BIN('%s'), '1', '1'); AddOnManyToMany.addLink(da, UserAuth.class, this.user.id, "application", this.app.id);
""".formatted(data), "mysql"); });
addAction(""" addAction((final DBAccess da) -> {
INSERT INTO `user_link_application` (`uuid`, `object1Id`, `object2Id`) final Settings settings = new Settings();
VALUES ('%s', '1', '1'); settings.key = "SIGN_UP_ENABLE";
""".formatted(data), "sqlite"); settings.right = "rwr-r-";
addAction(""" settings.type = "BOOLEAN";
INSERT INTO `settings` (`key`, `right`, `type`, `value`) VALUES settings.value = "false";
('SIGN_UP_ENABLE', 'rwr-r-', 'BOOLEAN', 'false'), da.insert(settings);
('SIGN_IN_ENABLE', 'rwr-r-', 'BOOLEAN', 'true'), settings.key = "SIGN_IN_ENABLE";
('SIGN_UP_FILTER', 'rw----', 'STRING', '.*'), settings.right = "rwr-r-";
('EMAIL_VALIDATION_REQUIRED', 'rwr-r-', 'BOOLEAN', 'false'); settings.type = "BOOLEAN";
"""); settings.value = "true";
addAction( da.insert(settings);
""" settings.key = "SIGN_UP_FILTER";
INSERT INTO `rightDescription` (`id`, `applicationId`, `key`, `title`, `description`, `type`, `defaultValue`) VALUES settings.right = "rw----";
(1, 1, 'ADMIN', 'Administrator', 'Full administrator Right', 'BOOLEAN', 'false'); settings.type = "STRING";
"""); settings.value = ".*";
addAction(""" da.insert(settings);
INSERT INTO `right` (`applicationId`, `userId`, `rightDescriptionId`, `value`) VALUES settings.key = "EMAIL_VALIDATION_REQUIRED";
(1, 1, 1, 'true'); settings.right = "rwr-r-";
"""); settings.type = "BOOLEAN";
// we generate an offset to permit to manage some generic upgrade in the future... (can not be done in sqlite) settings.value = "false";
da.insert(settings);
});
addAction((final DBAccess da) -> {
final RightDescription rightDescription = new RightDescription();
rightDescription.applicationId = this.app.id;
rightDescription.key = "ADMIN";
rightDescription.title = "Administrator";
rightDescription.description = "Full administrator Right";
this.rightDescription = da.insert(rightDescription);
});
addAction((final DBAccess da) -> {
final Right right = new Right();
right.applicationId = this.app.id;
right.userId = this.user.id;
right.rightDescriptionId = this.rightDescription.id;
right.value = PartRight.READ_WRITE;
da.insert(right);
});
// we generate an offset to permit to manage some generic upgrade in the future...
addAction(""" addAction("""
ALTER TABLE `application` AUTO_INCREMENT = 1000; ALTER TABLE `application` AUTO_INCREMENT = 1000;
""", "mysql"); """, "mysql");
@ -93,10 +144,10 @@ public class Initialization extends MigrationSqlStep {
display(); display();
} }
public static void dropAll() { public static void dropAll(final DBAccess da) {
for (final Class<?> element : LIST_OF_COMMON_CLASSES) { for (final Class<?> element : CLASSES_BASE) {
try { try {
DataAccess.drop(element); da.drop(element);
} catch (final Exception ex) { } catch (final Exception ex) {
LOGGER.error("Fail to drop table !!!!!!"); LOGGER.error("Fail to drop table !!!!!!");
ex.printStackTrace(); ex.printStackTrace();
@ -104,10 +155,10 @@ public class Initialization extends MigrationSqlStep {
} }
} }
public static void cleanAll() { public static void cleanAll(final DBAccess da) {
for (final Class<?> element : LIST_OF_COMMON_CLASSES) { for (final Class<?> element : CLASSES_BASE) {
try { try {
DataAccess.cleanAll(element); da.cleanAll(element);
} catch (final Exception ex) { } catch (final Exception ex) {
LOGGER.error("Fail to clean table !!!!!!"); LOGGER.error("Fail to clean table !!!!!!");
ex.printStackTrace(); ex.printStackTrace();

View File

@ -2,7 +2,7 @@ package org.kar.karso.migration;
import java.util.List; import java.util.List;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DBAccess;
import org.kar.archidata.dataAccess.options.AccessDeletedItems; import org.kar.archidata.dataAccess.options.AccessDeletedItems;
import org.kar.archidata.dataAccess.options.OverrideTableName; import org.kar.archidata.dataAccess.options.OverrideTableName;
import org.kar.archidata.migration.MigrationSqlStep; import org.kar.archidata.migration.MigrationSqlStep;
@ -28,14 +28,14 @@ public class Migration20240515 extends MigrationSqlStep {
addAction(""" addAction("""
ALTER TABLE `user_link_application` ADD `uuid` binary(16) AFTER `id`; ALTER TABLE `user_link_application` ADD `uuid` binary(16) AFTER `id`;
"""); """);
addAction(() -> { addAction((final DBAccess da) -> {
final List<UUIDConversion> datas = DataAccess.gets(UUIDConversion.class, new AccessDeletedItems(), final List<UUIDConversion> datas = da.gets(UUIDConversion.class, new AccessDeletedItems(),
new OverrideTableName("user_link_application")); new OverrideTableName("user_link_application"));
for (final UUIDConversion elem : datas) { for (final UUIDConversion elem : datas) {
elem.uuid = UuidUtils.nextUUID(); elem.uuid = UuidUtils.nextUUID();
} }
for (final UUIDConversion elem : datas) { for (final UUIDConversion elem : datas) {
DataAccess.update(elem, elem.id, List.of("uuid"), new OverrideTableName("user_link_application")); da.update(elem, elem.id, List.of("uuid"), new OverrideTableName("user_link_application"));
} }
}); });
addAction(""" addAction("""

View File

@ -0,0 +1,87 @@
package org.kar.karso.migration;
import java.util.List;
import org.bson.types.ObjectId;
import org.kar.archidata.dataAccess.DBAccess;
import org.kar.archidata.dataAccess.options.AccessDeletedItems;
import org.kar.archidata.dataAccess.options.OverrideTableName;
import org.kar.archidata.filter.PartRight;
import org.kar.archidata.migration.MigrationSqlStep;
import org.kar.karso.migration.model.OIDConversion;
import org.kar.karso.model.Right;
public class Migration20250204 extends MigrationSqlStep {
public static final int KARSO_INITIALISATION_ID = 1;
@Override
public String getName() {
return "migration-2025-02-04: mograte native id as ObjectId";
}
@Override
public void generateStep() {
// update migration update (last one)
addAction("""
ALTER TABLE `user_link_application` ADD `_id` binary(12) AFTER `uuid`;
""");
addAction((final DBAccess da) -> {
final List<OIDConversion> datas = da.gets(OIDConversion.class, new AccessDeletedItems(),
new OverrideTableName("user_link_application"));
for (final OIDConversion elem : datas) {
elem._id = new ObjectId();
}
for (final OIDConversion elem : datas) {
da.update(elem, elem.uuid, List.of("_id"), new OverrideTableName("user_link_application"));
}
});
addAction("""
ALTER TABLE `user_link_application`
CHANGE `uuid` `uuid` binary(16);
""");
addAction("""
ALTER TABLE `user_link_application` DROP PRIMARY KEY;
""");
addAction("""
ALTER TABLE `user_link_application`
CHANGE `_id` `_id` binary(12) NOT NULL;
""");
addAction("""
ALTER TABLE `user_link_application`
ADD PRIMARY KEY `_id` (`_id`);
""");
addAction("""
ALTER TABLE `user_link_application`
DROP `uuid`;
""");
addAction("""
ALTER TABLE `user`
DROP `admin`;
""");
addAction("""
ALTER TABLE `user`
DROP `removed`;
""");
addAction("""
ALTER TABLE `user` ADD `blockedReason` varchar(512) AFTER `blocked`;
""");
addAction("""
ALTER TABLE `right`
DROP `value`;
""");
addAction("""
ALTER TABLE `right` ADD `value` JSON NOT NULL AFTER `rightDescriptionId`;
""");
addAction((final DBAccess da) -> {
final List<Right> datas = da.gets(Right.class, new AccessDeletedItems());
for (final Right elem : datas) {
elem.value = PartRight.READ_WRITE;
}
for (final Right elem : datas) {
da.update(elem, elem.id, List.of("value"));
}
});
}
}

View File

@ -0,0 +1,13 @@
package org.kar.karso.migration.model;
import java.util.UUID;
import org.bson.types.ObjectId;
import jakarta.persistence.Id;
public class OIDConversion {
@Id
public UUID uuid = null;
public ObjectId _id = null;
}

View File

@ -1,21 +1,11 @@
package org.kar.karso.model; package org.kar.karso.model;
/*
CREATE TABLE `application` (
`id` bigint NOT NULL COMMENT 'Unique ID of the application' AUTO_INCREMENT PRIMARY KEY,
`description` text COMMENT 'description of the application',
`token` varchar(128) COLLATE 'latin1_bin' NOT NULL COMMENT 'Token (can be not unique)'
) AUTO_INCREMENT=10;
*/
import org.kar.archidata.annotation.DataComment;
import org.kar.archidata.annotation.DataIfNotExists; import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.model.GenericDataSoftDelete; import org.kar.archidata.model.GenericDataSoftDelete;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.DefaultValue;
@ -37,11 +27,11 @@ public class Application extends GenericDataSoftDelete {
@DefaultValue("'http://localhost:4200/sso/notification'") @DefaultValue("'http://localhost:4200/sso/notification'")
public String notification; public String notification;
@Column(nullable = false) @Column(nullable = false)
@DataComment("Expiration time ") @Schema(description = "Expiration time ")
@DefaultValue("666") @DefaultValue("666")
public Integer ttl; public Integer ttl;
@Column(nullable = false) @Column(nullable = false)
@DataComment("Right is manage with Karso") @Schema(description = "Right is manage with Karso")
@DefaultValue("0") @DefaultValue("0")
public Boolean manageRight; public Boolean manageRight;

View File

@ -1,19 +1,39 @@
package org.kar.karso.model; package org.kar.karso.model;
import org.kar.archidata.checker.CheckJPA;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.persistence.Column; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public class ChangePassword { public class ChangePassword {
@Column(length = 32) @NotNull
@Size(min = 2, max = 2)
@Pattern(regexp = "^v1$")
public String method; public String method;
@Column(length = 512) @NotNull
@Size(min = 3, max = 128)
@Pattern(regexp = "^[a-zA-Z0-9\\-_ \\.@]+$")
public String login; public String login;
@Column(length = 64) @NotNull
@Size(min = 20, max = 64)
@Pattern(regexp = "^\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})+.*$")
public String time; public String time;
@Column(length = 128) @NotNull
@Size(min = 128, max = 128)
@Pattern(regexp = "^[a-zA-Z0-9]{128}$")
public String password; public String password;
@Column(length = 128) @NotNull
@Size(min = 128, max = 128)
@Pattern(regexp = "^[a-zA-Z0-9]{128}$")
public String newPassword; public String newPassword;
public static class ChangePasswordChecker extends CheckJPA<ChangePassword> {
public ChangePasswordChecker() {
super(ChangePassword.class);
}
}
} }

View File

@ -3,21 +3,53 @@ package org.kar.karso.model;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import org.kar.archidata.checker.CheckJPA;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL) import jakarta.validation.constraints.NotNull;
public record DataGetToken( import jakarta.validation.constraints.Pattern;
String login, import jakarta.validation.constraints.Size;
String method,
String time,
String password) {
/*public DataGetToken(String login, String method, @JsonInclude(JsonInclude.Include.NON_NULL)
String time, public class DataGetToken {
String password) { @NotNull
this(login, method, time, password); @Size(min = 3, max = 128)
}*/ @Pattern(regexp = "^[a-zA-Z0-9\\-_ \\.@]+$")
public String login;
@NotNull
@Size(min = 2, max = 2)
@Pattern(regexp = "^v1$")
public String method;
@NotNull
@Size(min = 20, max = 64)
@Pattern(regexp = "^\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})+.*$")
public String time;
@NotNull
@Size(min = 128, max = 128)
@Pattern(regexp = "^[a-zA-Z0-9]{128}$")
public String password;
public static class DataGetTokenChecker extends CheckJPA<DataGetToken> {
public DataGetTokenChecker() {
super(DataGetToken.class);
}
}
public DataGetToken(final String login, final String password, final String time, final String method) {
this.method = method;
this.login = login;
this.time = time;
this.password = password;
}
public DataGetToken() {
}
public static String sha512(final String passwordToHash) { //, String salt){ public static String sha512(final String passwordToHash) { //, String salt){
String generatedPassword = null; String generatedPassword = null;
@ -36,20 +68,28 @@ public record DataGetToken(
return generatedPassword; return generatedPassword;
} }
public static DataGetToken generate(final String login, final String password) {
return generate(login, password, ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT));
}
public static DataGetToken generate(final String login, final String password, final String time) {
return generate(login, password, time, "v1");
}
public static DataGetToken generate( public static DataGetToken generate(
final String login, final String login,
final String method, final String password,
final String time, final String time,
final String password) { final String method) {
return generateSha(login, method, time, sha512(password)); return generateSha(login, sha512(password), time, method);
} }
public static DataGetToken generateSha( public static DataGetToken generateSha(
final String login, final String login,
final String method, final String password,
final String time, final String time,
final String password) { final String method) {
return new DataGetToken(login, method, time, return new DataGetToken(login, sha512("login='" + login + "';pass='" + password + "';date='" + time + "'"),
sha512("login='" + login + "';pass='" + password + "';date='" + time + "'")); time, method);
} }
} }

View File

@ -1,6 +1,8 @@
package org.kar.karso.model; package org.kar.karso.model;
import org.kar.archidata.annotation.DataIfNotExists; import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.filter.PartRight;
import org.kar.archidata.model.GenericDataSoftDelete; import org.kar.archidata.model.GenericDataSoftDelete;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
@ -15,7 +17,6 @@ import jakarta.persistence.Table;
@DataIfNotExists @DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public class Right extends GenericDataSoftDelete { public class Right extends GenericDataSoftDelete {
@Column(nullable = false) @Column(nullable = false)
@Schema(description = "application-ID that have the reference of the right") @Schema(description = "application-ID that have the reference of the right")
@ManyToOne(fetch = FetchType.LAZY, targetEntity = Application.class) @ManyToOne(fetch = FetchType.LAZY, targetEntity = Application.class)
@ -30,5 +31,6 @@ public class Right extends GenericDataSoftDelete {
public Long rightDescriptionId; public Long rightDescriptionId;
@Column(length = 1024, nullable = false) @Column(length = 1024, nullable = false)
@Schema(description = "Value of the right") @Schema(description = "Value of the right")
public String value; @DataJson
public PartRight value;
} }

View File

@ -10,7 +10,6 @@ import jakarta.persistence.Column;
import jakarta.persistence.FetchType; import jakarta.persistence.FetchType;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.ws.rs.DefaultValue;
@Table(name = "rightDescription") @Table(name = "rightDescription")
@DataIfNotExists @DataIfNotExists
@ -29,11 +28,4 @@ public class RightDescription extends GenericDataSoftDelete {
@Column(length = 1024, nullable = false) @Column(length = 1024, nullable = false)
@Schema(description = "Description of the right") @Schema(description = "Description of the right")
public String description; public String description;
@Column(length = 1024)
@Schema(description = "default value if Never set")
public String defaultValue;
@Column(length = 16, nullable = false)
@Schema(description = "Type of the property")
@DefaultValue("\"BOOLEAN\"")
public String type = "BOOLEAN"; // this is a place-holder (current type supported BOOLEAN)
} }

View File

@ -4,14 +4,18 @@ import java.sql.Timestamp;
import java.util.List; import java.util.List;
import org.kar.archidata.annotation.DataIfNotExists; import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.checker.CheckJPA;
import org.kar.archidata.model.User; import org.kar.archidata.model.User;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.ManyToMany; import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.DefaultValue;
@Table(name = "user") @Table(name = "user")
@ -19,6 +23,9 @@ import jakarta.ws.rs.DefaultValue;
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public class UserAuth extends User { public class UserAuth extends User {
@Column(length = 128, nullable = false) @Column(length = 128, nullable = false)
@Size(min = 128, max = 128)
@Pattern(regexp = "^[a-zA-Z0-9]{128}$")
@Nullable
public String password; public String password;
/* /*
@Column(length = 128) @Column(length = 128)
@ -27,9 +34,13 @@ public class UserAuth extends User {
public String passwordValidation; //!< UniqueId to validate the new password public String passwordValidation; //!< UniqueId to validate the new password
*/ */
@Column(length = 512, nullable = false) @Column(length = 512, nullable = false)
@Size(min = 5, max = 128)
@Pattern(regexp = "^[a-zA-Z0-9\\-\\._]+@[a-zA-Z0-9\\-_]+\\.[a-zA-Z0-9]+$")
public String email; public String email;
public Timestamp emailValidate; // time of validation public Timestamp emailValidate; // time of validation
@Column(length = 512) @Column(length = 512)
@Size(min = 5, max = 128)
@Pattern(regexp = "^[a-zA-Z0-9\\-\\._]+@[a-zA-Z0-9\\-_]+\\.[a-zA-Z0-9]+$")
public String newEmail; public String newEmail;
@DefaultValue("'0'") @DefaultValue("'0'")
@Column(nullable = false) @Column(nullable = false)
@ -38,4 +49,10 @@ public class UserAuth extends User {
@ManyToMany(targetEntity = Application.class) @ManyToMany(targetEntity = Application.class)
public List<Long> applications = null; public List<Long> applications = null;
public static class UserAuthChecker extends CheckJPA<UserAuth> {
public UserAuthChecker() {
super(UserAuth.class);
}
}
} }

View File

@ -1,10 +1,32 @@
package org.kar.karso.model; package org.kar.karso.model;
import org.kar.archidata.checker.CheckJPA;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public class UserCreate { public class UserCreate {
@NotNull
@Size(min = 3, max = 128)
@Pattern(regexp = "^[a-zA-Z0-9\\-_ \\.@]+$")
public String login; public String login;
@NotNull
@Size(min = 5, max = 128)
@Email()
public String email; public String email;
@NotNull
@Size(min = 128, max = 128)
@Pattern(regexp = "^[a-zA-Z0-9]{128}$")
public String password; public String password;
public static class UserCreateChecker extends CheckJPA<UserCreate> {
public UserCreateChecker() {
super(UserCreate.class);
}
}
} }

View File

@ -11,7 +11,7 @@ CREATE TABLE `application` (
*/ */
import org.kar.archidata.annotation.DataIfNotExists; import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.model.UUIDGenericDataSoftDelete; import org.kar.archidata.model.OIDGenericDataSoftDelete;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
@ -22,7 +22,7 @@ import jakarta.persistence.Table;
@Table(name = "user_link_application") @Table(name = "user_link_application")
@DataIfNotExists @DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public class UserLinkApplication extends UUIDGenericDataSoftDelete { public class UserLinkApplication extends OIDGenericDataSoftDelete {
@Column(name = "object1id") @Column(name = "object1id")
public Long userId; public Long userId;
@Column(name = "object2id") @Column(name = "object2id")

View File

@ -0,0 +1,13 @@
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern name="ConversionPattern">
%green(%d{HH:mm:ss.SSS}) %highlight(%-5level) %-30((%file:%line\)): %msg%n
</pattern>
</encoder>
</appender>
<root level="trace">
<appender-ref ref="CONSOLE" />
</root>
</configuration>

View File

@ -1,35 +0,0 @@
# SLF4J's SimpleLogger configuration file
# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err.
# Default logging detail level for all instances of SimpleLogger.
# Must be one of ("trace", "debug", "info", "warn", or "error").
# If not specified, defaults to "info".
org.slf4j.simpleLogger.defaultLogLevel=trace
# Logging detail level for a SimpleLogger instance named "xxxxx".
# Must be one of ("trace", "debug", "info", "warn", or "error").
# If not specified, the default logging detail level is used.
#org.slf4j.simpleLogger.log.xxxxx=
# Set to true if you want the current date and time to be included in output messages.
# Default is false, and will output the number of milliseconds elapsed since startup.
#org.slf4j.simpleLogger.showDateTime=false
# The date and time format to be used in the output messages.
# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat.
# If the format is not specified or is invalid, the default format is used.
# The default format is yyyy-MM-dd HH:mm:ss:SSS Z.
#org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z
# Set to true if you want to output the current thread name.
# Defaults to true.
org.slf4j.simpleLogger.showThreadName=true
# Set to true if you want the Logger instance name to be included in output messages.
# Defaults to true.
#org.slf4j.simpleLogger.showLogName=true
# Set to true if you want the last component of the name to be included in output messages.
# Defaults to false.
#org.slf4j.simpleLogger.showShortLogName=false

View File

@ -0,0 +1,126 @@
package test.kar.karso;
import java.io.IOException;
import java.util.List;
import org.kar.archidata.dataAccess.DBAccess;
import org.kar.archidata.db.DbConfig;
import org.kar.archidata.db.DbIoFactory;
import org.kar.archidata.exception.DataAccessException;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.kar.karso.api.RightResource;
import org.kar.karso.model.Application;
import org.kar.karso.model.Right;
import org.kar.karso.model.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.ws.rs.InternalServerErrorException;
public class ConfigureDb {
final static private Logger LOGGER = LoggerFactory.getLogger(ConfigureDb.class);
final static private String modeTestForced = null;// "MONGO";
public static DBAccess da = null;
public static void configure() throws IOException, InternalServerErrorException, DataAccessException {
String modeTest = System.getenv("TEST_E2E_MODE");
if (modeTest == null || modeTest.isEmpty() || "false".equalsIgnoreCase(modeTest)) {
modeTest = "SQLITE-MEMORY";
} else if ("true".equalsIgnoreCase(modeTest)) {
modeTest = "MY-SQL";
}
// override the local test:
if (modeTestForced != null) {
modeTest = modeTestForced;
}
// for local test:
ConfigBaseVariable.apiAdress = "http://127.0.0.1:12342/test/api/";
// Enable the test mode permit to access to the test token (never use it in production).
ConfigBaseVariable.testMode = "true";
final List<Class<?>> listObject = List.of( //
Application.class, //
RightResource.class, //
Right.class, //
Settings.class //
);
if ("SQLITE-MEMORY".equalsIgnoreCase(modeTest)) {
ConfigBaseVariable.dbType = "sqlite";
ConfigBaseVariable.bdDatabase = null;
ConfigBaseVariable.dbHost = "memory";
// for test we need to connect all time the DB
ConfigBaseVariable.dbKeepConnected = "true";
} else if ("SQLITE".equalsIgnoreCase(modeTest)) {
ConfigBaseVariable.dbType = "sqlite";
ConfigBaseVariable.bdDatabase = null;
ConfigBaseVariable.dbKeepConnected = "true";
} else if ("MY-SQL".equalsIgnoreCase(modeTest)) {
ConfigBaseVariable.dbType = "mysql";
ConfigBaseVariable.bdDatabase = "test_karso_db";
ConfigBaseVariable.dbPort = "3906";
ConfigBaseVariable.dbUser = "root";
} else if ("MONGO".equalsIgnoreCase(modeTest)) {
ConfigBaseVariable.dbType = "mongo";
ConfigBaseVariable.bdDatabase = "test_karso_db";
} else {
// User local modification ...
ConfigBaseVariable.bdDatabase = "test_karso_db";
ConfigBaseVariable.dbPort = "3906";
ConfigBaseVariable.dbUser = "root";
}
removeDB();
// Connect the dataBase...
da = DBAccess.createInterface();
}
public static void removeDB() {
String modeTest = System.getenv("TEST_E2E_MODE");
if (modeTest == null || modeTest.isEmpty() || "false".equalsIgnoreCase(modeTest)) {
modeTest = "SQLITE-MEMORY";
} else if ("true".equalsIgnoreCase(modeTest)) {
modeTest = "MY-SQL";
}
// override the local test:
if (modeTestForced != null) {
modeTest = modeTestForced;
}
DbConfig config = null;
try {
config = new DbConfig();
} catch (final DataAccessException e) {
e.printStackTrace();
LOGGER.error("Fail to clean the DB");
return;
}
config.setDbName(null);
LOGGER.info("Remove the DB and create a new one '{}'", config.getDbName());
try (final DBAccess daRoot = DBAccess.createInterface(config)) {
if ("SQLITE-MEMORY".equalsIgnoreCase(modeTest)) {
// nothing to do ...
} else if ("SQLITE".equalsIgnoreCase(modeTest)) {
daRoot.deleteDB(ConfigBaseVariable.bdDatabase);
} else if ("MY-SQL".equalsIgnoreCase(modeTest)) {
daRoot.deleteDB(ConfigBaseVariable.bdDatabase);
} else if ("MONGO".equalsIgnoreCase(modeTest)) {
daRoot.deleteDB(ConfigBaseVariable.bdDatabase);
}
daRoot.createDB(ConfigBaseVariable.bdDatabase);
} catch (final InternalServerErrorException e) {
e.printStackTrace();
LOGGER.error("Fail to clean the DB");
return;
} catch (final IOException e) {
e.printStackTrace();
LOGGER.error("Fail to clean the DB");
return;
}
}
public static void clear() throws IOException {
LOGGER.info("Remove the test db");
removeDB();
// The connection is by default open ==> close it at the end of test:
da.close();
DbIoFactory.closeAllForceMode();
ConfigBaseVariable.clearAllValue();
}
}

View File

@ -11,7 +11,8 @@ import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.kar.archidata.db.DBEntry; import org.kar.archidata.filter.PartRight;
import org.kar.archidata.filter.RightSafeCaster;
import org.kar.archidata.model.GetToken; import org.kar.archidata.model.GetToken;
import org.kar.archidata.tools.ConfigBaseVariable; import org.kar.archidata.tools.ConfigBaseVariable;
import org.kar.archidata.tools.JWTWrapper; import org.kar.archidata.tools.JWTWrapper;
@ -32,8 +33,7 @@ public class TestBase {
public void login(final String login, final String password) { public void login(final String login, final String password) {
try { try {
final GetToken token = api.post(GetToken.class, "users/get_token", final GetToken token = api.post(GetToken.class, "users/get_token", DataGetToken.generate(login, password));
DataGetToken.generate(login, "v1", "202515252", password));
api.setToken(token.jwt); api.setToken(token.jwt);
} catch (final Exception ex) { } catch (final Exception ex) {
Assertions.fail("Can not get Authentication for '" + login + "' ==> " + ex.getMessage()); Assertions.fail("Can not get Authentication for '" + login + "' ==> " + ex.getMessage());
@ -46,15 +46,10 @@ public class TestBase {
@BeforeAll @BeforeAll
public static void configureWebServer() throws Exception { public static void configureWebServer() throws Exception {
ConfigureDb.configure();
LOGGER.info("configure server ..."); LOGGER.info("configure server ...");
webInterface = new WebLauncherTest(); webInterface = new WebLauncherTest();
LOGGER.info("Create DB"); webInterface.migrateDB();
try {
webInterface.migrateDB();
} catch (final Exception ex) {
ex.printStackTrace();
LOGGER.error("Detect an error: {}", ex.getMessage());
}
LOGGER.info("Start REST (BEGIN)"); LOGGER.info("Start REST (BEGIN)");
webInterface.process(); webInterface.process();
LOGGER.info("Start REST (DONE)"); LOGGER.info("Start REST (DONE)");
@ -66,17 +61,14 @@ public class TestBase {
LOGGER.info("Kill the web server"); LOGGER.info("Kill the web server");
webInterface.stop(); webInterface.stop();
webInterface = null; webInterface = null;
LOGGER.info("Remove the test db"); ConfigureDb.clear();
DBEntry.closeAllForceMode();
ConfigBaseVariable.clearAllValue();
Thread.sleep(1000);
} }
@Order(3) @Order(3)
@Test @Test
public void firstUserConnect() throws Exception { public void firstUserConnect() throws Exception {
final GetToken result = api.post(GetToken.class, "users/get_token", final GetToken result = api.post(GetToken.class, "users/get_token",
DataGetToken.generate("karadmin", "v1", "202515252", "adminA@666")); DataGetToken.generate("karadmin", "adminA@666", "2025-15-25T22:32:23.252Z"));
final String[] splitted = result.jwt.split("\\."); final String[] splitted = result.jwt.split("\\.");
Assertions.assertEquals(3, splitted.length); Assertions.assertEquals(3, splitted.length);
final String authorization = result.jwt; final String authorization = result.jwt;
@ -94,17 +86,17 @@ public class TestBase {
final Object rowRight = ret.getClaim("right"); final Object rowRight = ret.getClaim("right");
Assertions.assertNotNull(rowRight); Assertions.assertNotNull(rowRight);
final Map<String, Map<String, Object>> rights = (Map<String, Map<String, Object>>) ret.getClaim("right"); final Map<String, Map<String, PartRight>> rights = RightSafeCaster.safeCastAndTransform(ret.getClaim("right"));
// Check if the element contain the basic keys: // Check if the element contain the basic keys:
Assertions.assertEquals(rights.size(), 1); Assertions.assertEquals(rights.size(), 1);
Assertions.assertTrue(rights.containsKey("karso")); Assertions.assertTrue(rights.containsKey("karso"));
final Map<String, Object> applRight = rights.get("karso"); final Map<String, PartRight> applRight = rights.get("karso");
//logger.error("full right: {}", applRight); //logger.error("full right: {}", applRight);
Assertions.assertEquals(applRight.size(), 2); Assertions.assertEquals(applRight.size(), 2);
Assertions.assertTrue(applRight.containsKey("ADMIN")); Assertions.assertTrue(applRight.containsKey("ADMIN"));
Assertions.assertEquals(true, applRight.get("ADMIN")); Assertions.assertEquals(PartRight.READ_WRITE, applRight.get("ADMIN"));
Assertions.assertTrue(applRight.containsKey("USER")); Assertions.assertTrue(applRight.containsKey("USER"));
Assertions.assertEquals(true, applRight.get("USER")); Assertions.assertEquals(PartRight.READ_WRITE, applRight.get("USER"));
//logger.debug("request user: '{}' right: '{}' row='{}'", userUID, applRight, rowRight); //logger.debug("request user: '{}' right: '{}' row='{}'", userUID, applRight, rowRight);

View File

@ -10,8 +10,7 @@ import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.kar.archidata.db.DBEntry; import org.kar.archidata.exception.RESTErrorResponseException;
import org.kar.archidata.exception.RESTErrorResponseExeption;
import org.kar.archidata.tools.ConfigBaseVariable; import org.kar.archidata.tools.ConfigBaseVariable;
import org.kar.archidata.tools.RESTApi; import org.kar.archidata.tools.RESTApi;
import org.kar.karso.api.HealthCheck.HealthResult; import org.kar.karso.api.HealthCheck.HealthResult;
@ -28,15 +27,10 @@ public class TestHealthCheck {
@BeforeAll @BeforeAll
public static void configureWebServer() throws Exception { public static void configureWebServer() throws Exception {
ConfigureDb.configure();
LOGGER.info("configure server ..."); LOGGER.info("configure server ...");
webInterface = new WebLauncherTest(); webInterface = new WebLauncherTest();
LOGGER.info("Create DB"); webInterface.migrateDB();
try {
webInterface.migrateDB();
} catch (final Exception ex) {
ex.printStackTrace();
LOGGER.error("Detect an error: {}", ex.getMessage());
}
LOGGER.info("Start REST (BEGIN)"); LOGGER.info("Start REST (BEGIN)");
webInterface.process(); webInterface.process();
LOGGER.info("Start REST (DONE)"); LOGGER.info("Start REST (DONE)");
@ -48,10 +42,7 @@ public class TestHealthCheck {
LOGGER.info("Kill the web server"); LOGGER.info("Kill the web server");
webInterface.stop(); webInterface.stop();
webInterface = null; webInterface = null;
LOGGER.info("Remove the test db"); ConfigureDb.clear();
DBEntry.closeAllForceMode();
ConfigBaseVariable.clearAllValue();
Thread.sleep(1000);
} }
@Order(1) @Order(1)
@ -65,7 +56,7 @@ public class TestHealthCheck {
@Order(2) @Order(2)
@Test @Test
public void checkHealthCheckWrongAPI() throws Exception { public void checkHealthCheckWrongAPI() throws Exception {
Assertions.assertThrows(RESTErrorResponseExeption.class, () -> api.get(HealthResult.class, "health_checks")); Assertions.assertThrows(RESTErrorResponseException.class, () -> api.get(HealthResult.class, "health_checks"));
} }
} }

View File

@ -9,8 +9,7 @@ import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestMethodOrder;
import org.kar.archidata.db.DBEntry; import org.kar.archidata.exception.RESTErrorResponseException;
import org.kar.archidata.exception.RESTErrorResponseExeption;
import org.kar.archidata.model.GetToken; import org.kar.archidata.model.GetToken;
import org.kar.archidata.tools.ConfigBaseVariable; import org.kar.archidata.tools.ConfigBaseVariable;
import org.kar.archidata.tools.RESTApi; import org.kar.archidata.tools.RESTApi;
@ -27,8 +26,7 @@ public class TestUnAuthorizedAPI {
public void login(final String login, final String password) { public void login(final String login, final String password) {
try { try {
final GetToken token = api.post(GetToken.class, "users/get_token", final GetToken token = api.post(GetToken.class, "users/get_token", DataGetToken.generate(login, password));
DataGetToken.generate(login, "v1", "202515252", password));
api.setToken(token.jwt); api.setToken(token.jwt);
} catch (final Exception ex) { } catch (final Exception ex) {
Assertions.fail("Can not get Authentication for '" + login + "' ==> " + ex.getMessage()); Assertions.fail("Can not get Authentication for '" + login + "' ==> " + ex.getMessage());
@ -41,15 +39,10 @@ public class TestUnAuthorizedAPI {
@BeforeAll @BeforeAll
public static void configureWebServer() throws Exception { public static void configureWebServer() throws Exception {
ConfigureDb.configure();
LOGGER.info("configure server ..."); LOGGER.info("configure server ...");
webInterface = new WebLauncherTest(); webInterface = new WebLauncherTest();
LOGGER.info("Create DB"); webInterface.migrateDB();
try {
webInterface.migrateDB();
} catch (final Exception ex) {
ex.printStackTrace();
LOGGER.error("Detect an error: {}", ex.getMessage());
}
LOGGER.info("Start REST (BEGIN)"); LOGGER.info("Start REST (BEGIN)");
webInterface.process(); webInterface.process();
LOGGER.info("Start REST (DONE)"); LOGGER.info("Start REST (DONE)");
@ -61,10 +54,7 @@ public class TestUnAuthorizedAPI {
LOGGER.info("Kill the web server"); LOGGER.info("Kill the web server");
webInterface.stop(); webInterface.stop();
webInterface = null; webInterface = null;
LOGGER.info("Remove the test db"); ConfigureDb.clear();
DBEntry.closeAllForceMode();
ConfigBaseVariable.clearAllValue();
Thread.sleep(1000);
} }
public void checkFail(final String type, final String urlOffset, final int errorStatus) { public void checkFail(final String type, final String urlOffset, final int errorStatus) {
@ -84,7 +74,7 @@ public class TestUnAuthorizedAPI {
api.delete(String.class, urlOffset); api.delete(String.class, urlOffset);
} }
Assertions.fail("Request on URL does not fail as expected: '" + type + "' url='" + urlOffset + "'"); Assertions.fail("Request on URL does not fail as expected: '" + type + "' url='" + urlOffset + "'");
} catch (final RESTErrorResponseExeption ex) { } catch (final RESTErrorResponseException ex) {
if (errorStatus != ex.status) { if (errorStatus != ex.status) {
LOGGER.error("Fail in test with the wrong return errors: {}", ex.toString()); LOGGER.error("Fail in test with the wrong return errors: {}", ex.toString());
} }
@ -113,7 +103,7 @@ public class TestUnAuthorizedAPI {
api.delete(String.class, urlOffset); api.delete(String.class, urlOffset);
} }
//Assertions.fail("Request on URL does not fail as expected: '" + type + "' url='" + urlOffset + "'"); //Assertions.fail("Request on URL does not fail as expected: '" + type + "' url='" + urlOffset + "'");
} catch (final RESTErrorResponseExeption ex) { } catch (final RESTErrorResponseException ex) {
Assertions.fail("Must not fail ... " + ex.toString()); Assertions.fail("Must not fail ... " + ex.toString());
} catch (final Exception ex) { } catch (final Exception ex) {
LOGGER.error("Unexpected throw error: {}", ex); LOGGER.error("Unexpected throw error: {}", ex);

View File

@ -11,12 +11,10 @@ import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.kar.archidata.db.DBEntry; import org.kar.archidata.exception.RESTErrorResponseException;
import org.kar.archidata.exception.RESTErrorResponseExeption;
import org.kar.archidata.model.GetToken; import org.kar.archidata.model.GetToken;
import org.kar.archidata.tools.ConfigBaseVariable; import org.kar.archidata.tools.ConfigBaseVariable;
import org.kar.archidata.tools.RESTApi; import org.kar.archidata.tools.RESTApi;
import org.kar.karso.migration.Initialization;
import org.kar.karso.model.DataGetToken; import org.kar.karso.model.DataGetToken;
import org.kar.karso.model.UserAuthGet; import org.kar.karso.model.UserAuthGet;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -34,32 +32,17 @@ public class TestUsers {
private static long idTest; private static long idTest;
@BeforeAll @BeforeAll
public static void configureWebServer() throws InterruptedException, RESTErrorResponseExeption, IOException { public static void configureWebServer() throws Exception {
ConfigureDb.configure();
LOGGER.info("configure server ..."); LOGGER.info("configure server ...");
webInterface = new WebLauncherTest(); webInterface = new WebLauncherTest();
LOGGER.info("Clean previous table"); webInterface.migrateDB();
try {
Initialization.dropAll();
} catch (final Exception ex) {
ex.printStackTrace();
LOGGER.error("plop: {}", ex.getLocalizedMessage());
throw ex;
}
LOGGER.info("Create DB");
try {
webInterface.migrateDB();
} catch (final Exception ex) {
ex.printStackTrace();
LOGGER.error("Detect an error: {}", ex.getMessage());
}
LOGGER.info("Start REST (BEGIN)"); LOGGER.info("Start REST (BEGIN)");
webInterface.process(); webInterface.process();
LOGGER.info("Start REST (DONE)"); LOGGER.info("Start REST (DONE)");
api = new RESTApi(ConfigBaseVariable.apiAdress); api = new RESTApi(ConfigBaseVariable.apiAdress);
final GetToken result = api.post(GetToken.class, "users/get_token", final GetToken result = api.post(GetToken.class, "users/get_token",
DataGetToken.generate("karadmin", "v1", "202515252", "adminA@666")); DataGetToken.generate("karadmin", "adminA@666"));
api.setToken(result.jwt); api.setToken(result.jwt);
} }
@ -68,19 +51,16 @@ public class TestUsers {
LOGGER.info("Kill the web server"); LOGGER.info("Kill the web server");
webInterface.stop(); webInterface.stop();
webInterface = null; webInterface = null;
LOGGER.info("Remove the test db"); ConfigureDb.clear();
DBEntry.closeAllForceMode();
ConfigBaseVariable.clearAllValue();
} }
@Order(1) @Order(1)
@Test @Test
public void getsValue() throws RESTErrorResponseExeption, IOException, InterruptedException { public void getsValue() throws RESTErrorResponseException, IOException, InterruptedException {
final List<UserAuthGet> listUsers = api.gets(UserAuthGet.class, TestUsers.ENDPOINT_NAME); final List<UserAuthGet> listUsers = api.gets(UserAuthGet.class, TestUsers.ENDPOINT_NAME);
Assertions.assertNotNull(listUsers); Assertions.assertNotNull(listUsers);
Assertions.assertEquals(1, listUsers.size()); Assertions.assertEquals(1, listUsers.size());
Assertions.assertEquals(1, listUsers.get(0).id); Assertions.assertEquals(1, listUsers.get(0).id);
Assertions.assertEquals(true, listUsers.get(0).admin);
Assertions.assertEquals(false, listUsers.get(0).blocked); Assertions.assertEquals(false, listUsers.get(0).blocked);
Assertions.assertEquals(false, listUsers.get(0).avatar); Assertions.assertEquals(false, listUsers.get(0).avatar);
Assertions.assertEquals("karadmin", listUsers.get(0).login); Assertions.assertEquals("karadmin", listUsers.get(0).login);

View File

@ -1,33 +1,12 @@
package test.kar.karso; package test.kar.karso;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.kar.karso.WebLauncher; import org.kar.karso.WebLauncher;
import org.kar.karso.util.ConfigVariable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
public class WebLauncherTest extends WebLauncher { public class WebLauncherTest extends WebLauncher {
final private static Logger LOGGER = LoggerFactory.getLogger(WebLauncherTest.class); final private static Logger LOGGER = LoggerFactory.getLogger(WebLauncherTest.class);
public WebLauncherTest() { public WebLauncherTest() {}
LOGGER.debug("Configure REST system");
// for local test:
ConfigBaseVariable.apiAdress = "http://127.0.0.1:12345/test/api/";
//ConfigBaseVariable.dbPort = "3306";
// create a unique key for test ==> not retrieve the token every load...
ConfigVariable.uuid_for_key_generation = "lkjlkjlkjlmkjqmwlsdkjqfsdlkf,nmQLSDK,NFMQLKSdjmlKQJSDMLQK,S;ndmLQKZNERMA,ÉL";
if (!"true".equalsIgnoreCase(System.getenv("TEST_E2E_MODE"))) {
ConfigBaseVariable.dbType = "sqlite";
ConfigBaseVariable.dbHost = "memory";
// for test we need to connect all time the DB
ConfigBaseVariable.dbKeepConnected = "true";
}
//ConfigBaseVariable.dbHost = "localhost";
//ConfigBaseVariable.dbUser = "root";
//ConfigBaseVariable.dbPassword = "ZERTYSDGFVHSDFGHJYZSDFGSQxfgsqdfgsqdrf4564654";
}
} }

View File

@ -0,0 +1,56 @@
services:
kar_db_service:
image: mysql:latest
restart: always
environment:
- MYSQL_ROOT_PASSWORD=base_db_password
volumes:
- ./data:/var/lib/mysql
mem_limit: 300m
ports:
- 3906:3306
healthcheck:
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
timeout: 10s
retries: 5
# perform a 1 minute grace to let the DB to perform the initialization
start_period: 1m
start_interval: 1m
kar_mongodb_service:
image: mongo:latest
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: base_db_password
ports:
- 27017:27017
volumes:
- ./dataMongo:/data/db
kar_adminer_service:
image: adminer:latest
restart: always
depends_on:
kar_db_service:
condition: service_healthy
links:
- kar_db_service:db
- kar_mongodb_service:dbm
ports:
- 4079:8080
mem_limit: 50m
mongo_express_service:
image: mongo-express
restart: always
ports:
- 4077:8081
links:
- kar_mongodb_service:db
environment:
ME_CONFIG_MONGODB_ADMINUSERNAME: root
ME_CONFIG_MONGODB_ADMINPASSWORD: base_db_password
ME_CONFIG_MONGODB_URL: mongodb://root:base_db_password@db:27017/
ME_CONFIG_BASICAUTH: false

2
front/.env.production Normal file
View File

@ -0,0 +1,2 @@
# URL for database connection
VITE_API_BASE_URL=karso/api/

27
front/.storybook/main.ts Normal file
View File

@ -0,0 +1,27 @@
import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
framework: {
name: '@storybook/react-vite',
options: {},
},
core: {
disableTelemetry: true,
builder: '@storybook/builder-vite',
},
stories: ['../src/**/*.@(mdx|stories.@(js|jsx|ts|tsx))'],
addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
staticDirs: ['../public'],
typescript: {
reactDocgen: false,
},
docs: {},
};
export default config;

View File

@ -0,0 +1,16 @@
<style>
html {
background: transparent !important;
}
.docs-story > :first-child {
padding: 0;
}
.docs-story > * {
background: transparent !important;
}
#root #start-ui-storybook-wrapper {
min-height: 100vh;
}
</style>

View File

@ -0,0 +1,34 @@
import React from 'react';
import { Box } from '@chakra-ui/react';
import { ChakraProvider } from '@chakra-ui/react';
import { MemoryRouter } from 'react-router-dom';
import { ColorModeProvider } from '../src/components/ui/color-mode';
import { Toaster } from '../src/components/ui/toaster';
import { systemTheme } from '../src/theme/theme';
// .
const DocumentationWrapper = ({ children }) => {
return (
<Box id="start-ui-storybook-wrapper" p="4" pb="8" flex="1">
{children}
</Box>
);
};
export const decorators = [
(Story, context) => (
<ColorModeProvider>
<ChakraProvider value={systemTheme}>
{/* Using MemoryRouter to avoid route clashing with Storybook */}
<MemoryRouter>
<DocumentationWrapper>
<Story {...context} />
</DocumentationWrapper>
</MemoryRouter>
<Toaster />
</ChakraProvider>
</ColorModeProvider>
),
];

2
front/LICENSE Normal file
View File

@ -0,0 +1,2 @@
Proprietary
@copyright Edouard Dupin 2024

10888
front/config sample.ts Normal file

File diff suppressed because it is too large Load Diff

13
front/index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>karso</title>
<link rel="icon" href="/favicon.ico" />
</head>
<body style="width:100vw;height:100vh;min-width:100%;min-height:100%;">
<div id="root" style="width:100%;height:100%;min-width:100%;min-height:100%;"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

9
front/knip.ts Normal file
View File

@ -0,0 +1,9 @@
import type { KnipConfig } from 'knip';
const config: KnipConfig = {
// Ignoring mostly shell binaries
ignoreBinaries: ['export', 'sleep'],
ignore: [],
};
export default config;

View File

@ -1,64 +1,92 @@
{ {
"name": "karso", "name": "karso",
"version": "0.0.0", "private": true,
"license": "MPL-2", "version": "0.0.1",
"scripts": { "description": "KAR web SSO application",
"all": "npm run build && npm run test", "author": {
"ng": "ng", "name": "Edouard DUPIN",
"dev": "ng serve karso --configuration=develop --watch --port 4200", "email": "yui.heero@gmail.farm"
"dev-hot-update": "ng serve karso --configuration=develop --watch --hmr --port 4200", },
"dev_edge": "ng serve karso-edge --configuration=develop --watch --port 4199", "license": "PROPRIETARY",
"build": "ng build karso --prod", "engines": {
"test": "ng test karso", "node": ">=20"
"test-coverage": "ng test karso --code-coverage", },
"lint": "ng lint", "scripts": {
"style": "prettier --write .", "update_packages": "ncu --target minor",
"e2e": "ng e2e", "upgrade_packages": "ncu --upgrade ",
"update_packages": "ncu --upgrade", "install_dependency": "pnpm install",
"install_dependency": "pnpm install --force", "test": "vitest run",
"link_kar_cw": "pnpm link ../../kar-cw/dist/kar-cw/", "test:watch": "vitest watch",
"unlink_kar_cw": "pnpm unlink ../../kar-cw/dist/kar-cw/" "build": "tsc && vite build",
}, "static:build": "pnpm build",
"private": true, "dev": "vite",
"dependencies": { "pretty": "prettier -w .",
"@angular/animations": "^18.0.1", "lint": "pnpm tsc --noEmit",
"@angular/cdk": "^18.0.1", "storybook": "storybook dev -p 3001",
"@angular/common": "^18.0.1", "storybook:build": "storybook build && mv ./storybook-static ./public/storybook"
"@angular/compiler": "^18.0.1", },
"@angular/core": "^18.0.1", "lint-staged": {
"@angular/forms": "^18.0.1", "*.{ts,tsx,js,jsx,json}": "prettier --write"
"@angular/material": "^18.0.1", },
"@angular/platform-browser": "^18.0.1", "dependencies": {
"@angular/platform-browser-dynamic": "^18.0.1", "@trivago/prettier-plugin-sort-imports": "5.2.2",
"@angular/router": "^18.0.1", "@chakra-ui/cli": "3.7.0",
"rxjs": "^7.8.1", "@chakra-ui/react": "3.7.0",
"zone.js": "^0.14.6", "@emotion/react": "11.14.0",
"zod": "3.23.8", "allotment": "1.20.2",
"@kangaroo-and-rabbit/kar-cw": "^0.4.0" "css-mediaquery": "0.1.2",
}, "dayjs": "1.11.13",
"devDependencies": { "history": "5.3.0",
"@angular-devkit/build-angular": "^18.0.2", "next-themes": "^0.4.4",
"@angular-eslint/builder": "18.0.1", "react": "19.0.0",
"@angular-eslint/eslint-plugin": "18.0.1", "react-dom": "19.0.0",
"@angular-eslint/eslint-plugin-template": "18.0.1", "react-error-boundary": "5.0.0",
"@angular-eslint/schematics": "18.0.1", "react-icons": "5.4.0",
"@angular-eslint/template-parser": "18.0.1", "react-router-dom": "7.1.5",
"@angular/cli": "^18.0.2", "react-select": "5.10.0",
"@angular/compiler-cli": "^18.0.1", "react-use": "17.6.0",
"@angular/language-service": "^18.0.1", "zod": "3.24.1",
"@playwright/test": "^1.44.1", "zustand": "5.0.3"
"@types/jest": "^29.5.12", },
"jasmine": "^5.1.0", "devDependencies": {
"jasmine-core": "^5.1.2", "@chakra-ui/styled-system": "^2.12.0",
"karma": "^6.4.3", "@playwright/test": "1.50.1",
"karma-coverage": "^2.2.1", "@storybook/addon-actions": "8.5.3",
"karma-coverage-istanbul-reporter": "^3.0.3", "@storybook/addon-essentials": "8.5.3",
"karma-firefox-launcher": "^2.1.3", "@storybook/addon-links": "8.5.3",
"karma-jasmine": "^5.1.0", "@storybook/addon-mdx-gfm": "8.5.3",
"karma-jasmine-html-reporter": "^2.1.0", "@storybook/react": "8.5.3",
"karma-spec-reporter": "^0.0.36", "@storybook/react-vite": "8.5.3",
"prettier": "^3.3.0", "@storybook/theming": "8.5.3",
"npm-check-updates": "^16.14.20", "@testing-library/jest-dom": "6.6.3",
"tslib": "^2.6.2" "@testing-library/react": "16.2.0",
} "@testing-library/user-event": "14.6.1",
"@trivago/prettier-plugin-sort-imports": "5.2.2",
"@types/jest": "29.5.14",
"@types/node": "22.13.1",
"@types/react": "19.0.8",
"@types/react-dom": "19.0.3",
"@typescript-eslint/eslint-plugin": "8.23.0",
"@typescript-eslint/parser": "8.23.0",
"@vitejs/plugin-react": "4.3.4",
"eslint": "9.20.0",
"eslint-plugin-codeceptjs": "1.3.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-react": "7.37.4",
"eslint-plugin-react-hooks": "5.1.0",
"eslint-plugin-storybook": "0.11.2",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"knip": "5.43.6",
"lint-staged": "15.4.3",
"npm-check-updates": "^17.1.14",
"prettier": "3.4.2",
"puppeteer": "24.2.0",
"react-is": "19.0.0",
"storybook": "8.5.3",
"ts-node": "10.9.2",
"typescript": "5.7.3",
"vite": "6.1.0",
"vitest": "3.0.5"
}
} }

View File

18960
front/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

16
front/prettier.config.js Normal file
View File

@ -0,0 +1,16 @@
// Using a JS file, allowing us to add comments
module.exports = {
// This plugins line is mandatory for the plugin to work with pnpm.
// https://github.com/trivago/prettier-plugin-sort-imports/blob/61d069711008c530f5a41ca4e254781abc5de358/README.md?plain=1#L89-L96
plugins: ['@trivago/prettier-plugin-sort-imports'],
endOfLine: 'lf',
semi: true,
singleQuote: true,
tabWidth: 2,
trailingComma: 'es5',
arrowParens: 'always',
importOrder: ['^react$', '^(?!^react$|^@/|^[./]).*', '^@/(.*)$', '^[./]'],
importOrderSeparation: true,
importOrderSortSpecifiers: true,
importOrderParserPlugins: ['jsx', 'typescript'],
};

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

18
front/src/App.tsx Normal file
View File

@ -0,0 +1,18 @@
import { ErrorBoundary } from '@/errors/ErrorBoundary';
import { AppRoutes } from '@/scene/AppRoutes';
import { ServiceContextProvider } from '@/service/ServiceContext';
import { EnvDevelopment } from './components/EnvDevelopment/EnvDevelopment';
export const App = () => {
return (
<ServiceContextProvider>
<EnvDevelopment />
<ErrorBoundary>
<AppRoutes />
</ErrorBoundary>
</ServiceContextProvider>
);
};
export default App;

View File

@ -1,26 +1,70 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg <svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="256" width="256"
height="256" height="256"
viewBox="0 0 67.733333 67.733333" viewBox="0 0 67.733333 67.733333"
version="1.1" version="1.1"
id="svg8" id="svg18"
inkscape:version="0.92.4 5da689c313, 2019-01-14" sodipodi:docname="ikon_red.svg"
sodipodi:docname="ikon.svg" inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
inkscape:export-filename="/home/heero/dev/perso/appl_pro/NoKomment/plugin/chrome/ikon.png" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:export-xdpi="7.1250005" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
inkscape:export-ydpi="7.1250005"> xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs <defs
id="defs2" /> id="defs2">
<filter
style="color-interpolation-filters:sRGB;"
id="filter5338"
x="-0.12319682"
y="-0.081815216"
width="1.2463936"
height="1.1636304">
<feFlood
flood-opacity="1"
flood-color="rgb(165,29,45)"
result="flood"
id="feFlood5328" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite5330" />
<feGaussianBlur
in="composite1"
stdDeviation="2.1"
result="blur"
id="feGaussianBlur5332" />
<feOffset
dx="0"
dy="0"
result="offset"
id="feOffset5334" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="composite2"
id="feComposite5336" />
</filter>
<filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter1159"
x="-0.11802406"
width="1.2360481"
y="-0.078379973"
height="1.1567599">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="2.0118255"
id="feGaussianBlur1161" />
</filter>
</defs>
<sodipodi:namedview <sodipodi:namedview
id="base" id="base"
pagecolor="#ffffff" pagecolor="#ffffff"
@ -28,22 +72,31 @@
borderopacity="1.0" borderopacity="1.0"
inkscape:pageopacity="0.0" inkscape:pageopacity="0.0"
inkscape:pageshadow="2" inkscape:pageshadow="2"
inkscape:zoom="1.979899" inkscape:zoom="7.9195959"
inkscape:cx="52.480467" inkscape:cx="89.966711"
inkscape:cy="138.73493" inkscape:cy="177.91312"
inkscape:document-units="mm" inkscape:document-units="mm"
inkscape:current-layer="layer1" inkscape:current-layer="layer1"
showgrid="true" showgrid="true"
units="px" units="px"
inkscape:snap-text-baseline="false" inkscape:snap-text-baseline="false"
inkscape:window-width="1918" inkscape:window-width="3838"
inkscape:window-height="1038" inkscape:window-height="2118"
inkscape:window-x="0" inkscape:window-x="0"
inkscape:window-y="20" inkscape:window-y="20"
inkscape:window-maximized="1"> inkscape:window-maximized="1"
inkscape:pagecheckerboard="0"
inkscape:showpageshadow="2"
inkscape:deskcolor="#d1d1d1">
<inkscape:grid <inkscape:grid
type="xygrid" type="xygrid"
id="grid4504" /> id="grid4504"
originx="0"
originy="0"
spacingy="1"
spacingx="1"
units="px"
visible="true" />
</sodipodi:namedview> </sodipodi:namedview>
<metadata <metadata
id="metadata5"> id="metadata5">
@ -61,18 +114,22 @@
inkscape:label="Layer 1" inkscape:label="Layer 1"
inkscape:groupmode="layer" inkscape:groupmode="layer"
id="layer1" id="layer1"
transform="translate(0,-229.26668)"> transform="translate(0,-229.26668)"
style="display:inline">
<g <g
aria-label="K" id="text821-7"
transform="scale(1.0347881,0.96638145)" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:84.55024719px;line-height:1.25;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;opacity:1;fill:#2b3137;fill-opacity:1;stroke:none;stroke-width:2.11405313;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:84.55024719px;line-height:1.25;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.11376619" transform="matrix(0.8407653,0,0,0.83753055,-37.28971,3.4402954)"
id="text821"> aria-label="K">
<path <path
d="m 12.784421,241.62303 h 8.949095 v 27.37877 l 25.568842,-27.37877 6.39221,6.84469 -20.455074,21.90302 20.455074,27.37876 -6.39221,5.47576 -19.176632,-27.37877 -6.39221,6.84469 0,20.53408 h -8.949095 z" id="path823-5"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:84.55024719px;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:2.11376619;fill:#ff0000;fill-opacity:1" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:84.55024719px;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;opacity:0.775;fill:#2b3137;fill-opacity:1;stroke-width:2.11405313;filter:url(#filter5338);stroke-miterlimit:4;stroke-dasharray:none"
id="path823" d="M 65.200545 279.95309 L 65.200545 341.55532 L 74.14964 341.55532 L 74.14964 321.02125 L 80.541851 314.17676 L 99.718483 341.55532 L 106.11069 336.07998 L 85.655619 308.7008 L 106.11069 286.7982 L 99.718483 279.95309 L 74.14964 307.33227 L 74.14964 279.95309 L 65.200545 279.95309 z M 69.586585 307.95792 C 71.270821 307.95521 72.163105 308.76519 72.164982 309.94037 C 72.166858 311.11555 71.276967 311.92813 69.592731 311.93085 C 67.946259 311.9335 67.061695 311.12357 67.059818 309.94839 C 67.057941 308.77322 67.940113 307.96057 69.586585 307.95792 z M 69.588429 309.10309 C 68.568824 309.10473 68.017826 309.4316 68.01865 309.94716 C 68.019473 310.46272 68.571283 310.78793 69.590887 310.78629 C 70.655808 310.78458 71.206973 310.45717 71.20615 309.94161 C 71.205327 309.42604 70.653349 309.10138 69.588429 309.10309 z M 70.651134 317.13779 C 71.466818 317.13648 72.177771 317.82535 72.179733 319.0536 C 72.180798 319.7208 71.940317 320.4034 71.472902 320.93487 L 70.70891 320.29194 C 71.010421 319.91995 71.221049 319.45722 71.220287 318.97956 C 71.219524 318.50191 71.038049 318.28194 70.788812 318.28234 C 70.441392 318.2829 70.351355 318.5789 70.155738 319.04928 L 69.884683 319.68666 C 69.681646 320.24045 69.259204 320.7412 68.541704 320.74236 C 67.726021 320.74367 67.075662 320.0017 67.073955 318.93267 C 67.072998 318.33371 67.298319 317.73449 67.720551 317.28649 L 68.424309 317.85414 C 68.175617 318.19572 68.032626 318.50667 68.033401 318.9919 C 68.033994 319.36341 68.192626 319.61369 68.479626 319.61323 C 68.774179 319.61276 68.886895 319.27856 69.059842 318.80063 L 69.315531 318.20151 C 69.556173 317.54909 69.956292 317.13891 70.651134 317.13779 z M 72.09983 325.98324 L 72.102289 327.23453 L 70.32845 328.18534 L 70.32968 328.76904 L 72.104747 328.76595 L 72.106591 329.88027 L 67.18213 329.88829 L 67.179057 328.13722 C 67.177386 327.09093 67.538843 326.22575 68.7095 326.22387 C 69.434552 326.2227 69.895571 326.57849 70.130538 327.10126 L 72.09983 325.98324 z M 68.711344 327.32338 C 68.227976 327.32416 68.061959 327.6353 68.062903 328.22668 L 68.064133 328.77274 L 69.445833 328.77027 L 69.445219 328.22422 C 69.444274 327.63284 69.194712 327.3226 68.711344 327.32338 z M 72.114581 335.0286 L 72.116425 336.2114 L 70.946159 336.5088 L 70.948618 338.00258 L 72.119499 338.30368 L 72.121342 339.44145 L 67.195038 337.91003 L 67.192579 336.57544 L 72.114581 335.0286 z M 70.078294 336.73771 L 69.625307 336.85248 C 69.134592 336.98216 68.560497 337.13471 68.039547 337.24921 L 68.039547 337.27945 C 68.560872 337.39992 69.13541 337.53513 69.626536 337.66323 L 70.079523 337.77614 L 70.078294 336.73771 z " />
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccccccc" />
</g> </g>
<g
id="text821"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:84.55024719px;line-height:1.25;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#2b3137;fill-opacity:1;stroke:none;stroke-width:2.11376619;stroke-opacity:1;filter:url(#filter1159)"
transform="matrix(1.0347881,0,0,0.96638144,-54.239583,-37.041665)"
aria-label="K" />
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -10,7 +10,7 @@ import {
} from "../rest-tools"; } from "../rest-tools";
import { import {
UUID, ObjectId,
} from "../model"; } from "../model";
export namespace DataResource { export namespace DataResource {
@ -30,13 +30,13 @@ export namespace DataResource {
}, },
params: { params: {
name: string, name: string,
uuid: UUID, oid: ObjectId,
}, },
data: string, data: string,
}): Promise<object> { }): Promise<object> {
return RESTRequestJson({ return RESTRequestJson({
restModel: { restModel: {
endPoint: "/data/{uuid}/{name}", endPoint: "/data/{oid}/{name}",
requestType: HTTPRequestModel.GET, requestType: HTTPRequestModel.GET,
}, },
restConfig, restConfig,
@ -59,13 +59,13 @@ export namespace DataResource {
Authorization?: string, Authorization?: string,
}, },
params: { params: {
uuid: UUID, oid: ObjectId,
}, },
data: string, data: string,
}): Promise<object> { }): Promise<object> {
return RESTRequestJson({ return RESTRequestJson({
restModel: { restModel: {
endPoint: "/data/{uuid}", endPoint: "/data/{oid}",
requestType: HTTPRequestModel.GET, requestType: HTTPRequestModel.GET,
}, },
restConfig, restConfig,
@ -88,13 +88,13 @@ export namespace DataResource {
Authorization?: string, Authorization?: string,
}, },
params: { params: {
uuid: UUID, oid: ObjectId,
}, },
data: string, data: string,
}): Promise<object> { }): Promise<object> {
return RESTRequestJson({ return RESTRequestJson({
restModel: { restModel: {
endPoint: "/data/thumbnail/{uuid}", endPoint: "/data/thumbnail/{oid}",
requestType: HTTPRequestModel.GET, requestType: HTTPRequestModel.GET,
}, },
restConfig, restConfig,

View File

@ -15,10 +15,12 @@ import {
DataGetTokenWrite, DataGetTokenWrite,
GetToken, GetToken,
Long, Long,
PartRight,
UserAuth, UserAuth,
UserAuthGet, UserAuthGet,
UserCreateWrite, UserCreateWrite,
UserOut, UserOut,
ZodPartRight,
ZodUserAuthGet, ZodUserAuthGet,
isGetToken, isGetToken,
isUserAuth, isUserAuth,
@ -63,8 +65,27 @@ export namespace UserResource {
data, data,
}, isUserAuthGet); }, isUserAuthGet);
}; };
export function get({
restConfig,
params,
}: {
restConfig: RESTConfig,
params: {
id: Long,
},
}): Promise<UserAuthGet> {
return RESTRequestJson({
restModel: {
endPoint: "/users/{id}",
requestType: HTTPRequestModel.GET,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
}, isUserAuthGet);
};
export const ZodGetApplicationRightTypeReturn = zod.record(zod.string(), zod.any()); export const ZodGetApplicationRightTypeReturn = zod.record(zod.string(), ZodPartRight);
export type GetApplicationRightTypeReturn = zod.infer<typeof ZodGetApplicationRightTypeReturn>; export type GetApplicationRightTypeReturn = zod.infer<typeof ZodGetApplicationRightTypeReturn>;
export function isGetApplicationRightTypeReturn(data: any): data is GetApplicationRightTypeReturn { export function isGetApplicationRightTypeReturn(data: any): data is GetApplicationRightTypeReturn {
@ -129,44 +150,25 @@ export namespace UserResource {
data, data,
}, isGetToken); }, isGetToken);
}; };
export function getUser({
restConfig,
params,
}: {
restConfig: RESTConfig,
params: {
id: Long,
},
}): Promise<UserAuthGet> {
return RESTRequestJson({
restModel: {
endPoint: "/users/{id}",
requestType: HTTPRequestModel.GET,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
}, isUserAuthGet);
};
export const ZodGetUsersTypeReturn = zod.array(ZodUserAuthGet); export const ZodGetsTypeReturn = zod.array(ZodUserAuthGet);
export type GetUsersTypeReturn = zod.infer<typeof ZodGetUsersTypeReturn>; export type GetsTypeReturn = zod.infer<typeof ZodGetsTypeReturn>;
export function isGetUsersTypeReturn(data: any): data is GetUsersTypeReturn { export function isGetsTypeReturn(data: any): data is GetsTypeReturn {
try { try {
ZodGetUsersTypeReturn.parse(data); ZodGetsTypeReturn.parse(data);
return true; return true;
} catch (e: any) { } catch (e: any) {
console.log(`Fail to parse data type='ZodGetUsersTypeReturn' error=${e}`); console.log(`Fail to parse data type='ZodGetsTypeReturn' error=${e}`);
return false; return false;
} }
} }
export function getUsers({ export function gets({
restConfig, restConfig,
}: { }: {
restConfig: RESTConfig, restConfig: RESTConfig,
}): Promise<GetUsersTypeReturn> { }): Promise<GetsTypeReturn> {
return RESTRequestJson({ return RESTRequestJson({
restModel: { restModel: {
endPoint: "/users/", endPoint: "/users/",
@ -174,7 +176,7 @@ export namespace UserResource {
accept: HTTPMimeType.JSON, accept: HTTPMimeType.JSON,
}, },
restConfig, restConfig,
}, isGetUsersTypeReturn); }, isGetsTypeReturn);
}; };
export function isEmailExist({ export function isEmailExist({
restConfig, restConfig,
@ -238,7 +240,7 @@ export namespace UserResource {
}, isUserAuth); }, isUserAuth);
}; };
export const ZodPatchApplicationRightTypeReturn = zod.record(zod.string(), zod.any()); export const ZodPatchApplicationRightTypeReturn = zod.record(zod.string(), ZodPartRight);
export type PatchApplicationRightTypeReturn = zod.infer<typeof ZodPatchApplicationRightTypeReturn>; export type PatchApplicationRightTypeReturn = zod.infer<typeof ZodPatchApplicationRightTypeReturn>;
export function isPatchApplicationRightTypeReturn(data: any): data is PatchApplicationRightTypeReturn { export function isPatchApplicationRightTypeReturn(data: any): data is PatchApplicationRightTypeReturn {
@ -261,7 +263,7 @@ export namespace UserResource {
applicationId: Long, applicationId: Long,
userId: Long, userId: Long,
}, },
data: {[key: string]: object;}, data: {[key: string]: PartRight;},
}): Promise<PatchApplicationRightTypeReturn> { }): Promise<PatchApplicationRightTypeReturn> {
return RESTRequestJson({ return RESTRequestJson({
restModel: { restModel: {

View File

@ -21,8 +21,10 @@ export function isAddUserData(data: any): data is AddUserData {
return false; return false;
} }
} }
export const ZodAddUserDataWrite = zod.object({
userId: ZodLong.nullable().optional(),
export const ZodAddUserDataWrite = ZodAddUserData.partial(); });
export type AddUserDataWrite = zod.infer<typeof ZodAddUserDataWrite>; export type AddUserDataWrite = zod.infer<typeof ZodAddUserDataWrite>;

View File

@ -7,9 +7,9 @@ import {ZodLong} from "./long";
export const ZodApplicationSmall = zod.object({ export const ZodApplicationSmall = zod.object({
id: ZodLong.optional(), id: ZodLong.optional(),
name: zod.string().max(255).optional(), name: zod.string().optional(),
description: zod.string().max(255).optional(), description: zod.string().optional(),
redirect: zod.string().max(255).optional(), redirect: zod.string().optional(),
}); });
@ -24,8 +24,13 @@ export function isApplicationSmall(data: any): data is ApplicationSmall {
return false; return false;
} }
} }
export const ZodApplicationSmallWrite = zod.object({
id: ZodLong.nullable().optional(),
name: zod.string().nullable().optional(),
description: zod.string().nullable().optional(),
redirect: zod.string().nullable().optional(),
export const ZodApplicationSmallWrite = ZodApplicationSmall.partial(); });
export type ApplicationSmallWrite = zod.infer<typeof ZodApplicationSmallWrite>; export type ApplicationSmallWrite = zod.infer<typeof ZodApplicationSmallWrite>;

View File

@ -3,11 +3,9 @@
*/ */
import { z as zod } from "zod"; import { z as zod } from "zod";
import {ZodGenericToken} from "./generic-token"; import {ZodGenericToken, ZodGenericTokenWrite } from "./generic-token";
export const ZodApplicationToken = ZodGenericToken.extend({ export const ZodApplicationToken = ZodGenericToken;
});
export type ApplicationToken = zod.infer<typeof ZodApplicationToken>; export type ApplicationToken = zod.infer<typeof ZodApplicationToken>;
@ -20,14 +18,7 @@ export function isApplicationToken(data: any): data is ApplicationToken {
return false; return false;
} }
} }
export const ZodApplicationTokenWrite = ZodGenericTokenWrite;
export const ZodApplicationTokenWrite = ZodApplicationToken.omit({
deleted: true,
id: true,
createdAt: true,
updatedAt: true,
}).partial();
export type ApplicationTokenWrite = zod.infer<typeof ZodApplicationTokenWrite>; export type ApplicationTokenWrite = zod.infer<typeof ZodApplicationTokenWrite>;

View File

@ -4,14 +4,14 @@
import { z as zod } from "zod"; import { z as zod } from "zod";
import {ZodInteger} from "./integer"; import {ZodInteger} from "./integer";
import {ZodGenericDataSoftDelete} from "./generic-data-soft-delete"; import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
export const ZodApplication = ZodGenericDataSoftDelete.extend({ export const ZodApplication = ZodGenericDataSoftDelete.extend({
name: zod.string().max(256).optional(), name: zod.string().optional(),
description: zod.string().max(2048).optional(), description: zod.string().optional(),
redirect: zod.string().max(2048), redirect: zod.string(),
redirectDev: zod.string().max(2048).optional(), redirectDev: zod.string().optional(),
notification: zod.string().max(2048).optional(), notification: zod.string().optional(),
/** /**
* Expiration time * Expiration time
*/ */
@ -34,14 +34,22 @@ export function isApplication(data: any): data is Application {
return false; return false;
} }
} }
export const ZodApplicationWrite = ZodGenericDataSoftDeleteWrite.extend({
name: zod.string().nullable().optional(),
description: zod.string().nullable().optional(),
redirect: zod.string().optional(),
redirectDev: zod.string().nullable().optional(),
notification: zod.string().nullable().optional(),
/**
* Expiration time
*/
ttl: ZodInteger.optional(),
/**
* Right is manage with Karso
*/
manageRight: zod.boolean().optional(),
export const ZodApplicationWrite = ZodApplication.omit({ });
deleted: true,
id: true,
createdAt: true,
updatedAt: true,
}).partial();
export type ApplicationWrite = zod.infer<typeof ZodApplicationWrite>; export type ApplicationWrite = zod.infer<typeof ZodApplicationWrite>;

View File

@ -5,11 +5,11 @@ import { z as zod } from "zod";
export const ZodChangePassword = zod.object({ export const ZodChangePassword = zod.object({
method: zod.string().max(32).optional(), method: zod.string().min(2).max(2),
login: zod.string().max(512).optional(), login: zod.string().min(3).max(128),
time: zod.string().max(64).optional(), time: zod.string().min(20).max(64),
password: zod.string().max(128).optional(), password: zod.string().min(128).max(128),
newPassword: zod.string().max(128).optional(), newPassword: zod.string().min(128).max(128),
}); });
@ -24,8 +24,14 @@ export function isChangePassword(data: any): data is ChangePassword {
return false; return false;
} }
} }
export const ZodChangePasswordWrite = zod.object({
method: zod.string().min(2).max(2).optional(),
login: zod.string().min(3).max(128).optional(),
time: zod.string().min(20).max(64).optional(),
password: zod.string().min(128).max(128).optional(),
newPassword: zod.string().min(128).max(128).optional(),
export const ZodChangePasswordWrite = ZodChangePassword.partial(); });
export type ChangePasswordWrite = zod.infer<typeof ZodChangePasswordWrite>; export type ChangePasswordWrite = zod.infer<typeof ZodChangePasswordWrite>;

View File

@ -5,7 +5,7 @@ import { z as zod } from "zod";
export const ZodClientToken = zod.object({ export const ZodClientToken = zod.object({
url: zod.string().max(1024).optional(), url: zod.string().optional(),
jwt: zod.string().optional(), jwt: zod.string().optional(),
}); });
@ -21,8 +21,11 @@ export function isClientToken(data: any): data is ClientToken {
return false; return false;
} }
} }
export const ZodClientTokenWrite = zod.object({
url: zod.string().nullable().optional(),
jwt: zod.string().nullable().optional(),
export const ZodClientTokenWrite = ZodClientToken.partial(); });
export type ClientTokenWrite = zod.infer<typeof ZodClientTokenWrite>; export type ClientTokenWrite = zod.infer<typeof ZodClientTokenWrite>;

View File

@ -6,7 +6,7 @@ import { z as zod } from "zod";
import {ZodInteger} from "./integer"; import {ZodInteger} from "./integer";
export const ZodCreateTokenRequest = zod.object({ export const ZodCreateTokenRequest = zod.object({
name: zod.string().max(255).optional(), name: zod.string().optional(),
validity: ZodInteger.optional(), validity: ZodInteger.optional(),
}); });
@ -22,8 +22,11 @@ export function isCreateTokenRequest(data: any): data is CreateTokenRequest {
return false; return false;
} }
} }
export const ZodCreateTokenRequestWrite = zod.object({
name: zod.string().nullable().optional(),
validity: ZodInteger.nullable().optional(),
export const ZodCreateTokenRequestWrite = ZodCreateTokenRequest.partial(); });
export type CreateTokenRequestWrite = zod.infer<typeof ZodCreateTokenRequestWrite>; export type CreateTokenRequestWrite = zod.infer<typeof ZodCreateTokenRequestWrite>;

View File

@ -5,6 +5,10 @@ import { z as zod } from "zod";
export const ZodDataGetToken = zod.object({ export const ZodDataGetToken = zod.object({
login: zod.string().min(3).max(128),
method: zod.string().min(2).max(2),
time: zod.string().min(20).max(64),
password: zod.string().min(128).max(128),
}); });
@ -19,8 +23,13 @@ export function isDataGetToken(data: any): data is DataGetToken {
return false; return false;
} }
} }
export const ZodDataGetTokenWrite = zod.object({
login: zod.string().min(3).max(128).optional(),
method: zod.string().min(2).max(2).optional(),
time: zod.string().min(20).max(64).optional(),
password: zod.string().min(128).max(128).optional(),
export const ZodDataGetTokenWrite = ZodDataGetToken.partial(); });
export type DataGetTokenWrite = zod.infer<typeof ZodDataGetTokenWrite>; export type DataGetTokenWrite = zod.infer<typeof ZodDataGetTokenWrite>;

View File

@ -3,7 +3,7 @@
*/ */
import { z as zod } from "zod"; import { z as zod } from "zod";
import {ZodGenericData} from "./generic-data"; import {ZodGenericData, ZodGenericDataWrite } from "./generic-data";
export const ZodGenericDataSoftDelete = ZodGenericData.extend({ export const ZodGenericDataSoftDelete = ZodGenericData.extend({
/** /**
@ -24,14 +24,7 @@ export function isGenericDataSoftDelete(data: any): data is GenericDataSoftDelet
return false; return false;
} }
} }
export const ZodGenericDataSoftDeleteWrite = ZodGenericDataWrite;
export const ZodGenericDataSoftDeleteWrite = ZodGenericDataSoftDelete.omit({
deleted: true,
id: true,
createdAt: true,
updatedAt: true,
}).partial();
export type GenericDataSoftDeleteWrite = zod.infer<typeof ZodGenericDataSoftDeleteWrite>; export type GenericDataSoftDeleteWrite = zod.infer<typeof ZodGenericDataSoftDeleteWrite>;

View File

@ -4,7 +4,7 @@
import { z as zod } from "zod"; import { z as zod } from "zod";
import {ZodLong} from "./long"; import {ZodLong} from "./long";
import {ZodGenericTiming} from "./generic-timing"; import {ZodGenericTiming, ZodGenericTimingWrite } from "./generic-timing";
export const ZodGenericData = ZodGenericTiming.extend({ export const ZodGenericData = ZodGenericTiming.extend({
/** /**
@ -25,13 +25,7 @@ export function isGenericData(data: any): data is GenericData {
return false; return false;
} }
} }
export const ZodGenericDataWrite = ZodGenericTimingWrite;
export const ZodGenericDataWrite = ZodGenericData.omit({
id: true,
createdAt: true,
updatedAt: true,
}).partial();
export type GenericDataWrite = zod.infer<typeof ZodGenericDataWrite>; export type GenericDataWrite = zod.infer<typeof ZodGenericDataWrite>;

View File

@ -28,12 +28,9 @@ export function isGenericTiming(data: any): data is GenericTiming {
return false; return false;
} }
} }
export const ZodGenericTimingWrite = zod.object({
export const ZodGenericTimingWrite = ZodGenericTiming.omit({ });
createdAt: true,
updatedAt: true,
}).partial();
export type GenericTimingWrite = zod.infer<typeof ZodGenericTimingWrite>; export type GenericTimingWrite = zod.infer<typeof ZodGenericTimingWrite>;

View File

@ -5,7 +5,7 @@ import { z as zod } from "zod";
import {ZodLong} from "./long"; import {ZodLong} from "./long";
import {ZodTimestamp} from "./timestamp"; import {ZodTimestamp} from "./timestamp";
import {ZodGenericDataSoftDelete} from "./generic-data-soft-delete"; import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
export const ZodGenericToken = ZodGenericDataSoftDelete.extend({ export const ZodGenericToken = ZodGenericDataSoftDelete.extend({
parentId: ZodLong, parentId: ZodLong,
@ -26,14 +26,13 @@ export function isGenericToken(data: any): data is GenericToken {
return false; return false;
} }
} }
export const ZodGenericTokenWrite = ZodGenericDataSoftDeleteWrite.extend({
parentId: ZodLong.optional(),
name: zod.string().optional(),
endValidityTime: ZodTimestamp.optional(),
token: zod.string().optional(),
export const ZodGenericTokenWrite = ZodGenericToken.omit({ });
deleted: true,
id: true,
createdAt: true,
updatedAt: true,
}).partial();
export type GenericTokenWrite = zod.infer<typeof ZodGenericTokenWrite>; export type GenericTokenWrite = zod.infer<typeof ZodGenericTokenWrite>;

View File

@ -20,8 +20,10 @@ export function isGetSignUpAvailable(data: any): data is GetSignUpAvailable {
return false; return false;
} }
} }
export const ZodGetSignUpAvailableWrite = zod.object({
signup: zod.boolean(),
export const ZodGetSignUpAvailableWrite = ZodGetSignUpAvailable.partial(); });
export type GetSignUpAvailableWrite = zod.infer<typeof ZodGetSignUpAvailableWrite>; export type GetSignUpAvailableWrite = zod.infer<typeof ZodGetSignUpAvailableWrite>;

View File

@ -20,8 +20,10 @@ export function isGetToken(data: any): data is GetToken {
return false; return false;
} }
} }
export const ZodGetTokenWrite = zod.object({
jwt: zod.string().optional(),
export const ZodGetTokenWrite = ZodGetToken.partial(); });
export type GetTokenWrite = zod.infer<typeof ZodGetTokenWrite>; export type GetTokenWrite = zod.infer<typeof ZodGetTokenWrite>;

View File

@ -15,10 +15,14 @@ export * from "./generic-timing"
export * from "./generic-token" export * from "./generic-token"
export * from "./get-sign-up-available" export * from "./get-sign-up-available"
export * from "./get-token" export * from "./get-token"
export * from "./int"
export * from "./integer" export * from "./integer"
export * from "./iso-date" export * from "./iso-date"
export * from "./jwt-header"
export * from "./jwt-payload"
export * from "./jwt-token"
export * from "./long" export * from "./long"
export * from "./object-id"
export * from "./part-right"
export * from "./public-key" export * from "./public-key"
export * from "./rest-error-response" export * from "./rest-error-response"
export * from "./right" export * from "./right"

View File

@ -0,0 +1,40 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
export const ZodJwtHeader = zod.object({
typ: zod.string().max(128),
alg: zod.string().max(128),
});
export type JwtHeader = zod.infer<typeof ZodJwtHeader>;
export function isJwtHeader(data: any): data is JwtHeader {
try {
ZodJwtHeader.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodJwtHeader' error=${e}`);
return false;
}
}
export const ZodJwtHeaderWrite = zod.object({
typ: zod.string().max(128).optional(),
alg: zod.string().max(128).optional(),
});
export type JwtHeaderWrite = zod.infer<typeof ZodJwtHeaderWrite>;
export function isJwtHeaderWrite(data: any): data is JwtHeaderWrite {
try {
ZodJwtHeaderWrite.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodJwtHeaderWrite' error=${e}`);
return false;
}
}

View File

@ -0,0 +1,51 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
import {ZodLong} from "./long";
export const ZodJwtPayload = zod.object({
sub: zod.string(),
application: zod.string(),
iss: zod.string(),
right: zod.record(zod.string(), zod.record(zod.string(), ZodLong)),
login: zod.string(),
exp: ZodLong,
iat: ZodLong,
});
export type JwtPayload = zod.infer<typeof ZodJwtPayload>;
export function isJwtPayload(data: any): data is JwtPayload {
try {
ZodJwtPayload.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodJwtPayload' error=${e}`);
return false;
}
}
export const ZodJwtPayloadWrite = zod.object({
sub: zod.string().optional(),
application: zod.string().optional(),
iss: zod.string().optional(),
right: zod.record(zod.string(), zod.record(zod.string(), ZodLong)).optional(),
login: zod.string().optional(),
exp: ZodLong.optional(),
iat: ZodLong.optional(),
});
export type JwtPayloadWrite = zod.infer<typeof ZodJwtPayloadWrite>;
export function isJwtPayloadWrite(data: any): data is JwtPayloadWrite {
try {
ZodJwtPayloadWrite.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodJwtPayloadWrite' error=${e}`);
return false;
}
}

View File

@ -0,0 +1,44 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
import {ZodJwtHeader, ZodJwtHeaderWrite } from "./jwt-header";
import {ZodJwtPayload, ZodJwtPayloadWrite } from "./jwt-payload";
export const ZodJwtToken = zod.object({
header: ZodJwtHeader,
payload: ZodJwtPayload,
signature: zod.string(),
});
export type JwtToken = zod.infer<typeof ZodJwtToken>;
export function isJwtToken(data: any): data is JwtToken {
try {
ZodJwtToken.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodJwtToken' error=${e}`);
return false;
}
}
export const ZodJwtTokenWrite = zod.object({
header: ZodJwtHeader.optional(),
payload: ZodJwtPayload.optional(),
signature: zod.string().optional(),
});
export type JwtTokenWrite = zod.infer<typeof ZodJwtTokenWrite>;
export function isJwtTokenWrite(data: any): data is JwtTokenWrite {
try {
ZodJwtTokenWrite.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodJwtTokenWrite' error=${e}`);
return false;
}
}

View File

@ -0,0 +1,8 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
export const ZodObjectId = zod.string().length(24, "Invalid ObjectId length").regex(/^[a-fA-F0-9]{24}$/, "Invalid ObjectId format");
export type ObjectId = zod.infer<typeof ZodObjectId>;

View File

@ -0,0 +1,24 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
export enum PartRight {
READ = 1,
NONE = 0,
WRITE = 2,
READ_WRITE = 3,
};
export const ZodPartRight = zod.nativeEnum(PartRight);
export function isPartRight(data: any): data is PartRight {
try {
ZodPartRight.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodPartRight' error=${e}`);
return false;
}
}

View File

@ -5,7 +5,7 @@ import { z as zod } from "zod";
export const ZodPublicKey = zod.object({ export const ZodPublicKey = zod.object({
key: zod.string().max(255).optional(), key: zod.string().optional(),
}); });
@ -20,8 +20,10 @@ export function isPublicKey(data: any): data is PublicKey {
return false; return false;
} }
} }
export const ZodPublicKeyWrite = zod.object({
key: zod.string().nullable().optional(),
export const ZodPublicKeyWrite = ZodPublicKey.partial(); });
export type PublicKeyWrite = zod.infer<typeof ZodPublicKeyWrite>; export type PublicKeyWrite = zod.infer<typeof ZodPublicKeyWrite>;

View File

@ -3,15 +3,15 @@
*/ */
import { z as zod } from "zod"; import { z as zod } from "zod";
import {ZodUUID} from "./uuid"; import {ZodObjectId} from "./object-id";
import {Zodint} from "./int"; import {ZodInteger} from "./integer";
export const ZodRestErrorResponse = zod.object({ export const ZodRestErrorResponse = zod.object({
uuid: ZodUUID.optional(), oid: ZodObjectId.optional(),
name: zod.string(), name: zod.string(),
message: zod.string(), message: zod.string(),
time: zod.string(), time: zod.string(),
status: Zodint, status: ZodInteger,
statusMessage: zod.string(), statusMessage: zod.string(),
}); });
@ -27,17 +27,3 @@ export function isRestErrorResponse(data: any): data is RestErrorResponse {
return false; return false;
} }
} }
export const ZodRestErrorResponseWrite = ZodRestErrorResponse.partial();
export type RestErrorResponseWrite = zod.infer<typeof ZodRestErrorResponseWrite>;
export function isRestErrorResponseWrite(data: any): data is RestErrorResponseWrite {
try {
ZodRestErrorResponseWrite.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodRestErrorResponseWrite' error=${e}`);
return false;
}
}

View File

@ -4,7 +4,7 @@
import { z as zod } from "zod"; import { z as zod } from "zod";
import {ZodLong} from "./long"; import {ZodLong} from "./long";
import {ZodGenericDataSoftDelete} from "./generic-data-soft-delete"; import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
export const ZodRightDescription = ZodGenericDataSoftDelete.extend({ export const ZodRightDescription = ZodGenericDataSoftDelete.extend({
/** /**
@ -14,23 +14,15 @@ export const ZodRightDescription = ZodGenericDataSoftDelete.extend({
/** /**
* Key of the property * Key of the property
*/ */
key: zod.string().max(64), key: zod.string(),
/** /**
* Title of the right * Title of the right
*/ */
title: zod.string().max(1024), title: zod.string(),
/** /**
* Description of the right * Description of the right
*/ */
description: zod.string().max(1024), description: zod.string(),
/**
* default value if Never set
*/
defaultValue: zod.string().max(1024).optional(),
/**
* Type of the property
*/
type: zod.string().max(16),
}); });
@ -45,14 +37,25 @@ export function isRightDescription(data: any): data is RightDescription {
return false; return false;
} }
} }
export const ZodRightDescriptionWrite = ZodGenericDataSoftDeleteWrite.extend({
/**
* Application id that have the reference of the right
*/
applicationId: ZodLong.optional(),
/**
* Key of the property
*/
key: zod.string().optional(),
/**
* Title of the right
*/
title: zod.string().optional(),
/**
* Description of the right
*/
description: zod.string().optional(),
export const ZodRightDescriptionWrite = ZodRightDescription.omit({ });
deleted: true,
id: true,
createdAt: true,
updatedAt: true,
}).partial();
export type RightDescriptionWrite = zod.infer<typeof ZodRightDescriptionWrite>; export type RightDescriptionWrite = zod.infer<typeof ZodRightDescriptionWrite>;

View File

@ -4,7 +4,8 @@
import { z as zod } from "zod"; import { z as zod } from "zod";
import {ZodLong} from "./long"; import {ZodLong} from "./long";
import {ZodGenericDataSoftDelete} from "./generic-data-soft-delete"; import {ZodPartRight} from "./part-right";
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
export const ZodRight = ZodGenericDataSoftDelete.extend({ export const ZodRight = ZodGenericDataSoftDelete.extend({
/** /**
@ -22,7 +23,7 @@ export const ZodRight = ZodGenericDataSoftDelete.extend({
/** /**
* Value of the right * Value of the right
*/ */
value: zod.string().max(1024), value: ZodPartRight,
}); });
@ -37,14 +38,25 @@ export function isRight(data: any): data is Right {
return false; return false;
} }
} }
export const ZodRightWrite = ZodGenericDataSoftDeleteWrite.extend({
/**
* application-ID that have the reference of the right
*/
applicationId: ZodLong.optional(),
/**
* user-ID
*/
userId: ZodLong.optional(),
/**
* rightDescription-ID of the right description
*/
rightDescriptionId: ZodLong.optional(),
/**
* Value of the right
*/
value: ZodPartRight.optional(),
export const ZodRightWrite = ZodRight.omit({ });
deleted: true,
id: true,
createdAt: true,
updatedAt: true,
}).partial();
export type RightWrite = zod.infer<typeof ZodRightWrite>; export type RightWrite = zod.infer<typeof ZodRightWrite>;

View File

@ -3,10 +3,10 @@
*/ */
import { z as zod } from "zod"; import { z as zod } from "zod";
import {ZodUser} from "./user"; import {ZodUser, ZodUserWrite } from "./user";
export const ZodUserAuthGet = ZodUser.extend({ export const ZodUserAuthGet = ZodUser.extend({
email: zod.string().max(512), email: zod.string(),
avatar: zod.boolean(), avatar: zod.boolean(),
}); });
@ -22,14 +22,11 @@ export function isUserAuthGet(data: any): data is UserAuthGet {
return false; return false;
} }
} }
export const ZodUserAuthGetWrite = ZodUserWrite.extend({
email: zod.string().optional(),
avatar: zod.boolean().optional(),
export const ZodUserAuthGetWrite = ZodUserAuthGet.omit({ });
deleted: true,
id: true,
createdAt: true,
updatedAt: true,
}).partial();
export type UserAuthGetWrite = zod.infer<typeof ZodUserAuthGetWrite>; export type UserAuthGetWrite = zod.infer<typeof ZodUserAuthGetWrite>;

View File

@ -5,13 +5,13 @@ import { z as zod } from "zod";
import {ZodTimestamp} from "./timestamp"; import {ZodTimestamp} from "./timestamp";
import {ZodLong} from "./long"; import {ZodLong} from "./long";
import {ZodUser} from "./user"; import {ZodUser, ZodUserWrite } from "./user";
export const ZodUserAuth = ZodUser.extend({ export const ZodUserAuth = ZodUser.extend({
password: zod.string().max(128), password: zod.string().min(128).max(128).optional(),
email: zod.string().max(512), email: zod.string().min(5).max(128),
emailValidate: ZodTimestamp.optional(), emailValidate: ZodTimestamp.optional(),
newEmail: zod.string().max(512).optional(), newEmail: zod.string().min(5).max(128).optional(),
avatar: zod.boolean(), avatar: zod.boolean(),
/** /**
* List of accessible application (if not set the application is not available) * List of accessible application (if not set the application is not available)
@ -31,14 +31,18 @@ export function isUserAuth(data: any): data is UserAuth {
return false; return false;
} }
} }
export const ZodUserAuthWrite = ZodUserWrite.extend({
password: zod.string().min(128).max(128).nullable().optional(),
email: zod.string().min(5).max(128).optional(),
emailValidate: ZodTimestamp.nullable().optional(),
newEmail: zod.string().min(5).max(128).nullable().optional(),
avatar: zod.boolean().optional(),
/**
* List of accessible application (if not set the application is not available)
*/
applications: zod.array(ZodLong).optional(),
export const ZodUserAuthWrite = ZodUserAuth.omit({ });
deleted: true,
id: true,
createdAt: true,
updatedAt: true,
}).partial();
export type UserAuthWrite = zod.infer<typeof ZodUserAuthWrite>; export type UserAuthWrite = zod.infer<typeof ZodUserAuthWrite>;

View File

@ -5,9 +5,9 @@ import { z as zod } from "zod";
export const ZodUserCreate = zod.object({ export const ZodUserCreate = zod.object({
login: zod.string().max(255).optional(), login: zod.string().min(3).max(128),
email: zod.string().max(255).optional(), email: zod.string().min(5).max(128),
password: zod.string().max(255).optional(), password: zod.string().min(128).max(128),
}); });
@ -22,8 +22,12 @@ export function isUserCreate(data: any): data is UserCreate {
return false; return false;
} }
} }
export const ZodUserCreateWrite = zod.object({
login: zod.string().min(3).max(128).optional(),
email: zod.string().min(5).max(128).optional(),
password: zod.string().min(128).max(128).optional(),
export const ZodUserCreateWrite = ZodUserCreate.partial(); });
export type UserCreateWrite = zod.infer<typeof ZodUserCreateWrite>; export type UserCreateWrite = zod.infer<typeof ZodUserCreateWrite>;

View File

@ -7,7 +7,7 @@ import {ZodLong} from "./long";
export const ZodUserOut = zod.object({ export const ZodUserOut = zod.object({
id: ZodLong, id: ZodLong,
login: zod.string().max(255).optional(), login: zod.string().optional(),
}); });
@ -22,8 +22,11 @@ export function isUserOut(data: any): data is UserOut {
return false; return false;
} }
} }
export const ZodUserOutWrite = zod.object({
id: ZodLong,
login: zod.string().nullable().optional(),
export const ZodUserOutWrite = ZodUserOut.partial(); });
export type UserOutWrite = zod.infer<typeof ZodUserOutWrite>; export type UserOutWrite = zod.infer<typeof ZodUserOutWrite>;

View File

@ -5,14 +5,13 @@ import { z as zod } from "zod";
import {ZodTimestamp} from "./timestamp"; import {ZodTimestamp} from "./timestamp";
import {ZodUUID} from "./uuid"; import {ZodUUID} from "./uuid";
import {ZodGenericDataSoftDelete} from "./generic-data-soft-delete"; import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
export const ZodUser = ZodGenericDataSoftDelete.extend({ export const ZodUser = ZodGenericDataSoftDelete.extend({
login: zod.string().max(128).optional(), login: zod.string().min(3).max(128),
lastConnection: ZodTimestamp.optional(), lastConnection: ZodTimestamp.optional(),
admin: zod.boolean(), blocked: zod.boolean().optional(),
blocked: zod.boolean(), blockedReason: zod.string().max(512).optional(),
removed: zod.boolean(),
/** /**
* List of Id of the specific covers * List of Id of the specific covers
*/ */
@ -31,14 +30,17 @@ export function isUser(data: any): data is User {
return false; return false;
} }
} }
export const ZodUserWrite = ZodGenericDataSoftDeleteWrite.extend({
login: zod.string().min(3).max(128).optional(),
lastConnection: ZodTimestamp.nullable().optional(),
blocked: zod.boolean().nullable().optional(),
blockedReason: zod.string().max(512).nullable().optional(),
/**
* List of Id of the specific covers
*/
covers: zod.array(ZodUUID).nullable().optional(),
export const ZodUserWrite = ZodUser.omit({ });
deleted: true,
id: true,
createdAt: true,
updatedAt: true,
}).partial();
export type UserWrite = zod.infer<typeof ZodUserWrite>; export type UserWrite = zod.infer<typeof ZodUserWrite>;

View File

@ -7,11 +7,15 @@
import { RestErrorResponse, isRestErrorResponse } from "./model"; import { RestErrorResponse, isRestErrorResponse } from "./model";
export enum HTTPRequestModel { export enum HTTPRequestModel {
ARCHIVE = "ARCHIVE",
DELETE = "DELETE", DELETE = "DELETE",
HEAD = "HEAD",
GET = "GET", GET = "GET",
OPTION = "OPTION",
PATCH = "PATCH", PATCH = "PATCH",
POST = "POST", POST = "POST",
PUT = "PUT", PUT = "PUT",
RESTORE = "RESTORE",
} }
export enum HTTPMimeType { export enum HTTPMimeType {
ALL = "*/*", ALL = "*/*",
@ -74,7 +78,7 @@ export interface RESTRequestType {
data?: any; data?: any;
params?: object; params?: object;
queries?: object; queries?: object;
callback?: RESTCallbacks; callbacks?: RESTCallbacks;
} }
function replaceAll(input, searchValue, replaceValue) { function replaceAll(input, searchValue, replaceValue) {
@ -237,7 +241,7 @@ export function RESTRequest({
data, data,
params, params,
queries, queries,
callback, callbacks,
}: RESTRequestType): Promise<ModelResponseHttp> { }: RESTRequestType): Promise<ModelResponseHttp> {
// Create the URL PATH: // Create the URL PATH:
let generateUrl = RESTUrl({ restModel, restConfig, data, params, queries }); let generateUrl = RESTUrl({ restModel, restConfig, data, params, queries });
@ -248,9 +252,14 @@ export function RESTRequest({
if (restModel.accept !== undefined) { if (restModel.accept !== undefined) {
headers["Accept"] = restModel.accept; headers["Accept"] = restModel.accept;
} }
if (restModel.requestType !== HTTPRequestModel.GET) { if (restModel.requestType !== HTTPRequestModel.GET &&
restModel.requestType !== HTTPRequestModel.ARCHIVE &&
restModel.requestType !== HTTPRequestModel.RESTORE
) {
// if Get we have not a content type, the body is empty // if Get we have not a content type, the body is empty
if (restModel.contentType !== HTTPMimeType.MULTIPART) { if (restModel.contentType !== HTTPMimeType.MULTIPART &&
restModel.contentType !== undefined
) {
// special case of multi-part ==> no content type otherwise the browser does not set the ";bundary=--****" // special case of multi-part ==> no content type otherwise the browser does not set the ";bundary=--****"
headers["Content-Type"] = restModel.contentType; headers["Content-Type"] = restModel.contentType;
} }
@ -268,10 +277,10 @@ export function RESTRequest({
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let action: undefined | Promise<Response> = undefined; let action: undefined | Promise<Response> = undefined;
if ( if (
isNullOrUndefined(callback) || isNullOrUndefined(callbacks) ||
(isNullOrUndefined(callback.progressDownload) && (isNullOrUndefined(callbacks.progressDownload) &&
isNullOrUndefined(callback.progressUpload) && isNullOrUndefined(callbacks.progressUpload) &&
isNullOrUndefined(callback.abortHandle)) isNullOrUndefined(callbacks.abortHandle))
) { ) {
// No information needed: call the generic fetch interface // No information needed: call the generic fetch interface
action = fetch(generateUrl, { action = fetch(generateUrl, {
@ -288,7 +297,7 @@ export function RESTRequest({
headers, headers,
body, body,
}, },
callback callbacks
); );
} }
action action

View File

@ -1,57 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2018, Edouard DUPIN, all right reserved
* @license PROPRIETARY (see license file)
*/
import { Injectable } from '@angular/core';
import { SessionService } from '@kangaroo-and-rabbit/kar-cw';
import { ApplicationToken, ApplicationTokenResource, Long } from 'back-api';
import { RESTConfig } from 'back-api/rest-tools';
import { environment } from 'environments/environment';
@Injectable()
export class ApplicationTokenService {
getRestConfig(): RESTConfig {
return {
server: environment.server.karso,
token: this.session.getToken()
}
}
constructor(
private session: SessionService) {
console.log('Start ApplicationTokenService');
}
gets(applicationId: Long): Promise<ApplicationToken[]> {
return ApplicationTokenResource.gets({
restConfig: this.getRestConfig(),
params: {
applicationId
}
});
}
create(applicationId: number, name: string, validity: number): Promise<ApplicationToken> {
return ApplicationTokenResource.create({
restConfig: this.getRestConfig(),
params: {
applicationId
},
data: {
name,
validity
}
});
}
remove(applicationId: number, tokenId: number): Promise<void> {
return ApplicationTokenResource.remove({
restConfig: this.getRestConfig(),
params: {
applicationId,
tokenId
}
});
}
}

View File

@ -1,127 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2018, Edouard DUPIN, all right reserved
* @license PROPRIETARY (see license file)
*/
import { Injectable } from '@angular/core';
import { SessionService } from '@kangaroo-and-rabbit/kar-cw';
import { RightDescription, ApplicationSmall, Application, ApplicationResource, ClientToken, Long } from 'back-api';
import { RESTConfig } from 'back-api/rest-tools';
import { environment } from 'environments/environment';
@Injectable()
export class ApplicationService {
getRestConfig(): RESTConfig {
return {
server: environment.server.karso,
token: this.session.getToken()
}
}
constructor(private session: SessionService) {
console.log('Start ApplicationService');
}
getRights(id: Long): Promise<RightDescription[]> {
return ApplicationResource.getRightsDescription({
restConfig: this.getRestConfig(),
params: {
id
}
});
}
getApplicationSpecificToken(application: string): Promise<ClientToken> {
return ApplicationResource.getClientToken({
restConfig: this.getRestConfig(),
queries: {
application
}
});
}
addUser(id: Long, userId: Long): Promise<void> {
return ApplicationResource.addUser({
restConfig: this.getRestConfig(),
params: {
id
},
data: {
userId
}
});
}
removeUser(id: Long, userId: Long): Promise<void> {
return ApplicationResource.removeUser({
restConfig: this.getRestConfig(),
params: {
id,
userId
},
});
}
getApplicationReturn(application: string): Promise<string> {
return ApplicationResource.logOut({
restConfig: this.getRestConfig(),
queries: {
application
},
});
}
getApplicationsSmall(): Promise<ApplicationSmall[]> {
return ApplicationResource.getApplicationsSmall({
restConfig: this.getRestConfig(),
});
}
getUsers(id: number): Promise<Long[]> {
return ApplicationResource.getApplicationUsers({
restConfig: this.getRestConfig(),
params: {
id,
}
});
}
gets(): Promise<Application[]> {
return ApplicationResource.gets({
restConfig: this.getRestConfig(),
});
}
get(id: number): Promise<Application> {
return ApplicationResource.get({
restConfig: this.getRestConfig(),
params: {
id,
}
});
}
update(id: number, updateState: Application): Promise<Application> {
return ApplicationResource.patch({
restConfig: this.getRestConfig(),
params: {
id,
},
data: updateState
});
}
create(name: string, redirect: string): Promise<Application> {
return ApplicationResource.create({
restConfig: this.getRestConfig(),
data: {
name,
redirect,
}
});
}
remove(id: number): Promise<void> {
return ApplicationResource.remove({
restConfig: this.getRestConfig(),
params: {
id,
},
});
}
}

View File

@ -0,0 +1,112 @@
import { ReactElement, useEffect, useState } from 'react';
import { Box, BoxProps, Flex, FlexProps } from '@chakra-ui/react';
import { Image } from '@chakra-ui/react';
import { ObjectId } from '@/back-api';
import { DataUrlAccess } from '@/utils/data-url-access';
import { Icon } from './Icon';
export type CoversProps = Omit<BoxProps, 'iconEmpty'> & {
data?: ObjectId[];
size?: BoxProps['width'];
iconEmpty?: ReactElement;
slideshow?: boolean;
};
export const Covers = ({
data,
iconEmpty,
size = '100px',
slideshow = false,
...rest
}: CoversProps) => {
const [currentImageIndex, setCurrentImageIndex] = useState(0);
const [previousImageIndex, setPreviousImageIndex] = useState(0);
const [topOpacity, setTopOpacity] = useState(0.0);
useEffect(() => {
if (!slideshow) {
return;
}
const interval = setInterval(() => {
setPreviousImageIndex(currentImageIndex);
setTopOpacity(0.0);
setTimeout(() => {
setCurrentImageIndex(
(prevIndex) => (prevIndex + 1) % (data?.length ?? 1)
);
setTopOpacity(1.0);
}, 1500);
}, 3000);
return () => clearInterval(interval);
}, [slideshow, data]);
if (!data || data.length < 1) {
if (iconEmpty) {
return <Icon children={iconEmpty} sizeIcon={size} />;
} else {
return (
<Box
width={size}
height={size}
minHeight={size}
minWidth={size}
borderColor="blue"
borderWidth="1px"
margin="auto"
{...rest}
></Box>
);
}
}
if (slideshow === false || data.length === 1) {
const url = DataUrlAccess.getThumbnailUrl(data[0]);
return (
<Image
loading="lazy"
src={url}
maxWidth={size}
boxSize={size} /*{...rest}*/
/>
);
}
const urlCurrent = DataUrlAccess.getThumbnailUrl(data[currentImageIndex]);
const urlPrevious = DataUrlAccess.getThumbnailUrl(data[previousImageIndex]);
return (
<Flex
position="relative"
// {...rest}
maxWidth={size}
width={size}
height={size}
overflow="hidden"
>
<Image
src={urlPrevious}
loading="lazy"
position="absolute"
top="0"
left="0"
width="100%"
height="100%"
zIndex={1}
boxSize={size}
/>
<Image
src={urlCurrent}
loading="lazy"
position="absolute"
top="0"
left="0"
width="100%"
height="100%"
boxSize={size}
transition="opacity 0.5s ease-in-out"
opacity={topOpacity}
zIndex={2}
/>
</Flex>
);
};

View File

@ -0,0 +1,13 @@
import { Box } from '@chakra-ui/react';
export const EmptyEnd = () => {
return (
<Box
width="full"
height="25%"
minHeight="250px"
// borderWidth="1px"
// borderColor="red"
></Box>
);
};

View File

@ -0,0 +1,120 @@
import {
Box,
Button,
Dialog,
Select,
Span,
Stack,
Text,
createListCollection,
useDisclosure,
} from '@chakra-ui/react';
import { useLogin } from '@/scene/connection/useLogin';
import { useSessionService } from '@/service/session';
export const USERS_COLLECTION = createListCollection({
items: [
{ label: 'karadmin', value: 'adminA@666' },
{ label: 'karuser', value: 'userA@666' },
{ label: 'NO_USER', value: '' },
],
});
export const EnvDevelopment = () => {
const dialog = useDisclosure();
const { clearToken } = useSessionService();
const { connect, lastError } = useLogin();
const buildEnv =
process.env.NODE_ENV === 'development'
? 'Development'
: import.meta.env.VITE_DEV_ENV_NAME;
const envName: Array<string> = [];
!!buildEnv && envName.push(buildEnv);
if (!envName.length) {
return null;
}
const handleChange = (key: string, value: string) => {
console.log(`SELECT: [${key}:${value}]`);
if (key === 'NO_USER') {
clearToken();
} else {
connect(key, value);
}
};
return (
<>
<Box
as="button"
zIndex="100000"
position="fixed"
top="0"
insetStart="0"
insetEnd="0"
h="2px"
bg="warning.400"
cursor="pointer"
data-test-id="devtools"
onClick={dialog.onOpen}
>
<Text
position="fixed"
top="0"
insetStart="4"
bg="warning.400"
color="warning.900"
fontSize="0.6rem"
fontWeight="bold"
px="10px"
marginLeft="25%"
borderBottomStartRadius="sm"
borderBottomEndRadius="sm"
textTransform="uppercase"
>
{envName.join(' : ')}
</Text>
</Box>
<Dialog.Root open={dialog.open} onOpenChange={dialog.onClose}>
<Dialog.Positioner>
<Dialog.Backdrop />
<Dialog.Content>
<Dialog.Header>Development tools</Dialog.Header>
<Dialog.Body>
<Stack>
<Text>
User{' '}
<Span color="red" fontWeight="bold">
{lastError}
</Span>
</Text>
<Select.Root collection={USERS_COLLECTION}>
<Select.Trigger>
<Select.ValueText placeholder="Select test user" />
</Select.Trigger>
<Select.Content>
{USERS_COLLECTION.items.map((value) => (
<Select.Item
item={value}
key={value.value}
onClick={() => handleChange(value.label, value.value)}
>
{value.label}
</Select.Item>
))}
</Select.Content>
</Select.Root>
</Stack>
</Dialog.Body>
<Dialog.Footer>
<Button onClick={dialog.onClose}>Close</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Positioner>
</Dialog.Root>
</>
);
};

View File

@ -0,0 +1,40 @@
import { ReactNode, forwardRef } from 'react';
import { Box, Flex, FlexProps } from '@chakra-ui/react';
export type IconProps = FlexProps & {
children: ReactNode;
color?: string;
sizeIcon?: FlexProps['width'];
};
export const Icon = forwardRef<HTMLDivElement, IconProps>(
({ children, color, sizeIcon = '1em', ...rest }, ref) => {
return (
<Flex
flex="none"
minWidth={sizeIcon}
minHeight={sizeIcon}
maxWidth={sizeIcon}
maxHeight={sizeIcon}
align="center"
padding="1px"
ref={ref}
{...rest}
>
<Box
marginX="auto"
width="100%"
minWidth="100%"
height="100%"
color={color}
asChild
>
{children}
</Box>
</Flex>
);
}
);
Icon.displayName = 'Icon';

View File

@ -0,0 +1,52 @@
import React, { ReactNode, useEffect } from 'react';
import { Flex, Image } from '@chakra-ui/react';
import { useLocation } from 'react-router-dom';
import ikon from '@/assets/images/ikon.svg';
import { TOP_BAR_HEIGHT } from '@/components/TopBar/TopBar';
export type LayoutProps = React.PropsWithChildren<unknown> & {
topBar?: ReactNode;
};
export const PageLayout = ({ children }: LayoutProps) => {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return (
<>
<Flex
minH={`calc(100vh - ${TOP_BAR_HEIGHT})`}
maxH={`calc(100vh - ${TOP_BAR_HEIGHT})`}
position="absolute"
top={TOP_BAR_HEIGHT}
bottom={0}
left={0}
right={0}
minWidth="300px"
zIndex={0}
>
<Image src={ikon} boxSize="90vh" margin="auto" />
</Flex>
<Flex
direction="column"
overflowX="auto"
overflowY="auto"
minH={`calc(100vh - ${TOP_BAR_HEIGHT})`}
maxH={`calc(100vh - ${TOP_BAR_HEIGHT})`}
position="absolute"
top={TOP_BAR_HEIGHT}
bottom={0}
left={0}
right={0}
minWidth="300px"
>
{children}
</Flex>
</>
);
};

View File

@ -0,0 +1,43 @@
import { ReactNode, useEffect } from 'react';
import { Flex, FlexProps } from '@chakra-ui/react';
import { useLocation } from 'react-router-dom';
import { PageLayout } from '@/components/Layout/PageLayout';
import { useColorModeValue } from '@/components/ui/color-mode';
import { colors } from '@/theme/colors';
export type LayoutProps = FlexProps & {
children: ReactNode;
};
export const PageLayoutInfoCenter = ({
children,
width = '25%',
...rest
}: LayoutProps) => {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return (
<PageLayout>
<Flex
direction="column"
margin="auto"
minWidth={width}
border="back.900"
borderWidth="1px"
borderRadius="8px"
padding="10px"
boxShadow={'0px 0px 16px ' + colors.back[900]}
backgroundColor={useColorModeValue('#FFFFFF', '#000000')}
{...rest}
>
{children}
</Flex>
</PageLayout>
);
};

View File

@ -0,0 +1,20 @@
export {
ParameterLayoutContent as Content,
type ParameterLayoutContentProps as ContentProps,
} from './ParameterLayoutContent';
export {
ParameterLayoutFooter as Footer,
type ParameterLayoutFooterProps as FooterProps,
} from './ParameterLayoutFooter';
export {
ParameterLayoutHeader as Header,
type ParameterLayoutHeaderProps as HeaderProps,
} from './ParameterLayoutHeader';
export {
ParameterLayoutHeaderBase as HeaderBase,
type ParameterLayoutHeaderBaseProps as HeaderBaseProps,
} from './ParameterLayoutHeaderBase';
export {
ParameterLayoutRoot as Root,
type ParameterLayoutRootProps as RootProps,
} from './ParameterLayoutRoot';

View File

@ -0,0 +1,25 @@
import { ReactNode } from 'react';
import { Flex } from '@chakra-ui/react';
export type ParameterLayoutContentProps = {
children?: ReactNode;
};
export const ParameterLayoutContent = ({
children,
}: ParameterLayoutContentProps) => {
return (
<Flex
direction="column"
width="full"
borderY="1px solid black"
paddingY="15px"
paddingX="25px"
minHeight="10px"
background="gray.700"
>
{children}
</Flex>
);
};

View File

@ -0,0 +1,17 @@
import { ReactNode } from 'react';
import { Flex } from '@chakra-ui/react';
export type ParameterLayoutFooterProps = {
children?: ReactNode;
};
export const ParameterLayoutFooter = ({
children,
}: ParameterLayoutFooterProps) => {
return (
<Flex width="full" paddingY="15px" paddingX="25px" minHeight="10px">
{children}
</Flex>
);
};

View File

@ -0,0 +1,17 @@
import { ReactNode } from 'react';
import { Flex } from '@chakra-ui/react';
export type ParameterLayoutHeaderProps = {
children?: ReactNode;
};
export const ParameterLayoutHeader = ({
children,
}: ParameterLayoutHeaderProps) => {
return (
<Flex width="full" paddingY="15px" paddingX="25px" minHeight="10px">
{children}
</Flex>
);
};

View File

@ -0,0 +1,24 @@
import { Flex, Text } from '@chakra-ui/react';
import { ParameterLayoutHeader } from './ParameterLayoutHeader';
export type ParameterLayoutHeaderBaseProps = {
title: string;
description?: string;
};
export const ParameterLayoutHeaderBase = ({
title,
description,
}: ParameterLayoutHeaderBaseProps) => {
return (
<ParameterLayoutHeader>
<Flex direction="column">
<Text fontSize="25px" fontWeight="bold">
{title}
</Text>
{description && <Text>{description}</Text>}
</Flex>
</ParameterLayoutHeader>
);
};

View File

@ -0,0 +1,24 @@
import { ReactNode } from 'react';
import { VStack } from '@chakra-ui/react';
export type ParameterLayoutRootProps = {
children?: ReactNode;
};
export const ParameterLayoutRoot = ({ children }: ParameterLayoutRootProps) => {
return (
<VStack
gap="0px"
marginX="15%"
marginY="20px"
justify="center"
//borderRadius="20px"
borderRadius="0px"
border="1px solid black"
background="gray.500"
>
{children}
</VStack>
);
};

View File

@ -0,0 +1 @@
export * as ParameterLayout from './ParameterLayout';

View File

@ -0,0 +1,53 @@
import { useState } from 'react';
import { Group, Input } from '@chakra-ui/react';
import { MdSearch } from 'react-icons/md';
export type SearchInputProps = {
onChange?: (data?: string) => void;
onSubmit?: (data?: string) => void;
};
export const SearchInput = ({
onChange: onChangeValue,
onSubmit: onSubmitValue,
}: SearchInputProps) => {
const [inputData, setInputData] = useState<string | undefined>(undefined);
const [searchInputProperty, setSearchInputProperty] =
useState<any>(undefined);
function onFocusKeep(): void {
setSearchInputProperty({
width: '70%',
maxWidth: '70%',
});
}
function onFocusLost(): void {
setSearchInputProperty({
width: '250px',
});
}
function onChange(event): void {
const data =
event.target.value.length === 0 ? undefined : event.target.value;
setInputData(data);
if (onChangeValue) {
onChangeValue(data);
}
}
function onSubmit(): void {
if (onSubmitValue) {
onSubmitValue(inputData);
}
}
return (
<Group maxWidth="200px" marginLeft="auto" {...searchInputProperty}>
<MdSearch color="gray.300" />
<Input
onFocus={onFocusKeep}
onBlur={() => setTimeout(() => onFocusLost(), 200)}
onChange={onChange}
onSubmit={onSubmit}
/>
</Group>
);
};

View File

@ -0,0 +1,296 @@
import { ReactNode } from 'react';
import {
Box,
Button,
ConditionalValue,
Flex,
HStack,
IconButton,
Span,
Text,
chakra,
useDisclosure,
} from '@chakra-ui/react';
import {
LuAlignJustify,
LuArrowBigLeft,
LuCircleUserRound,
LuLogIn,
LuLogOut,
LuMoon,
LuSettings,
LuSun,
} from 'react-icons/lu';
import {
MdHelp,
MdHome,
MdKey,
MdMore,
MdOutlineDashboardCustomize,
MdOutlineGroup,
MdSettings,
MdSupervisedUserCircle,
} from 'react-icons/md';
import { useNavigate } from 'react-router-dom';
import { useColorMode, useColorModeValue } from '@/components/ui/color-mode';
import {
DrawerBody,
DrawerContent,
DrawerHeader,
DrawerRoot,
} from '@/components/ui/drawer';
import {
MenuContent,
MenuItem,
MenuRoot,
MenuTrigger,
} from '@/components/ui/menu';
import { useServiceContext } from '@/service/ServiceContext';
import { useSessionService } from '@/service/session';
import { colors } from '@/theme/colors';
export const TOP_BAR_HEIGHT = '50px';
export const BUTTON_TOP_BAR_PROPERTY = {
variant: 'ghost' as ConditionalValue<
'ghost' | 'outline' | 'solid' | 'subtle' | 'surface' | 'plain' | undefined
>,
//colorPalette: "brand",
fontSize: '20px',
textTransform: 'uppercase',
height: TOP_BAR_HEIGHT,
};
export type TopBarProps = {
children?: ReactNode;
title?: string;
};
const ButtonMenuLeft = ({
dest,
title,
icon,
onClickEnd = () => {},
}: {
dest: string;
title: string;
icon: ReactNode;
onClickEnd?: () => void;
}) => {
const navigate = useNavigate();
return (
<>
<Button
background="#00000000"
borderRadius="0px"
onClick={() => {
navigate(dest);
onClickEnd();
}}
width="full"
{...BUTTON_TOP_BAR_PROPERTY}
>
<Box asChild style={{ width: '45px', height: '45px' }}>
{icon}
</Box>
<Text paddingLeft="3px" fontWeight="bold" marginRight="auto">
{title}
</Text>
</Button>
<Box marginY="5" marginX="10" height="2px" background="brand.600" />
</>
);
};
export const TopBar = ({ title, children }: TopBarProps) => {
const { colorMode, toggleColorMode } = useColorMode();
const { session } = useServiceContext();
const backColor = useColorModeValue('back.100', 'back.800');
const drawerDisclose = useDisclosure();
const onChangeTheme = () => {
drawerDisclose.onOpen();
};
const navigate = useNavigate();
return (
<Flex
position="absolute"
top={0}
left={0}
right={0}
height={TOP_BAR_HEIGHT}
alignItems="center"
justifyContent="space-between"
backgroundColor={backColor}
gap="2"
px="2"
boxShadow={'0px 2px 4px ' + colors.back[900]}
zIndex={200}
>
<Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onChangeTheme}>
<HStack>
<LuAlignJustify />
<Text paddingLeft="3px" fontWeight="bold">
karso
</Text>
</HStack>
</Button>
{title && (
<Text
fontSize="20px"
fontWeight="bold"
textTransform="uppercase"
marginRight="auto"
userSelect="none"
color="brand.500"
>
{title}
</Text>
)}
{children}
<Flex right="0">
{!session?.token && (
<>
<Button
{...BUTTON_TOP_BAR_PROPERTY}
onClick={() => navigate('/login')}
>
<LuLogIn />
<Text paddingLeft="3px" fontWeight="bold">
Sign-in
</Text>
</Button>
<Button
{...BUTTON_TOP_BAR_PROPERTY}
onClick={() => navigate('/singup')}
disabled={true}
>
<MdMore />
<Text paddingLeft="3px" fontWeight="bold">
Sign-up
</Text>
</Button>
</>
)}
{session?.token && (
<MenuRoot>
<MenuTrigger asChild>
<IconButton
{...BUTTON_TOP_BAR_PROPERTY}
width={TOP_BAR_HEIGHT}
>
<LuCircleUserRound />
</IconButton>
</MenuTrigger>
<MenuContent>
<MenuItem
value="user"
valueText="user"
color={useColorModeValue('brand.800', 'brand.200')}
>
<MdSupervisedUserCircle />
<Box flex="1">Sign in as {session?.login ?? 'Fail'}</Box>
</MenuItem>
<MenuItem
value="Settings"
valueText="Settings"
onClick={() => navigate('/settings')}
>
<LuSettings />
Settings
</MenuItem>
<MenuItem
value="Help"
valueText="Help"
onClick={() => navigate('/help')}
>
<MdHelp /> Help
</MenuItem>
<MenuItem
value="Sign-out"
valueText="Sign-out"
onClick={() => navigate('/signout')}
>
<LuLogOut /> Sign-out
</MenuItem>
{colorMode === 'light' ? (
<MenuItem
value="set-dark"
valueText="set-dark"
onClick={toggleColorMode}
>
<LuMoon /> Set dark mode
</MenuItem>
) : (
<MenuItem
value="set-light"
valueText="set-light"
onClick={toggleColorMode}
>
<LuSun /> Set light mode
</MenuItem>
)}
</MenuContent>
</MenuRoot>
)}
</Flex>
<DrawerRoot
placement="start"
onOpenChange={drawerDisclose.onClose}
open={drawerDisclose.open}
data-testid="top-bar_drawer-root"
>
<DrawerContent data-testid="top-bar_drawer-content">
<DrawerHeader
paddingY="auto"
as="button"
onClick={drawerDisclose.onClose}
boxShadow={'0px 2px 4px ' + colors.back[900]}
backgroundColor={backColor}
color={useColorModeValue('brand.900', 'brand.50')}
textTransform="uppercase"
>
<HStack {...BUTTON_TOP_BAR_PROPERTY} cursor="pointer">
<LuArrowBigLeft />
<Span paddingLeft="3px">karso</Span>
</HStack>
</DrawerHeader>
<DrawerBody paddingX="0px">
<Box marginY="3" />
<ButtonMenuLeft
onClickEnd={drawerDisclose.onClose}
dest="/"
title="Home"
icon={<MdHome />}
/>
<ButtonMenuLeft
onClickEnd={drawerDisclose.onClose}
dest="/change-password"
title="Change password"
icon={<MdKey />}
/>
<ButtonMenuLeft
onClickEnd={drawerDisclose.onClose}
dest="/admin-settings"
title="Admin settings"
icon={<MdSettings />}
/>
<ButtonMenuLeft
onClickEnd={drawerDisclose.onClose}
dest="/manage-account"
title="Manage account"
icon={<MdOutlineGroup />}
/>
<ButtonMenuLeft
onClickEnd={drawerDisclose.onClose}
dest="/manage-application"
title="Manage application"
icon={<MdOutlineDashboardCustomize />}
/>
</DrawerBody>
</DrawerContent>
</DrawerRoot>
</Flex>
);
};

View File

@ -0,0 +1,52 @@
import { ReactNode } from 'react';
import { LuMenu } from 'react-icons/lu';
import { Button } from '../ui/button';
import { MenuContent, MenuItem, MenuRoot, MenuTrigger } from '../ui/menu';
export type MenuElement = {
icon?: ReactNode;
name: string;
onClick: () => void;
};
export type ContextMenuProps = {
elements?: MenuElement[];
};
export const ContextMenu = ({ elements }: ContextMenuProps) => {
if (!elements) {
return <></>;
}
return (
<MenuRoot data-testid="context-menu">
<MenuTrigger
asChild
marginY="auto"
marginRight="4px"
data-testid="context-menu_trigger"
>
{/* This is very stupid, we need to set as span to prevent a button in button... WTF */}
<Button variant="ghost" color="brand.500">
<LuMenu />
</Button>
</MenuTrigger>
<MenuContent data-testid="context-menu_content">
{elements?.map((data) => (
<MenuItem
key={data.name}
value={data.name}
onClick={data.onClick}
height="65px"
fontSize="25px"
data-test-id="context-menu_item"
>
{data.icon}
{data.name}
</MenuItem>
))}
</MenuContent>
</MenuRoot>
);
};

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