[FEAT] update to the new archidata 0.28.0, and update namespace kar-> atriasoft

This commit is contained in:
Edouard DUPIN 2025-04-15 00:18:49 +02:00
parent 77acd2060b
commit 3d7ee42ca8
97 changed files with 3144 additions and 2834 deletions

View File

@ -98,4 +98,4 @@ EXPOSE 80
HEALTHCHECK --start-period=10s --start-interval=2s --interval=30s --timeout=5s --retries=10 \
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.karideo.WebLauncher"]
CMD ["java", "-Xms64M", "-Xmx1G", "-cp", "/application/application.jar", "org.atriasoft.karideo.WebLauncher"]

View File

@ -11,7 +11,7 @@ mvn package
// download all dependency in out/maven/dependency
mvn dependency:copy-dependencies
java -cp out/maven/kar-karideo-0.1.0.jar org.kar.karideo.WebLauncher
java -cp out/maven/kar-karideo-0.1.0.jar org.atriasoft.karideo.WebLauncher
// create a single package jar
@ -19,7 +19,7 @@ mvn clean compile assembly:single
java -cp out/maven/karideo-0.1.0-jar-with-dependencies.jar org.kar.karideo.WebLauncher
java -cp out/maven/karideo-0.1.0-jar-with-dependencies.jar org.atriasoft.karideo.WebLauncher

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.kar</groupId>
<groupId>org.atriasoft</groupId>
<artifactId>karideo</artifactId>
<version>0.3.0</version>
<properties>
@ -10,17 +10,11 @@
<maven.compiler.target>23</maven.compiler.target>
<maven.dependency.version>3.1.1</maven.dependency.version>
</properties>
<repositories>
<repository>
<id>gitea</id>
<url>https://gitea.atria-soft.org/api/packages/kangaroo-and-rabbit/maven</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>kangaroo-and-rabbit</groupId>
<groupId>org.atria-soft</groupId>
<artifactId>archidata</artifactId>
<version>0.24.0</version>
<version>0.28.0</version>
</dependency>
<!-- Loopback of logger JDK logging API to SLF4J -->
<dependency>
@ -112,7 +106,7 @@
<goal>java</goal>
</goals>
<configuration>
<mainClass>org.kar.karideo.WebLauncher</mainClass>
<mainClass>org.atriasoft.karideo.WebLauncher</mainClass>
</configuration>
</execution>
<execution>
@ -121,7 +115,7 @@
<goal>java</goal>
</goals>
<configuration>
<mainClass>org.kar.karideo.WebLauncherLocal</mainClass>
<mainClass>org.atriasoft.karideo.WebLauncherLocal</mainClass>
</configuration>
</execution>
<execution>
@ -130,7 +124,7 @@
<goal>java</goal>
</goals>
<configuration>
<mainClass>org.kar.karideo.GenerateApi</mainClass>
<mainClass>org.atriasoft.karideo.GenerateApi</mainClass>
</configuration>
</execution>
</executions>

View File

@ -1,9 +1,9 @@
org.kar.karideo.dataTmpFolder=/application/data/tmp
org.kar.karideo.dataTmpFolder=/application/data/media
org.kar.karideo.rest.oauth=http://192.168.1.156:21080/oauth/api/
org.kar.karideo.db.host=1992.156.1.156
org.kar.karideo.db.port=20306
org.kar.karideo.db.login=root
org.kar.karideo.db.port=klkhj456gkgtkhjgvkujfhjgkjhgsdfhb3467465fgdhdesfgh
org.kar.karideo.db.name=karideo
org.kar.karideo.address=http://0.0.0.0:18080/karideo/api/
org.atriasoft.karideo.dataTmpFolder=/application/data/tmp
org.atriasoft.karideo.dataTmpFolder=/application/data/media
org.atriasoft.karideo.rest.oauth=http://192.168.1.156:21080/oauth/api/
org.atriasoft.karideo.db.host=1992.156.1.156
org.atriasoft.karideo.db.port=20306
org.atriasoft.karideo.db.login=root
org.atriasoft.karideo.db.port=klkhj456gkgtkhjgvkujfhjgkjhgsdfhb3467465fgdhdesfgh
org.atriasoft.karideo.db.name=karideo
org.atriasoft.karideo.address=http://0.0.0.0:18080/karideo/api/

View File

@ -1,6 +1,6 @@
package org.kar.karideo;
package org.atriasoft.karideo;
import org.kar.karideo.migration.Initialization;
import org.atriasoft.karideo.migration.Initialization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -1,38 +1,40 @@
package org.kar.karideo;
package org.atriasoft.karideo;
import java.net.URI;
import java.util.logging.LogManager;
import org.atriasoft.archidata.UpdateJwtPublicKey;
import org.atriasoft.archidata.api.DataResource;
import org.atriasoft.archidata.catcher.GenericCatcher;
import org.atriasoft.archidata.db.DbConfig;
import org.atriasoft.archidata.filter.CORSFilter;
import org.atriasoft.archidata.filter.OptionFilter;
import org.atriasoft.archidata.migration.MigrationEngine;
import org.atriasoft.archidata.tools.ConfigBaseVariable;
import org.atriasoft.archidata.tools.ContextGenericTools;
import org.atriasoft.karideo.api.Front;
import org.atriasoft.karideo.api.HealthCheck;
import org.atriasoft.karideo.api.MediaResource;
import org.atriasoft.karideo.api.SeasonResource;
import org.atriasoft.karideo.api.SeriesResource;
import org.atriasoft.karideo.api.TypeResource;
import org.atriasoft.karideo.api.UserMediaAdvancementResource;
import org.atriasoft.karideo.api.UserResource;
import org.atriasoft.karideo.filter.KarideoAuthenticationFilter;
import org.atriasoft.karideo.migration.Initialization;
import org.atriasoft.karideo.migration.Migration20230810;
import org.atriasoft.karideo.migration.Migration20231015;
import org.atriasoft.karideo.migration.Migration20231126;
import org.atriasoft.karideo.migration.Migration20240226;
import org.atriasoft.karideo.migration.Migration20240611;
import org.atriasoft.karideo.migration.Migration20250214;
import org.atriasoft.karideo.migration.Migration20250414;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.kar.archidata.UpdateJwtPublicKey;
import org.kar.archidata.api.DataResource;
import org.kar.archidata.catcher.GenericCatcher;
import org.kar.archidata.db.DbConfig;
import org.kar.archidata.filter.CORSFilter;
import org.kar.archidata.filter.OptionFilter;
import org.kar.archidata.migration.MigrationEngine;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.kar.archidata.tools.ContextGenericTools;
import org.kar.karideo.api.Front;
import org.kar.karideo.api.HealthCheck;
import org.kar.karideo.api.MediaResource;
import org.kar.karideo.api.SeasonResource;
import org.kar.karideo.api.SeriesResource;
import org.kar.karideo.api.TypeResource;
import org.kar.karideo.api.UserMediaAdvancementResource;
import org.kar.karideo.api.UserResource;
import org.kar.karideo.filter.KarideoAuthenticationFilter;
import org.kar.karideo.migration.Initialization;
import org.kar.karideo.migration.Migration20230810;
import org.kar.karideo.migration.Migration20231015;
import org.kar.karideo.migration.Migration20231126;
import org.kar.karideo.migration.Migration20240226;
import org.kar.karideo.migration.Migration20240611;
import org.kar.karideo.migration.Migration20250214;
import org.glassfish.jersey.server.validation.ValidationFeature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;
@ -64,6 +66,7 @@ public class WebLauncher {
migrationEngine.add(new Migration20240226());
migrationEngine.add(new Migration20240611());
migrationEngine.add(new Migration20250214());
migrationEngine.add(new Migration20250414());
WebLauncher.LOGGER.info("Migrate the DB [START]");
migrationEngine.migrateWaitAdmin(new DbConfig());
WebLauncher.LOGGER.info("Migrate the DB [STOP]");
@ -116,6 +119,8 @@ public class WebLauncher {
ContextGenericTools.addJsr310(rc);
// add jackson to be discover when we are ins stand-alone server
rc.register(JacksonFeature.class);
// enable jersey specific validations (@Valid)
rc.register(ValidationFeature.class);
// enable this to show low level request
// rc.property(LoggingFeature.LOGGING_FEATURE_LOGGER_LEVEL_SERVER, Level.WARNING.getName());

View File

@ -1,10 +1,10 @@
package org.kar.karideo;
package org.atriasoft.karideo;
import java.util.logging.LogManager;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.kar.karideo.migration.Initialization;
import org.atriasoft.archidata.tools.ConfigBaseVariable;
import org.atriasoft.karideo.migration.Initialization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;

View File

@ -1,10 +1,10 @@
package org.kar.karideo.api;
package org.atriasoft.karideo.api;
import org.kar.archidata.api.FrontGeneric;
import org.atriasoft.archidata.api.FrontGeneric;
import jakarta.ws.rs.Path;
import org.kar.karideo.util.ConfigVariable;
import org.atriasoft.karideo.util.ConfigVariable;
@Path("/front")
public class Front extends FrontGeneric {

View File

@ -1,8 +1,8 @@
package org.kar.karideo.api;
package org.atriasoft.karideo.api;
import org.kar.archidata.exception.FailException;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.kar.archidata.tools.JWTWrapper;
import org.atriasoft.archidata.exception.FailException;
import org.atriasoft.archidata.tools.ConfigBaseVariable;
import org.atriasoft.archidata.tools.JWTWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -1,37 +1,38 @@
package org.kar.karideo.api;
package org.atriasoft.karideo.api;
import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.List;
import java.util.UUID;
import org.atriasoft.archidata.annotation.apiGenerator.ApiAsyncType;
import org.atriasoft.archidata.annotation.apiGenerator.ApiTypeScriptProgress;
import org.atriasoft.archidata.api.DataResource;
import org.atriasoft.archidata.dataAccess.DBAccess;
import org.atriasoft.archidata.dataAccess.DataAccess;
import org.atriasoft.archidata.dataAccess.addOnSQL.AddOnDataJson;
import org.atriasoft.archidata.exception.FailException;
import org.atriasoft.archidata.exception.InputException;
import org.atriasoft.archidata.model.Data;
import org.atriasoft.archidata.tools.DataTools;
import org.atriasoft.karideo.model.Media;
import org.atriasoft.karideo.model.Season;
import org.atriasoft.karideo.model.Series;
import org.atriasoft.karideo.model.Type;
import org.bson.types.ObjectId;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.kar.archidata.annotation.AsyncType;
import org.kar.archidata.annotation.TypeScriptProgress;
import org.kar.archidata.api.DataResource;
import org.kar.archidata.dataAccess.DBAccess;
import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.addOnSQL.AddOnDataJson;
import org.kar.archidata.exception.FailException;
import org.kar.archidata.exception.InputException;
import org.kar.archidata.model.Data;
import org.kar.archidata.tools.DataTools;
import org.kar.karideo.model.Media;
import org.kar.karideo.model.Season;
import org.kar.karideo.model.Series;
import org.kar.karideo.model.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.security.RolesAllowed;
import jakarta.validation.Valid;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.PATCH;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
@ -57,14 +58,15 @@ public class MediaResource {
return DataAccess.get(Media.class, id);
}
@PATCH
@PUT
@Path("{id}")
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(description = "Modify a specific Media", tags = "GLOBAL")
public Media patch(@PathParam("id") final Long id, @AsyncType(Media.class) final String jsonRequest) throws Exception {
LOGGER.info("update video {} ==> '{}'", id, jsonRequest);
DataAccess.updateWithJson(Media.class, id, jsonRequest);
public Media patch(@PathParam("id") final Long id, @Valid final Media media) throws Exception {
LOGGER.info("update video {} ==> '{}'", id, media);
media.id = id;
DataAccess.update(media, id);
return DataAccess.get(Media.class, id);
}
@ -88,7 +90,7 @@ public class MediaResource {
@RolesAllowed("ADMIN")
@Consumes({ MediaType.MULTIPART_FORM_DATA })
@Operation(description = "Create a new Media", tags = "GLOBAL")
@TypeScriptProgress
@ApiTypeScriptProgress
public Media uploadMedia( //
// @AsyncType(Long.class) @FormDataParam("universeId") String universeId, //
// @AsyncType(Long.class) @FormDataParam("typeId") String typeId, //
@ -174,7 +176,7 @@ public class MediaResource {
DataResource.removeTemporaryFile(tmpUID);
throw new InputException("seriesId", "seriesId does not exist ...");
}
if (seriesNode.parentId != typeNode.id) {
if (seriesNode.parentId.equals(typeNode.id)) {
DataResource.removeTemporaryFile(tmpUID);
throw new InputException("seriesId", "seriesId object have not the correct parent...");
}
@ -233,9 +235,9 @@ public class MediaResource {
@Path("{id}/cover")
@RolesAllowed("ADMIN")
@Consumes({ MediaType.MULTIPART_FORM_DATA })
@AsyncType(Media.class)
@ApiAsyncType(Media.class)
@Operation(description = "Upload a new season cover media", tags = "GLOBAL")
@TypeScriptProgress
@ApiTypeScriptProgress
public Media uploadCover( //
@PathParam("id") final Long id, //
@FormDataParam("file") final InputStream fileInputStream, //
@ -253,7 +255,7 @@ public class MediaResource {
@Operation(description = "Remove a specific cover of a media", tags = "GLOBAL")
public Media removeCover( //
@PathParam("id") final Long id, //
@PathParam("coverId") final UUID coverId //
@PathParam("coverId") final ObjectId coverId //
) throws Exception {
try (DBAccess dbIo = DBAccess.createInterface()) {
AddOnDataJson.removeLink(dbIo, Media.class, "id", id, "covers", coverId);

View File

@ -1,31 +1,31 @@
package org.kar.karideo.api;
package org.atriasoft.karideo.api;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;
import org.atriasoft.archidata.annotation.apiGenerator.ApiTypeScriptProgress;
import org.atriasoft.archidata.dataAccess.DBAccess;
import org.atriasoft.archidata.dataAccess.DataAccess;
import org.atriasoft.archidata.dataAccess.QueryAnd;
import org.atriasoft.archidata.dataAccess.QueryCondition;
import org.atriasoft.archidata.dataAccess.addOnSQL.AddOnDataJson;
import org.atriasoft.archidata.dataAccess.options.Condition;
import org.atriasoft.archidata.tools.DataTools;
import org.atriasoft.karideo.model.Season;
import org.bson.types.ObjectId;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.kar.archidata.annotation.AsyncType;
import org.kar.archidata.annotation.TypeScriptProgress;
import org.kar.archidata.dataAccess.DBAccess;
import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.QueryAnd;
import org.kar.archidata.dataAccess.QueryCondition;
import org.kar.archidata.dataAccess.addOnSQL.AddOnDataJson;
import org.kar.archidata.dataAccess.options.Condition;
import org.kar.archidata.tools.DataTools;
import org.kar.karideo.model.Season;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.security.RolesAllowed;
import jakarta.validation.Valid;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.PATCH;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
@ -58,17 +58,18 @@ public class SeasonResource {
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(description = "Create a new season", tags = "GLOBAL")
public Season post(final Season jsonRequest) throws Exception {
public Season post(@Valid final Season jsonRequest) throws Exception {
return DataAccess.insert(jsonRequest);
}
@PATCH
@PUT
@Path("{id}")
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(description = "Modify a specific season", tags = "GLOBAL")
public Season patch(@PathParam("id") final Long id, @AsyncType(Season.class) final String jsonRequest) throws Exception {
DataAccess.updateWithJson(Season.class, id, jsonRequest);
public Season patch(@PathParam("id") final Long id, @Valid final Season season) throws Exception {
season.id = id;
DataAccess.update(season, id);
return DataAccess.get(Season.class, id);
}
@ -85,7 +86,7 @@ public class SeasonResource {
@RolesAllowed("ADMIN")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Operation(description = "Upload a new season cover season", tags = "GLOBAL")
@TypeScriptProgress
@ApiTypeScriptProgress
public Season uploadCover(@PathParam("id") final Long id, @FormDataParam("file") final InputStream fileInputStream, @FormDataParam("file") final FormDataContentDisposition fileMetaData)
throws Exception {
try (DBAccess dbIo = DBAccess.createInterface()) {
@ -98,7 +99,7 @@ public class SeasonResource {
@Path("{id}/cover/{coverId}")
@RolesAllowed("ADMIN")
@Operation(description = "Remove a specific cover of a season", tags = "GLOBAL")
public Season removeCover(@PathParam("id") final Long id, @PathParam("coverId") final UUID coverId) throws Exception {
public Season removeCover(@PathParam("id") final Long id, @PathParam("coverId") final ObjectId coverId) throws Exception {
try (DBAccess dbIo = DBAccess.createInterface()) {
AddOnDataJson.removeLink(dbIo, Season.class, "id", id, "covers", coverId);
return dbIo.get(Season.class, id);

View File

@ -1,31 +1,31 @@
package org.kar.karideo.api;
package org.atriasoft.karideo.api;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;
import org.atriasoft.archidata.annotation.apiGenerator.ApiTypeScriptProgress;
import org.atriasoft.archidata.dataAccess.DBAccess;
import org.atriasoft.archidata.dataAccess.DataAccess;
import org.atriasoft.archidata.dataAccess.QueryAnd;
import org.atriasoft.archidata.dataAccess.QueryCondition;
import org.atriasoft.archidata.dataAccess.addOnSQL.AddOnDataJson;
import org.atriasoft.archidata.dataAccess.options.Condition;
import org.atriasoft.archidata.tools.DataTools;
import org.atriasoft.karideo.model.Series;
import org.bson.types.ObjectId;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.kar.archidata.annotation.AsyncType;
import org.kar.archidata.annotation.TypeScriptProgress;
import org.kar.archidata.dataAccess.DBAccess;
import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.QueryAnd;
import org.kar.archidata.dataAccess.QueryCondition;
import org.kar.archidata.dataAccess.addOnSQL.AddOnDataJson;
import org.kar.archidata.dataAccess.options.Condition;
import org.kar.archidata.tools.DataTools;
import org.kar.karideo.model.Series;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.security.RolesAllowed;
import jakarta.validation.Valid;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.PATCH;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
@ -61,17 +61,18 @@ public class SeriesResource {
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(description = "Create a new Series", tags = "GLOBAL")
public Series post(final Series jsonRequest) throws Exception {
public Series post(@Valid final Series jsonRequest) throws Exception {
return DataAccess.insert(jsonRequest);
}
@PATCH
@PUT
@Path("{id}")
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(description = "Modify a specific Series", tags = "GLOBAL")
public Series patch(@PathParam("id") final Long id, @AsyncType(Series.class) final String jsonRequest) throws Exception {
DataAccess.updateWithJson(Series.class, id, jsonRequest);
public Series patch(@PathParam("id") final Long id, @Valid final Series series) throws Exception {
series.id = id;
DataAccess.update(series, id);
return DataAccess.get(Series.class, id);
}
@ -88,7 +89,7 @@ public class SeriesResource {
@RolesAllowed("ADMIN")
@Consumes({ MediaType.MULTIPART_FORM_DATA })
@Operation(description = "Upload a new season cover Series", tags = "GLOBAL")
@TypeScriptProgress
@ApiTypeScriptProgress
public Series uploadCover(@PathParam("id") final Long id, @FormDataParam("file") final InputStream fileInputStream, @FormDataParam("file") final FormDataContentDisposition fileMetaData)
throws Exception {
try (DBAccess dbIo = DBAccess.createInterface()) {
@ -101,7 +102,7 @@ public class SeriesResource {
@Path("{id}/cover/{coverId}")
@RolesAllowed("ADMIN")
@Operation(description = "Remove a specific Series of a season", tags = "GLOBAL")
public Series removeCover(@PathParam("id") final Long id, @PathParam("coverId") final UUID coverId) throws Exception {
public Series removeCover(@PathParam("id") final Long id, @PathParam("coverId") final ObjectId coverId) throws Exception {
try (DBAccess dbIo = DBAccess.createInterface()) {
AddOnDataJson.removeLink(dbIo, Series.class, "id", id, "covers", coverId);
return dbIo.get(Series.class, id);

View File

@ -1,30 +1,30 @@
package org.kar.karideo.api;
package org.atriasoft.karideo.api;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;
import org.atriasoft.archidata.annotation.apiGenerator.ApiTypeScriptProgress;
import org.atriasoft.archidata.dataAccess.DBAccess;
import org.atriasoft.archidata.dataAccess.DataAccess;
import org.atriasoft.archidata.dataAccess.QueryCondition;
import org.atriasoft.archidata.dataAccess.addOnSQL.AddOnDataJson;
import org.atriasoft.archidata.dataAccess.options.Condition;
import org.atriasoft.archidata.tools.DataTools;
import org.atriasoft.karideo.model.Type;
import org.bson.types.ObjectId;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.kar.archidata.annotation.AsyncType;
import org.kar.archidata.annotation.TypeScriptProgress;
import org.kar.archidata.dataAccess.DBAccess;
import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.QueryCondition;
import org.kar.archidata.dataAccess.addOnSQL.AddOnDataJson;
import org.kar.archidata.dataAccess.options.Condition;
import org.kar.archidata.tools.DataTools;
import org.kar.karideo.model.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.security.RolesAllowed;
import jakarta.validation.Valid;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.PATCH;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
@ -61,17 +61,18 @@ public class TypeResource {
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(description = "Create a new Type", tags = "GLOBAL")
public Type post(final Type jsonRequest) throws Exception {
public Type post(@Valid final Type jsonRequest) throws Exception {
return DataAccess.insert(jsonRequest);
}
@PATCH
@PUT
@Path("{id}")
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(description = "Modify a specific Type", tags = "GLOBAL")
public Type patch(@PathParam("id") final Long id, @AsyncType(Type.class) final String jsonRequest) throws Exception {
DataAccess.updateWithJson(Type.class, id, jsonRequest);
public Type patch(@PathParam("id") final Long id, @Valid final Type type) throws Exception {
type.id = id;
DataAccess.update(type, id);
return DataAccess.get(Type.class, id);
}
@ -88,7 +89,7 @@ public class TypeResource {
@RolesAllowed("ADMIN")
@Consumes({ MediaType.MULTIPART_FORM_DATA })
@Operation(description = "Upload a new season cover Type", tags = "GLOBAL")
@TypeScriptProgress
@ApiTypeScriptProgress
public Type uploadCover(@PathParam("id") final Long id, @FormDataParam("file") final InputStream fileInputStream, @FormDataParam("file") final FormDataContentDisposition fileMetaData)
throws Exception {
try (DBAccess dbIo = DBAccess.createInterface()) {
@ -101,7 +102,7 @@ public class TypeResource {
@Path("{id}/cover/{coverId}")
@RolesAllowed("ADMIN")
@Operation(description = "Remove a specific cover of a type", tags = "GLOBAL")
public Type removeCover(@PathParam("id") final Long id, @PathParam("coverId") final UUID coverId) throws Exception {
public Type removeCover(@PathParam("id") final Long id, @PathParam("coverId") final ObjectId coverId) throws Exception {
try (DBAccess dbIo = DBAccess.createInterface()) {
AddOnDataJson.removeLink(dbIo, Type.class, "id", id, "covers", coverId);

View File

@ -1,22 +1,23 @@
package org.kar.karideo.api;
package org.atriasoft.karideo.api;
import java.util.List;
import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.QueryAnd;
import org.kar.archidata.dataAccess.QueryCondition;
import org.kar.archidata.dataAccess.options.Condition;
import org.kar.archidata.filter.GenericContext;
import org.kar.karideo.model.UserMediaAdvancement;
import org.atriasoft.archidata.dataAccess.DataAccess;
import org.atriasoft.archidata.dataAccess.QueryAnd;
import org.atriasoft.archidata.dataAccess.QueryCondition;
import org.atriasoft.archidata.dataAccess.options.Condition;
import org.atriasoft.archidata.filter.GenericContext;
import org.atriasoft.karideo.model.UserMediaAdvancement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.security.RolesAllowed;
import jakarta.validation.Valid;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.PATCH;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
@ -69,12 +70,12 @@ public class UserMediaAdvancementResource {
public record MediaInformationsDelta(int time, float percent, boolean addCount) {
}
@PATCH
@PUT
@Path("{id}")
@RolesAllowed("USER")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(description = "Modify a user advancement", tags = "GLOBAL")
public UserMediaAdvancement patch(@Context final SecurityContext sc, @PathParam("id") final Long id, final MediaInformationsDelta data) throws Exception {
public UserMediaAdvancement patch(@Context final SecurityContext sc, @PathParam("id") final Long id, @Valid final MediaInformationsDelta data) throws Exception {
final UserMediaAdvancement elem = get(sc, id);
if (elem == null) {
// insert element

View File

@ -1,10 +1,10 @@
package org.kar.karideo.api;
package org.atriasoft.karideo.api;
import java.util.List;
import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.filter.GenericContext;
import org.kar.karideo.model.UserKarideo;
import org.atriasoft.archidata.dataAccess.DataAccess;
import org.atriasoft.archidata.filter.GenericContext;
import org.atriasoft.karideo.model.UserKarideo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -1,6 +1,6 @@
package org.kar.karideo.filter;
package org.atriasoft.karideo.filter;
import org.kar.archidata.filter.AuthenticationFilter;
import org.atriasoft.archidata.filter.AuthenticationFilter;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.ext.Provider;

View File

@ -1,4 +1,4 @@
package org.kar.karideo.internal;
package org.atriasoft.karideo.internal;
//import io.scenarium.logger.LogLevel;
//import io.scenarium.logger.Logger;

View File

@ -1,28 +1,29 @@
package org.kar.karideo.migration;
package org.atriasoft.karideo.migration;
import java.util.List;
import org.kar.archidata.api.DataResource;
import org.kar.archidata.dataAccess.DBAccess;
import org.kar.archidata.externalRestApi.AnalyzeApi;
import org.kar.archidata.externalRestApi.TsGenerateApi;
import org.kar.archidata.migration.MigrationSqlStep;
import org.kar.archidata.model.Data;
import org.kar.archidata.model.User;
import org.kar.archidata.model.token.JwtToken;
import org.kar.karideo.api.Front;
import org.kar.karideo.api.HealthCheck;
import org.kar.karideo.api.MediaResource;
import org.kar.karideo.api.SeasonResource;
import org.kar.karideo.api.SeriesResource;
import org.kar.karideo.api.TypeResource;
import org.kar.karideo.api.UserMediaAdvancementResource;
import org.kar.karideo.api.UserResource;
import org.kar.karideo.model.Media;
import org.kar.karideo.model.Season;
import org.kar.karideo.model.Series;
import org.kar.karideo.model.Type;
import org.kar.karideo.model.UserMediaAdvancement;
import org.atriasoft.archidata.api.DataResource;
import org.atriasoft.archidata.dataAccess.DBAccess;
import org.atriasoft.archidata.externalRestApi.AnalyzeApi;
import org.atriasoft.archidata.externalRestApi.TsGenerateApi;
import org.atriasoft.archidata.filter.PartRight;
import org.atriasoft.archidata.migration.MigrationSqlStep;
import org.atriasoft.archidata.model.Data;
import org.atriasoft.archidata.model.User;
import org.atriasoft.archidata.model.token.JwtToken;
import org.atriasoft.karideo.api.Front;
import org.atriasoft.karideo.api.HealthCheck;
import org.atriasoft.karideo.api.MediaResource;
import org.atriasoft.karideo.api.SeasonResource;
import org.atriasoft.karideo.api.SeriesResource;
import org.atriasoft.karideo.api.TypeResource;
import org.atriasoft.karideo.api.UserMediaAdvancementResource;
import org.atriasoft.karideo.api.UserResource;
import org.atriasoft.karideo.model.Media;
import org.atriasoft.karideo.model.Season;
import org.atriasoft.karideo.model.Series;
import org.atriasoft.karideo.model.Type;
import org.atriasoft.karideo.model.UserMediaAdvancement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -45,6 +46,7 @@ public class Initialization extends MigrationSqlStep {
final AnalyzeApi api = new AnalyzeApi();
api.addAllApi(listOfResources);
api.addModel(JwtToken.class);
api.addModel(PartRight.class);
TsGenerateApi.generateApi(api, "../front/src/back-api/");
LOGGER.info("Generate APIs (DONE)");
}

View File

@ -1,7 +1,7 @@
package org.kar.karideo.migration;
package org.atriasoft.karideo.migration;
import org.kar.archidata.migration.MigrationSqlStep;
import org.kar.karideo.model.UserMediaAdvancement;
import org.atriasoft.archidata.migration.MigrationSqlStep;
import org.atriasoft.karideo.model.UserMediaAdvancement;
public class Migration20230810 extends MigrationSqlStep {

View File

@ -1,8 +1,8 @@
package org.kar.karideo.migration;
package org.atriasoft.karideo.migration;
import java.util.List;
import org.kar.archidata.migration.MigrationSqlStep;
import org.atriasoft.archidata.migration.MigrationSqlStep;
public class Migration20231015 extends MigrationSqlStep {

View File

@ -1,6 +1,6 @@
package org.kar.karideo.migration;
package org.atriasoft.karideo.migration;
import org.kar.archidata.migration.MigrationSqlStep;
import org.atriasoft.archidata.migration.MigrationSqlStep;
public class Migration20231126 extends MigrationSqlStep {

View File

@ -1,6 +1,6 @@
package org.kar.karideo.migration;
package org.atriasoft.karideo.migration;
import org.kar.archidata.migration.MigrationSqlStep;
import org.atriasoft.archidata.migration.MigrationSqlStep;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -1,6 +1,6 @@
package org.kar.karideo.migration;
package org.atriasoft.karideo.migration;
import org.kar.archidata.migration.MigrationSqlStep;
import org.atriasoft.archidata.migration.MigrationSqlStep;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -1,4 +1,4 @@
package org.kar.karideo.migration;
package org.atriasoft.karideo.migration;
import java.io.FileWriter;
import java.io.IOException;
@ -11,15 +11,15 @@ import java.util.List;
import java.util.UUID;
import org.bson.types.ObjectId;
import org.kar.archidata.api.DataResource;
import org.kar.archidata.dataAccess.DBAccess;
import org.kar.archidata.dataAccess.options.AccessDeletedItems;
import org.kar.archidata.dataAccess.options.OverrideTableName;
import org.kar.archidata.migration.MigrationSqlStep;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.kar.karideo.migration.model.CoverConversion;
import org.kar.karideo.migration.model.MediaConversion;
import org.kar.karideo.migration.model.OIDConversion;
import org.atriasoft.archidata.api.DataResource;
import org.atriasoft.archidata.dataAccess.DBAccess;
import org.atriasoft.archidata.dataAccess.options.AccessDeletedItems;
import org.atriasoft.archidata.dataAccess.options.OverrideTableName;
import org.atriasoft.archidata.migration.MigrationSqlStep;
import org.atriasoft.archidata.tools.ConfigBaseVariable;
import org.atriasoft.karideo.migration.model.CoverConversion;
import org.atriasoft.karideo.migration.model.MediaConversion;
import org.atriasoft.karideo.migration.model.OIDConversion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -0,0 +1,24 @@
package org.atriasoft.karideo.migration;
import org.atriasoft.archidata.migration.MigrationSqlStep;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Migration20250414 extends MigrationSqlStep {
private static final Logger LOGGER = LoggerFactory.getLogger(Migration20240226.class);
public static final int KARSO_INITIALISATION_ID = 1;
@Override
public String getName() {
return "migration-2025-04-14: update to archidata 0.28.0";
}
@Override
public void generateStep() throws Exception {
addAction("""
ALTER TABLE `media` ADD `datePublication` timestamp(3) NULL;
""");
}
}

View File

@ -1,10 +1,10 @@
package org.kar.karideo.migration.model;
package org.atriasoft.karideo.migration.model;
import java.util.List;
import java.util.UUID;
import org.bson.types.ObjectId;
import org.kar.archidata.annotation.DataJson;
import org.atriasoft.archidata.annotation.DataJson;
import jakarta.persistence.Id;

View File

@ -1,4 +1,4 @@
package org.kar.karideo.migration.model;
package org.atriasoft.karideo.migration.model;
import java.util.UUID;

View File

@ -1,4 +1,4 @@
package org.kar.karideo.migration.model;
package org.atriasoft.karideo.migration.model;
import java.util.UUID;

View File

@ -1,12 +1,16 @@
package org.kar.karideo.model;
package org.atriasoft.karideo.model;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import org.atriasoft.archidata.annotation.DataJson;
import org.atriasoft.archidata.annotation.apiGenerator.ApiGenerationMode;
import org.atriasoft.archidata.annotation.checker.CheckForeignKey;
import org.atriasoft.archidata.annotation.checker.CollectionNotEmpty;
import org.atriasoft.archidata.model.Data;
import org.atriasoft.archidata.model.GenericDataSoftDelete;
import org.bson.types.ObjectId;
import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.model.Data;
import org.kar.archidata.model.GenericDataSoftDelete;
import org.hibernate.validator.constraints.UniqueElements;
import com.fasterxml.jackson.annotation.JsonInclude;
@ -17,51 +21,58 @@ import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.PositiveOrZero;
import jakarta.validation.constraints.Size;
@Entity
@Table(name = "media")
@JsonInclude(JsonInclude.Include.NON_NULL)
@ApiGenerationMode(create = true, update = true)
public class Media extends GenericDataSoftDelete {
@Schema(description = "Name of the media (this represent the title)")
@Column(nullable = false, length = 0)
@Size(min = 0, max = 256)
public String name;
@Schema(description = "Description of the media")
@Column(length = 0)
@Size(min = 0, max = 8192)
public String description;
@Schema(description = "Foreign Key Id of the data")
@ManyToOne(fetch = FetchType.LAZY, targetEntity = Data.class)
@Column(nullable = false)
@CheckForeignKey(target = Data.class)
public ObjectId dataId;
@ManyToOne(fetch = FetchType.LAZY, targetEntity = Data.class)
@Column(nullable = false)
@Nullable
public UUID dataIdOld;
@Schema(description = "Type of the media")
@ManyToOne(fetch = FetchType.LAZY, targetEntity = Type.class)
@CheckForeignKey(target = Type.class)
public Long typeId;
@Schema(description = "Series reference of the media")
@ManyToOne(fetch = FetchType.LAZY, targetEntity = Series.class)
@CheckForeignKey(target = Series.class)
public Long seriesId;
@Schema(description = "Season reference of the media")
@ManyToOne(fetch = FetchType.LAZY, targetEntity = Season.class)
@CheckForeignKey(target = Season.class)
public Long seasonId;
@Schema(description = "Episode Id")
@PositiveOrZero
public Integer episode;
// ")
public Integer date;
@Schema(description = "Creation years of the media")
public Integer time;
public Date datePublication;
@Schema(description = "Limitation Age of the media")
@PositiveOrZero
public Integer ageLimit;
@Schema(description = "List of Id of the specific covers")
@DataJson(targetEntity = Data.class)
@DataJson()
@Nullable
public List<ObjectId> covers = null;
@CollectionNotEmpty
@UniqueElements
public List<@CheckForeignKey(target = Data.class) ObjectId> covers = null;
@Override
public String toString() {
return "Media [name=" + this.name + ", description=" + this.description + ", dataId=" + this.dataId + ", typeId=" + this.typeId + ", seriesId=" + this.seriesId + ", seasonId=" + this.seasonId
+ ", episode=" + this.episode + ", date=" + this.date + ", time=" + this.time + ", ageLimit=" + this.ageLimit + ", covers=" + this.covers + "]";
+ ", episode=" + this.episode + ", date=" + this.datePublication + ", ageLimit=" + this.ageLimit + ", covers=" + this.covers + "]";
}
}

View File

@ -1,11 +1,16 @@
package org.kar.karideo.model;
package org.atriasoft.karideo.model;
import java.util.List;
import org.atriasoft.archidata.annotation.DataIfNotExists;
import org.atriasoft.archidata.annotation.DataJson;
import org.atriasoft.archidata.annotation.apiGenerator.ApiGenerationMode;
import org.atriasoft.archidata.annotation.checker.CheckForeignKey;
import org.atriasoft.archidata.annotation.checker.CollectionNotEmpty;
import org.atriasoft.archidata.model.Data;
import org.atriasoft.archidata.model.GenericDataSoftDelete;
import org.bson.types.ObjectId;
import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.model.GenericDataSoftDelete;
import org.hibernate.validator.constraints.UniqueElements;
import com.fasterxml.jackson.annotation.JsonInclude;
@ -15,23 +20,31 @@ import jakarta.persistence.Column;
import jakarta.persistence.FetchType;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
@Table(name = "season")
@DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL)
@ApiGenerationMode(create = true, update = true)
public class Season extends GenericDataSoftDelete {
@Column(nullable = false, length = 0)
@Schema(description = "Name of the media (this represent the title)")
@Size(min = 0, max = 256)
public String name;
@Column(length = 0)
@Schema(description = "Description of the media")
@Size(min = 0, max = 8192)
public String description;
@Column(nullable = false)
@Schema(description = "series parent ID")
@ManyToOne(fetch = FetchType.LAZY, targetEntity = Series.class)
@CheckForeignKey(target = Series.class)
public Long parentId;
@Schema(description = "List of Id of the specific covers")
@DataJson()
@Nullable
public List<ObjectId> covers = null;
@CollectionNotEmpty
@UniqueElements
public List<@CheckForeignKey(target = Data.class) @NotNull ObjectId> covers = null;
}

View File

@ -1,11 +1,16 @@
package org.kar.karideo.model;
package org.atriasoft.karideo.model;
import java.util.List;
import org.atriasoft.archidata.annotation.DataIfNotExists;
import org.atriasoft.archidata.annotation.DataJson;
import org.atriasoft.archidata.annotation.apiGenerator.ApiGenerationMode;
import org.atriasoft.archidata.annotation.checker.CheckForeignKey;
import org.atriasoft.archidata.annotation.checker.CollectionNotEmpty;
import org.atriasoft.archidata.model.Data;
import org.atriasoft.archidata.model.GenericDataSoftDelete;
import org.bson.types.ObjectId;
import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.model.GenericDataSoftDelete;
import org.hibernate.validator.constraints.UniqueElements;
import com.fasterxml.jackson.annotation.JsonInclude;
@ -15,23 +20,31 @@ import jakarta.persistence.Column;
import jakarta.persistence.FetchType;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
@Table(name = "series")
@DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL)
@ApiGenerationMode(create = true, update = true)
public class Series extends GenericDataSoftDelete {
@Column(nullable = false, length = 0)
@Size(min = 0, max = 256)
@Schema(description = "Name of the media (this represent the title)")
public String name;
@Column(length = 0)
@Schema(description = "Description of the media")
@Size(min = 0, max = 8192)
public String description;
@Column(nullable = false)
@Schema(description = "series parent ID")
@ManyToOne(fetch = FetchType.LAZY, targetEntity = Type.class)
@CheckForeignKey(target = Type.class)
public Long parentId;
@Schema(description = "List of Id of the specific covers")
@DataJson()
@Nullable
public List<ObjectId> covers = null;
@CollectionNotEmpty
@UniqueElements
public List<@CheckForeignKey(target = Data.class) @NotNull ObjectId> covers = null;
}

View File

@ -1,11 +1,16 @@
package org.kar.karideo.model;
package org.atriasoft.karideo.model;
import java.util.List;
import org.atriasoft.archidata.annotation.DataIfNotExists;
import org.atriasoft.archidata.annotation.DataJson;
import org.atriasoft.archidata.annotation.apiGenerator.ApiGenerationMode;
import org.atriasoft.archidata.annotation.checker.CheckForeignKey;
import org.atriasoft.archidata.annotation.checker.CollectionNotEmpty;
import org.atriasoft.archidata.model.Data;
import org.atriasoft.archidata.model.GenericDataSoftDelete;
import org.bson.types.ObjectId;
import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.model.GenericDataSoftDelete;
import org.hibernate.validator.constraints.UniqueElements;
import com.fasterxml.jackson.annotation.JsonInclude;
@ -13,21 +18,28 @@ import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
@Table(name = "type")
@DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL)
@ApiGenerationMode(create = true, update = true)
public class Type extends GenericDataSoftDelete {
@Column(nullable = false, length = 0)
@Size(min = 0, max = 256)
@Schema(description = "Name of the media (this represent the title)")
public String name;
@Column(length = 0)
@Schema(description = "Description of the media")
@Size(min = 0, max = 8192)
public String description;
@Schema(description = "List of Id of the specific covers")
@DataJson()
@Nullable
public List<ObjectId> covers = null;
@CollectionNotEmpty
@UniqueElements
public List<@CheckForeignKey(target = Data.class) @NotNull ObjectId> covers = null;
public Type() {}

View File

@ -1,7 +1,7 @@
package org.kar.karideo.model;
package org.atriasoft.karideo.model;
import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.model.User;
import org.atriasoft.archidata.annotation.DataIfNotExists;
import org.atriasoft.archidata.model.User;
import com.fasterxml.jackson.annotation.JsonInclude;

View File

@ -1,8 +1,11 @@
package org.kar.karideo.model;
package org.atriasoft.karideo.model;
import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.annotation.DataNotRead;
import org.kar.archidata.model.GenericDataSoftDelete;
import org.atriasoft.archidata.annotation.DataIfNotExists;
import org.atriasoft.archidata.annotation.DataNotRead;
import org.atriasoft.archidata.annotation.apiGenerator.ApiAccessLimitation;
import org.atriasoft.archidata.annotation.apiGenerator.ApiGenerationMode;
import org.atriasoft.archidata.annotation.checker.CheckForeignKey;
import org.atriasoft.archidata.model.GenericDataSoftDelete;
import com.fasterxml.jackson.annotation.JsonInclude;
@ -12,28 +15,35 @@ import jakarta.persistence.Column;
import jakarta.persistence.FetchType;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.PositiveOrZero;
@Table(name = "userMediaAdvancement")
@DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL)
@ApiGenerationMode(create = true, update = true)
public class UserMediaAdvancement extends GenericDataSoftDelete {
@DataNotRead
@Column(nullable = false)
@Schema(description = "Foreign Key Id of the user")
@ManyToOne(fetch = FetchType.LAZY, targetEntity = UserKarideo.class)
@Nullable
@ApiAccessLimitation(updatable = false, creatable = false)
public Long userId;
@Column(nullable = false)
@Schema(description = "Id of the media")
@ManyToOne(fetch = FetchType.LAZY, targetEntity = Media.class)
@CheckForeignKey(target = Media.class)
public Long mediaId;
@Column(nullable = false)
@Schema(description = "Percent of advancement in the media")
@PositiveOrZero
public Float percent;
@Column(nullable = false)
@Schema(description = "Number of second of advancement in the media")
@PositiveOrZero
public Integer time;
@Column(nullable = false)
@Schema(description = "Number of time this media has been read")
@PositiveOrZero
public Integer count;
}

View File

@ -1,4 +1,4 @@
package org.kar.karideo.util;
package org.atriasoft.karideo.util;
public class ConfigVariable {
public static final String BASE_NAME = "ORG_KARIDEO_";

View File

@ -1,20 +0,0 @@
package org.kar.karideo;
public class CacheFilter {
@Override
public List<ResourceFilter> create(AbstractMethod am) {
if (am.isAnnotationPresent(CacheMaxAge.class)) {
CacheMaxAge maxAge = am.getAnnotation(CacheMaxAge.class);
return newCacheFilter("max-age: " + maxAge.unit().toSeconds(maxAge.time()));
} else if (am.isAnnotationPresent(NoCache.class)) {
return newCacheFilter("no-cache");
} else {
return Collections.emptyList();
}
}
private List<ResourceFilter> newCacheFilter(String content) {
return Collections
.<ResourceFilter> singletonList(new CacheResponseFilter(content));
}
}

View File

@ -1,10 +1,11 @@
package test.kar.karideo;
package test.atriasoft.karideo;
import java.util.Map;
import org.kar.archidata.tools.JWTWrapper;
import org.atriasoft.archidata.filter.PartRight;
import org.atriasoft.archidata.tools.JWTWrapper;
public class Common {
static String USER_TOKEN = JWTWrapper.createJwtTestToken(16512, "test_user_login", "KarAuth", "karideo", Map.of("karideo", Map.of("USER", Boolean.TRUE)));
static String ADMIN_TOKEN = JWTWrapper.createJwtTestToken(16512, "test_admin_login", "KarAuth", "karideo", Map.of("karideo", Map.of("USER", Boolean.TRUE, "ADMIN", Boolean.TRUE)));
static String USER_TOKEN = JWTWrapper.createJwtTestToken(16512, "test_user_login", "KarAuth", "karideo", Map.of("karideo", Map.of("USER", PartRight.READ)));
static String ADMIN_TOKEN = JWTWrapper.createJwtTestToken(16512, "test_admin_login", "KarAuth", "karideo", Map.of("karideo", Map.of("USER", PartRight.READ_WRITE, "ADMIN", PartRight.READ_WRITE)));
}

View File

@ -1,18 +1,18 @@
package test.kar.karideo;
package test.atriasoft.karideo;
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.karideo.model.Media;
import org.kar.karideo.model.Season;
import org.kar.karideo.model.Series;
import org.kar.karideo.model.Type;
import org.kar.karideo.model.UserKarideo;
import org.atriasoft.archidata.dataAccess.DBAccess;
import org.atriasoft.archidata.db.DbConfig;
import org.atriasoft.archidata.db.DbIoFactory;
import org.atriasoft.archidata.exception.DataAccessException;
import org.atriasoft.archidata.tools.ConfigBaseVariable;
import org.atriasoft.karideo.model.Media;
import org.atriasoft.karideo.model.Season;
import org.atriasoft.karideo.model.Series;
import org.atriasoft.karideo.model.Type;
import org.atriasoft.karideo.model.UserKarideo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -1,4 +1,4 @@
package test.kar.karideo;
package test.atriasoft.karideo;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;

View File

@ -1,4 +1,4 @@
package test.kar.karideo;
package test.atriasoft.karideo;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
@ -6,8 +6,8 @@ import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.kar.archidata.tools.RESTApi;
import org.atriasoft.archidata.tools.ConfigBaseVariable;
import org.atriasoft.archidata.tools.RESTApi;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -1,6 +1,4 @@
package test.kar.karideo;
import java.io.IOException;
package test.atriasoft.karideo;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
@ -10,10 +8,10 @@ import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith;
import org.kar.archidata.exception.RESTErrorResponseException;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.kar.archidata.tools.RESTApi;
import org.kar.karideo.api.HealthCheck.HealthResult;
import org.atriasoft.archidata.exception.RESTErrorResponseException;
import org.atriasoft.archidata.tools.ConfigBaseVariable;
import org.atriasoft.archidata.tools.RESTApi;
import org.atriasoft.karideo.api.HealthCheck.HealthResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -27,29 +25,22 @@ public class TestHealthCheck {
@BeforeAll
public static void configureWebServer() throws Exception {
ConfigureDb.configure();
LOGGER.info("configure server ...");
webInterface = new WebLauncherTest();
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)");
webInterface.process();
LOGGER.info("Start REST (DONE)");
api = new RESTApi(ConfigBaseVariable.apiAdress);
api.setToken(Common.ADMIN_TOKEN);
}
@AfterAll
public static void stopWebServer() throws InterruptedException, IOException {
public static void stopWebServer() throws Exception {
LOGGER.info("Kill the web server");
webInterface.stop();
webInterface = null;
LOGGER.info("Remove the test db");
// DBEntry.closeAllForceMode();
ConfigBaseVariable.clearAllValue();
ConfigureDb.clear();
}
@Order(1)

View File

@ -1,7 +1,7 @@
package test.kar.karideo;
package test.atriasoft.karideo;
import org.kar.karideo.WebLauncher;
import org.atriasoft.karideo.WebLauncher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -2,6 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="manifest" href="/manifest.json" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Karideo</title>
<link rel="icon" href="/favicon.ico" />

View File

@ -29,64 +29,66 @@
"*.{ts,tsx,js,jsx,json}": "prettier --write"
},
"dependencies": {
"react-speech-recognition": "4.0.0",
"regenerator-runtime": "0.14.1",
"@locator/babel-jsx": "0.4.4",
"@trivago/prettier-plugin-sort-imports": "5.2.2",
"@chakra-ui/cli": "3.8.1",
"@chakra-ui/react": "3.8.1",
"@chakra-ui/cli": "3.16.0",
"@chakra-ui/react": "3.16.0",
"@emotion/react": "11.14.0",
"allotment": "1.20.3",
"css-mediaquery": "0.1.2",
"dayjs": "1.11.13",
"history": "5.3.0",
"next-themes": "^0.4.4",
"react": "19.0.0",
"react-dom": "19.0.0",
"next-themes": "^0.4.6",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-error-boundary": "5.0.0",
"react-icons": "5.5.0",
"react-router-dom": "7.2.0",
"react-select": "5.10.0",
"react-router-dom": "7.5.0",
"react-select": "5.10.1",
"react-use": "17.6.0",
"zod": "3.24.2",
"zustand": "5.0.3"
},
"devDependencies": {
"@chakra-ui/styled-system": "^2.12.0",
"@playwright/test": "1.50.1",
"@storybook/addon-actions": "8.5.8",
"@storybook/addon-essentials": "8.5.8",
"@storybook/addon-links": "8.5.8",
"@storybook/addon-mdx-gfm": "8.5.8",
"@storybook/react": "8.5.8",
"@storybook/react-vite": "8.5.8",
"@storybook/theming": "8.5.8",
"@playwright/test": "1.51.1",
"@storybook/addon-actions": "8.6.12",
"@storybook/addon-essentials": "8.6.12",
"@storybook/addon-links": "8.6.12",
"@storybook/addon-mdx-gfm": "8.6.12",
"@storybook/react": "8.6.12",
"@storybook/react-vite": "8.6.12",
"@storybook/theming": "8.6.12",
"@testing-library/jest-dom": "6.6.3",
"@testing-library/react": "16.2.0",
"@testing-library/react": "16.3.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.5",
"@types/react": "19.0.10",
"@types/react-dom": "19.0.4",
"@typescript-eslint/eslint-plugin": "8.24.1",
"@typescript-eslint/parser": "8.24.1",
"@types/node": "22.14.1",
"@types/react": "19.1.2",
"@types/react-dom": "19.1.2",
"@typescript-eslint/eslint-plugin": "8.30.1",
"@typescript-eslint/parser": "8.30.1",
"@vitejs/plugin-react": "4.3.4",
"eslint": "9.21.0",
"eslint": "9.24.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.3",
"eslint-plugin-react": "7.37.5",
"eslint-plugin-react-hooks": "5.2.0",
"eslint-plugin-storybook": "0.12.0",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"knip": "5.44.5",
"lint-staged": "15.4.3",
"npm-check-updates": "^17.1.15",
"prettier": "3.5.2",
"puppeteer": "24.2.1",
"react-is": "19.0.0",
"storybook": "8.5.8",
"knip": "5.50.3",
"lint-staged": "15.5.1",
"npm-check-updates": "^17.1.18",
"prettier": "3.5.3",
"puppeteer": "24.6.1",
"react-is": "19.1.0",
"storybook": "8.6.12",
"ts-node": "10.9.2",
"typescript": "5.7.3",
"vite": "6.1.1",
"vitest": "3.0.6"
"typescript": "5.8.3",
"vite": "6.2.6",
"vitest": "3.1.1"
}
}

3104
front/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -0,0 +1,21 @@
{
"name": "Karusic",
"short_name": "Karusic",
"description": "(K)angaroo (A)nd (R)abbit m(usic) is a music streaming",
"start_url": "/karusic/",
"display": "standalone",
"background_color": "#000000",
"theme_color": "#FFFFFF",
"icons": [
{
"src": "/karusic/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/karusic/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}

View File

@ -22,7 +22,7 @@ export namespace DataResource {
restConfig,
queries,
params,
data,
headers,
}: {
restConfig: RESTConfig,
queries: {
@ -32,7 +32,9 @@ export namespace DataResource {
name: string,
oid: ObjectId,
},
data: string,
headers?: {
Range?: string,
},
}): Promise<object> {
return RESTRequestJson({
restModel: {
@ -42,7 +44,7 @@ export namespace DataResource {
restConfig,
params,
queries,
data,
headers,
});
};
/**
@ -52,7 +54,7 @@ export namespace DataResource {
restConfig,
queries,
params,
data,
headers,
}: {
restConfig: RESTConfig,
queries: {
@ -61,7 +63,9 @@ export namespace DataResource {
params: {
oid: ObjectId,
},
data: string,
headers?: {
Range: string,
},
}): Promise<object> {
return RESTRequestJson({
restModel: {
@ -71,7 +75,7 @@ export namespace DataResource {
restConfig,
params,
queries,
data,
headers,
});
};
/**
@ -81,7 +85,7 @@ export namespace DataResource {
restConfig,
queries,
params,
data,
headers,
}: {
restConfig: RESTConfig,
queries: {
@ -90,7 +94,9 @@ export namespace DataResource {
params: {
oid: ObjectId,
},
data: string,
headers?: {
Range: string,
},
}): Promise<object> {
return RESTRequestJson({
restModel: {
@ -100,7 +106,7 @@ export namespace DataResource {
restConfig,
params,
queries,
data,
headers,
});
};
/**

View File

@ -14,7 +14,7 @@ import { z as zod } from "zod"
import {
Long,
Media,
MediaWrite,
MediaUpdate,
UUID,
ZodMedia,
isMedia,
@ -87,12 +87,12 @@ export namespace MediaResource {
params: {
id: Long,
},
data: MediaWrite,
data: MediaUpdate,
}): Promise<Media> {
return RESTRequestJson({
restModel: {
endPoint: "/media/{id}",
requestType: HTTPRequestModel.PATCH,
requestType: HTTPRequestModel.PUT,
contentType: HTTPMimeType.JSON,
accept: HTTPMimeType.JSON,
},

View File

@ -14,7 +14,8 @@ import { z as zod } from "zod"
import {
Long,
Season,
SeasonWrite,
SeasonCreate,
SeasonUpdate,
UUID,
ZodSeason,
isSeason,
@ -88,12 +89,12 @@ export namespace SeasonResource {
params: {
id: Long,
},
data: SeasonWrite,
data: SeasonUpdate,
}): Promise<Season> {
return RESTRequestJson({
restModel: {
endPoint: "/season/{id}",
requestType: HTTPRequestModel.PATCH,
requestType: HTTPRequestModel.PUT,
contentType: HTTPMimeType.JSON,
accept: HTTPMimeType.JSON,
},
@ -110,7 +111,7 @@ export namespace SeasonResource {
data,
}: {
restConfig: RESTConfig,
data: SeasonWrite,
data: SeasonCreate,
}): Promise<Season> {
return RESTRequestJson({
restModel: {

View File

@ -14,7 +14,8 @@ import { z as zod } from "zod"
import {
Long,
Series,
SeriesWrite,
SeriesCreate,
SeriesUpdate,
UUID,
ZodSeries,
isSeries,
@ -88,12 +89,12 @@ export namespace SeriesResource {
params: {
id: Long,
},
data: SeriesWrite,
data: SeriesUpdate,
}): Promise<Series> {
return RESTRequestJson({
restModel: {
endPoint: "/series/{id}",
requestType: HTTPRequestModel.PATCH,
requestType: HTTPRequestModel.PUT,
contentType: HTTPMimeType.JSON,
accept: HTTPMimeType.JSON,
},
@ -110,7 +111,7 @@ export namespace SeriesResource {
data,
}: {
restConfig: RESTConfig,
data: SeriesWrite,
data: SeriesCreate,
}): Promise<Series> {
return RESTRequestJson({
restModel: {

View File

@ -14,7 +14,8 @@ import { z as zod } from "zod"
import {
Long,
Type,
TypeWrite,
TypeCreate,
TypeUpdate,
UUID,
ZodType,
isType,
@ -88,12 +89,12 @@ export namespace TypeResource {
params: {
id: Long,
},
data: TypeWrite,
data: TypeUpdate,
}): Promise<Type> {
return RESTRequestJson({
restModel: {
endPoint: "/type/{id}",
requestType: HTTPRequestModel.PATCH,
requestType: HTTPRequestModel.PUT,
contentType: HTTPMimeType.JSON,
accept: HTTPMimeType.JSON,
},
@ -110,7 +111,7 @@ export namespace TypeResource {
data,
}: {
restConfig: RESTConfig,
data: TypeWrite,
data: TypeCreate,
}): Promise<Type> {
return RESTRequestJson({
restModel: {

View File

@ -12,7 +12,7 @@ import {
import { z as zod } from "zod"
import {
Long,
MediaInformationsDeltaWrite,
MediaInformationsDelta,
UserMediaAdvancement,
ZodUserMediaAdvancement,
isUserMediaAdvancement,
@ -85,12 +85,12 @@ export namespace UserMediaAdvancementResource {
params: {
id: Long,
},
data: MediaInformationsDeltaWrite,
data: MediaInformationsDelta,
}): Promise<UserMediaAdvancement> {
return RESTRequestJson({
restModel: {
endPoint: "/advancement/{id}",
requestType: HTTPRequestModel.PATCH,
requestType: HTTPRequestModel.PUT,
contentType: HTTPMimeType.JSON,
accept: HTTPMimeType.JSON,
},

View File

@ -3,23 +3,23 @@
*/
import { z as zod } from "zod";
import {ZodLong} from "./long";
import {ZodObjectId} from "./object-id";
import {ZodOIDGenericDataSoftDelete, ZodOIDGenericDataSoftDeleteWrite } from "./oid-generic-data-soft-delete";
import {ZodLong} from "./long";
import {ZodOIDGenericDataSoftDelete, ZodOIDGenericDataSoftDeleteUpdate , ZodOIDGenericDataSoftDeleteCreate } from "./oid-generic-data-soft-delete";
export const ZodData = ZodOIDGenericDataSoftDelete.extend({
/**
* Sha512 of the data
*/
sha512: zod.string().max(512),
sha512: zod.string().max(512).readonly(),
/**
* Mime -type of the media
*/
mimeType: zod.string().max(512),
mimeType: zod.string().max(512).readonly(),
/**
* Size in Byte of the data
*/
size: ZodLong,
size: ZodLong.readonly(),
/**
* Unique ObjectID of the object
*/
@ -38,30 +38,3 @@ export function isData(data: any): data is Data {
return false;
}
}
export const ZodDataWrite = ZodOIDGenericDataSoftDeleteWrite.extend({
/**
* Sha512 of the data
*/
sha512: zod.string().max(512).optional(),
/**
* Mime -type of the media
*/
mimeType: zod.string().max(512).optional(),
/**
* Size in Byte of the data
*/
size: ZodLong.optional(),
});
export type DataWrite = zod.infer<typeof ZodDataWrite>;
export function isDataWrite(data: any): data is DataWrite {
try {
ZodDataWrite.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodDataWrite' error=${e}`);
return false;
}
}

View File

@ -3,7 +3,7 @@
*/
import { z as zod } from "zod";
import {ZodGenericData, ZodGenericDataWrite } from "./generic-data";
import {ZodGenericData, ZodGenericDataUpdate , ZodGenericDataCreate } from "./generic-data";
export const ZodGenericDataSoftDelete = ZodGenericData.extend({
/**
@ -24,16 +24,29 @@ export function isGenericDataSoftDelete(data: any): data is GenericDataSoftDelet
return false;
}
}
export const ZodGenericDataSoftDeleteWrite = ZodGenericDataWrite;
export const ZodGenericDataSoftDeleteUpdate = ZodGenericDataUpdate;
export type GenericDataSoftDeleteWrite = zod.infer<typeof ZodGenericDataSoftDeleteWrite>;
export type GenericDataSoftDeleteUpdate = zod.infer<typeof ZodGenericDataSoftDeleteUpdate>;
export function isGenericDataSoftDeleteWrite(data: any): data is GenericDataSoftDeleteWrite {
export function isGenericDataSoftDeleteUpdate(data: any): data is GenericDataSoftDeleteUpdate {
try {
ZodGenericDataSoftDeleteWrite.parse(data);
ZodGenericDataSoftDeleteUpdate.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodGenericDataSoftDeleteWrite' error=${e}`);
console.log(`Fail to parse data type='ZodGenericDataSoftDeleteUpdate' error=${e}`);
return false;
}
}
export const ZodGenericDataSoftDeleteCreate = ZodGenericDataCreate;
export type GenericDataSoftDeleteCreate = zod.infer<typeof ZodGenericDataSoftDeleteCreate>;
export function isGenericDataSoftDeleteCreate(data: any): data is GenericDataSoftDeleteCreate {
try {
ZodGenericDataSoftDeleteCreate.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodGenericDataSoftDeleteCreate' error=${e}`);
return false;
}
}

View File

@ -3,8 +3,8 @@
*/
import { z as zod } from "zod";
import {ZodGenericTiming, ZodGenericTimingUpdate , ZodGenericTimingCreate } from "./generic-timing";
import {ZodLong} from "./long";
import {ZodGenericTiming, ZodGenericTimingWrite } from "./generic-timing";
export const ZodGenericData = ZodGenericTiming.extend({
/**
@ -25,16 +25,29 @@ export function isGenericData(data: any): data is GenericData {
return false;
}
}
export const ZodGenericDataWrite = ZodGenericTimingWrite;
export const ZodGenericDataUpdate = ZodGenericTimingUpdate;
export type GenericDataWrite = zod.infer<typeof ZodGenericDataWrite>;
export type GenericDataUpdate = zod.infer<typeof ZodGenericDataUpdate>;
export function isGenericDataWrite(data: any): data is GenericDataWrite {
export function isGenericDataUpdate(data: any): data is GenericDataUpdate {
try {
ZodGenericDataWrite.parse(data);
ZodGenericDataUpdate.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodGenericDataWrite' error=${e}`);
console.log(`Fail to parse data type='ZodGenericDataUpdate' error=${e}`);
return false;
}
}
export const ZodGenericDataCreate = ZodGenericTimingCreate;
export type GenericDataCreate = zod.infer<typeof ZodGenericDataCreate>;
export function isGenericDataCreate(data: any): data is GenericDataCreate {
try {
ZodGenericDataCreate.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodGenericDataCreate' error=${e}`);
return false;
}
}

View File

@ -28,18 +28,33 @@ export function isGenericTiming(data: any): data is GenericTiming {
return false;
}
}
export const ZodGenericTimingWrite = zod.object({
export const ZodGenericTimingUpdate = zod.object({
});
export type GenericTimingWrite = zod.infer<typeof ZodGenericTimingWrite>;
export type GenericTimingUpdate = zod.infer<typeof ZodGenericTimingUpdate>;
export function isGenericTimingWrite(data: any): data is GenericTimingWrite {
export function isGenericTimingUpdate(data: any): data is GenericTimingUpdate {
try {
ZodGenericTimingWrite.parse(data);
ZodGenericTimingUpdate.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodGenericTimingWrite' error=${e}`);
console.log(`Fail to parse data type='ZodGenericTimingUpdate' error=${e}`);
return false;
}
}
export const ZodGenericTimingCreate = zod.object({
});
export type GenericTimingCreate = zod.infer<typeof ZodGenericTimingCreate>;
export function isGenericTimingCreate(data: any): data is GenericTimingCreate {
try {
ZodGenericTimingCreate.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodGenericTimingCreate' error=${e}`);
return false;
}
}

View File

@ -19,18 +19,3 @@ export function isHealthResult(data: any): data is HealthResult {
return false;
}
}
export const ZodHealthResultWrite = zod.object({
});
export type HealthResultWrite = zod.infer<typeof ZodHealthResultWrite>;
export function isHealthResultWrite(data: any): data is HealthResultWrite {
try {
ZodHealthResultWrite.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodHealthResultWrite' error=${e}`);
return false;
}
}

View File

@ -18,7 +18,9 @@ export * from "./media-informations-delta"
export * from "./object-id"
export * from "./oid-generic-data"
export * from "./oid-generic-data-soft-delete"
export * from "./part-right"
export * from "./rest-error-response"
export * from "./rest-input-error"
export * from "./season"
export * from "./series"
export * from "./timestamp"

View File

@ -21,20 +21,3 @@ export function isJwtHeader(data: any): data is JwtHeader {
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

@ -27,25 +27,3 @@ export function isJwtPayload(data: any): data is JwtPayload {
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

@ -3,8 +3,8 @@
*/
import { z as zod } from "zod";
import {ZodJwtHeader, ZodJwtHeaderWrite } from "./jwt-header";
import {ZodJwtPayload, ZodJwtPayloadWrite } from "./jwt-payload";
import {ZodJwtHeader} from "./jwt-header";
import {ZodJwtPayload} from "./jwt-payload";
export const ZodJwtToken = zod.object({
header: ZodJwtHeader,
@ -24,21 +24,3 @@ export function isJwtToken(data: any): data is JwtToken {
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

@ -19,18 +19,3 @@ export function isMediaInformationsDelta(data: any): data is MediaInformationsDe
return false;
}
}
export const ZodMediaInformationsDeltaWrite = zod.object({
});
export type MediaInformationsDeltaWrite = zod.infer<typeof ZodMediaInformationsDeltaWrite>;
export function isMediaInformationsDeltaWrite(data: any): data is MediaInformationsDeltaWrite {
try {
ZodMediaInformationsDeltaWrite.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodMediaInformationsDeltaWrite' error=${e}`);
return false;
}
}

View File

@ -4,25 +4,24 @@
import { z as zod } from "zod";
import {ZodObjectId} from "./object-id";
import {ZodUUID} from "./uuid";
import {ZodLong} from "./long";
import {ZodInteger} from "./integer";
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
import {ZodIsoDate} from "./iso-date";
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteUpdate , ZodGenericDataSoftDeleteCreate } from "./generic-data-soft-delete";
export const ZodMedia = ZodGenericDataSoftDelete.extend({
/**
* Name of the media (this represent the title)
*/
name: zod.string(),
name: zod.string().max(256),
/**
* Description of the media
*/
description: zod.string().optional(),
description: zod.string().max(8192).optional(),
/**
* Foreign Key Id of the data
*/
dataId: ZodObjectId,
dataIdOld: ZodUUID.optional(),
/**
* Type of the media
*/
@ -39,11 +38,10 @@ export const ZodMedia = ZodGenericDataSoftDelete.extend({
* Episode Id
*/
episode: ZodInteger.optional(),
date: ZodInteger.optional(),
/**
* Creation years of the media
*/
time: ZodInteger.optional(),
datePublication: ZodIsoDate.optional(),
/**
* Limitation Age of the media
*/
@ -66,20 +64,19 @@ export function isMedia(data: any): data is Media {
return false;
}
}
export const ZodMediaWrite = ZodGenericDataSoftDeleteWrite.extend({
export const ZodMediaUpdate = ZodGenericDataSoftDeleteUpdate.extend({
/**
* Name of the media (this represent the title)
*/
name: zod.string().optional(),
name: zod.string().max(256),
/**
* Description of the media
*/
description: zod.string().nullable().optional(),
description: zod.string().max(8192).nullable().optional(),
/**
* Foreign Key Id of the data
*/
dataId: ZodObjectId.optional(),
dataIdOld: ZodUUID.nullable().optional(),
dataId: ZodObjectId,
/**
* Type of the media
*/
@ -96,11 +93,10 @@ export const ZodMediaWrite = ZodGenericDataSoftDeleteWrite.extend({
* Episode Id
*/
episode: ZodInteger.nullable().optional(),
date: ZodInteger.nullable().optional(),
/**
* Creation years of the media
*/
time: ZodInteger.nullable().optional(),
datePublication: ZodIsoDate.nullable().optional(),
/**
* Limitation Age of the media
*/
@ -112,14 +108,69 @@ export const ZodMediaWrite = ZodGenericDataSoftDeleteWrite.extend({
});
export type MediaWrite = zod.infer<typeof ZodMediaWrite>;
export type MediaUpdate = zod.infer<typeof ZodMediaUpdate>;
export function isMediaWrite(data: any): data is MediaWrite {
export function isMediaUpdate(data: any): data is MediaUpdate {
try {
ZodMediaWrite.parse(data);
ZodMediaUpdate.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodMediaWrite' error=${e}`);
console.log(`Fail to parse data type='ZodMediaUpdate' error=${e}`);
return false;
}
}
export const ZodMediaCreate = ZodGenericDataSoftDeleteCreate.extend({
/**
* Name of the media (this represent the title)
*/
name: zod.string().max(256),
/**
* Description of the media
*/
description: zod.string().max(8192).nullable().optional(),
/**
* Foreign Key Id of the data
*/
dataId: ZodObjectId,
/**
* Type of the media
*/
typeId: ZodLong.nullable().optional(),
/**
* Series reference of the media
*/
seriesId: ZodLong.nullable().optional(),
/**
* Season reference of the media
*/
seasonId: ZodLong.nullable().optional(),
/**
* Episode Id
*/
episode: ZodInteger.nullable().optional(),
/**
* Creation years of the media
*/
datePublication: ZodIsoDate.nullable().optional(),
/**
* Limitation Age of the media
*/
ageLimit: ZodInteger.nullable().optional(),
/**
* List of Id of the specific covers
*/
covers: zod.array(ZodObjectId).nullable().optional(),
});
export type MediaCreate = zod.infer<typeof ZodMediaCreate>;
export function isMediaCreate(data: any): data is MediaCreate {
try {
ZodMediaCreate.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodMediaCreate' error=${e}`);
return false;
}
}

View File

@ -4,7 +4,7 @@
import { z as zod } from "zod";
import {ZodObjectId} from "./object-id";
import {ZodOIDGenericData, ZodOIDGenericDataWrite } from "./oid-generic-data";
import {ZodOIDGenericData, ZodOIDGenericDataUpdate , ZodOIDGenericDataCreate } from "./oid-generic-data";
export const ZodOIDGenericDataSoftDelete = ZodOIDGenericData.extend({
/**
@ -29,16 +29,29 @@ export function isOIDGenericDataSoftDelete(data: any): data is OIDGenericDataSof
return false;
}
}
export const ZodOIDGenericDataSoftDeleteWrite = ZodOIDGenericDataWrite;
export const ZodOIDGenericDataSoftDeleteUpdate = ZodOIDGenericDataUpdate;
export type OIDGenericDataSoftDeleteWrite = zod.infer<typeof ZodOIDGenericDataSoftDeleteWrite>;
export type OIDGenericDataSoftDeleteUpdate = zod.infer<typeof ZodOIDGenericDataSoftDeleteUpdate>;
export function isOIDGenericDataSoftDeleteWrite(data: any): data is OIDGenericDataSoftDeleteWrite {
export function isOIDGenericDataSoftDeleteUpdate(data: any): data is OIDGenericDataSoftDeleteUpdate {
try {
ZodOIDGenericDataSoftDeleteWrite.parse(data);
ZodOIDGenericDataSoftDeleteUpdate.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodOIDGenericDataSoftDeleteWrite' error=${e}`);
console.log(`Fail to parse data type='ZodOIDGenericDataSoftDeleteUpdate' error=${e}`);
return false;
}
}
export const ZodOIDGenericDataSoftDeleteCreate = ZodOIDGenericDataCreate;
export type OIDGenericDataSoftDeleteCreate = zod.infer<typeof ZodOIDGenericDataSoftDeleteCreate>;
export function isOIDGenericDataSoftDeleteCreate(data: any): data is OIDGenericDataSoftDeleteCreate {
try {
ZodOIDGenericDataSoftDeleteCreate.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodOIDGenericDataSoftDeleteCreate' error=${e}`);
return false;
}
}

View File

@ -4,7 +4,7 @@
import { z as zod } from "zod";
import {ZodObjectId} from "./object-id";
import {ZodGenericTiming, ZodGenericTimingWrite } from "./generic-timing";
import {ZodGenericTiming, ZodGenericTimingUpdate , ZodGenericTimingCreate } from "./generic-timing";
export const ZodOIDGenericData = ZodGenericTiming.extend({
/**
@ -25,16 +25,29 @@ export function isOIDGenericData(data: any): data is OIDGenericData {
return false;
}
}
export const ZodOIDGenericDataWrite = ZodGenericTimingWrite;
export const ZodOIDGenericDataUpdate = ZodGenericTimingUpdate;
export type OIDGenericDataWrite = zod.infer<typeof ZodOIDGenericDataWrite>;
export type OIDGenericDataUpdate = zod.infer<typeof ZodOIDGenericDataUpdate>;
export function isOIDGenericDataWrite(data: any): data is OIDGenericDataWrite {
export function isOIDGenericDataUpdate(data: any): data is OIDGenericDataUpdate {
try {
ZodOIDGenericDataWrite.parse(data);
ZodOIDGenericDataUpdate.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodOIDGenericDataWrite' error=${e}`);
console.log(`Fail to parse data type='ZodOIDGenericDataUpdate' error=${e}`);
return false;
}
}
export const ZodOIDGenericDataCreate = ZodGenericTimingCreate;
export type OIDGenericDataCreate = zod.infer<typeof ZodOIDGenericDataCreate>;
export function isOIDGenericDataCreate(data: any): data is OIDGenericDataCreate {
try {
ZodOIDGenericDataCreate.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodOIDGenericDataCreate' error=${e}`);
return false;
}
}

View File

@ -3,8 +3,9 @@
*/
import { z as zod } from "zod";
import {ZodObjectId} from "./object-id";
import {ZodInteger} from "./integer";
import {ZodObjectId} from "./object-id";
import {ZodRestInputError} from "./rest-input-error";
export const ZodRestErrorResponse = zod.object({
oid: ZodObjectId.optional(),
@ -13,6 +14,7 @@ export const ZodRestErrorResponse = zod.object({
time: zod.string(),
status: ZodInteger,
statusMessage: zod.string(),
inputError: zod.array(ZodRestInputError).optional(),
});

View File

@ -0,0 +1,24 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
export const ZodRestInputError = zod.object({
argument: zod.string().optional(),
path: zod.string().optional(),
message: zod.string(),
});
export type RestInputError = zod.infer<typeof ZodRestInputError>;
export function isRestInputError(data: any): data is RestInputError {
try {
ZodRestInputError.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodRestInputError' error=${e}`);
return false;
}
}

View File

@ -3,19 +3,19 @@
*/
import { z as zod } from "zod";
import {ZodLong} from "./long";
import {ZodObjectId} from "./object-id";
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
import {ZodLong} from "./long";
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteUpdate , ZodGenericDataSoftDeleteCreate } from "./generic-data-soft-delete";
export const ZodSeason = ZodGenericDataSoftDelete.extend({
/**
* Name of the media (this represent the title)
*/
name: zod.string(),
name: zod.string().max(256),
/**
* Description of the media
*/
description: zod.string().optional(),
description: zod.string().max(8192).optional(),
/**
* series parent ID
*/
@ -38,19 +38,19 @@ export function isSeason(data: any): data is Season {
return false;
}
}
export const ZodSeasonWrite = ZodGenericDataSoftDeleteWrite.extend({
export const ZodSeasonUpdate = ZodGenericDataSoftDeleteUpdate.extend({
/**
* Name of the media (this represent the title)
*/
name: zod.string().optional(),
name: zod.string().max(256),
/**
* Description of the media
*/
description: zod.string().nullable().optional(),
description: zod.string().max(8192).nullable().optional(),
/**
* series parent ID
*/
parentId: ZodLong.optional(),
parentId: ZodLong,
/**
* List of Id of the specific covers
*/
@ -58,14 +58,45 @@ export const ZodSeasonWrite = ZodGenericDataSoftDeleteWrite.extend({
});
export type SeasonWrite = zod.infer<typeof ZodSeasonWrite>;
export type SeasonUpdate = zod.infer<typeof ZodSeasonUpdate>;
export function isSeasonWrite(data: any): data is SeasonWrite {
export function isSeasonUpdate(data: any): data is SeasonUpdate {
try {
ZodSeasonWrite.parse(data);
ZodSeasonUpdate.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodSeasonWrite' error=${e}`);
console.log(`Fail to parse data type='ZodSeasonUpdate' error=${e}`);
return false;
}
}
export const ZodSeasonCreate = ZodGenericDataSoftDeleteCreate.extend({
/**
* Name of the media (this represent the title)
*/
name: zod.string().max(256),
/**
* Description of the media
*/
description: zod.string().max(8192).nullable().optional(),
/**
* series parent ID
*/
parentId: ZodLong,
/**
* List of Id of the specific covers
*/
covers: zod.array(ZodObjectId).nullable().optional(),
});
export type SeasonCreate = zod.infer<typeof ZodSeasonCreate>;
export function isSeasonCreate(data: any): data is SeasonCreate {
try {
ZodSeasonCreate.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodSeasonCreate' error=${e}`);
return false;
}
}

View File

@ -3,19 +3,19 @@
*/
import { z as zod } from "zod";
import {ZodLong} from "./long";
import {ZodObjectId} from "./object-id";
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
import {ZodLong} from "./long";
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteUpdate , ZodGenericDataSoftDeleteCreate } from "./generic-data-soft-delete";
export const ZodSeries = ZodGenericDataSoftDelete.extend({
/**
* Name of the media (this represent the title)
*/
name: zod.string(),
name: zod.string().max(256),
/**
* Description of the media
*/
description: zod.string().optional(),
description: zod.string().max(8192).optional(),
/**
* series parent ID
*/
@ -38,19 +38,19 @@ export function isSeries(data: any): data is Series {
return false;
}
}
export const ZodSeriesWrite = ZodGenericDataSoftDeleteWrite.extend({
export const ZodSeriesUpdate = ZodGenericDataSoftDeleteUpdate.extend({
/**
* Name of the media (this represent the title)
*/
name: zod.string().optional(),
name: zod.string().max(256),
/**
* Description of the media
*/
description: zod.string().nullable().optional(),
description: zod.string().max(8192).nullable().optional(),
/**
* series parent ID
*/
parentId: ZodLong.optional(),
parentId: ZodLong,
/**
* List of Id of the specific covers
*/
@ -58,14 +58,45 @@ export const ZodSeriesWrite = ZodGenericDataSoftDeleteWrite.extend({
});
export type SeriesWrite = zod.infer<typeof ZodSeriesWrite>;
export type SeriesUpdate = zod.infer<typeof ZodSeriesUpdate>;
export function isSeriesWrite(data: any): data is SeriesWrite {
export function isSeriesUpdate(data: any): data is SeriesUpdate {
try {
ZodSeriesWrite.parse(data);
ZodSeriesUpdate.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodSeriesWrite' error=${e}`);
console.log(`Fail to parse data type='ZodSeriesUpdate' error=${e}`);
return false;
}
}
export const ZodSeriesCreate = ZodGenericDataSoftDeleteCreate.extend({
/**
* Name of the media (this represent the title)
*/
name: zod.string().max(256),
/**
* Description of the media
*/
description: zod.string().max(8192).nullable().optional(),
/**
* series parent ID
*/
parentId: ZodLong,
/**
* List of Id of the specific covers
*/
covers: zod.array(ZodObjectId).nullable().optional(),
});
export type SeriesCreate = zod.infer<typeof ZodSeriesCreate>;
export function isSeriesCreate(data: any): data is SeriesCreate {
try {
ZodSeriesCreate.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodSeriesCreate' error=${e}`);
return false;
}
}

View File

@ -4,17 +4,17 @@
import { z as zod } from "zod";
import {ZodObjectId} from "./object-id";
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteUpdate , ZodGenericDataSoftDeleteCreate } from "./generic-data-soft-delete";
export const ZodType = ZodGenericDataSoftDelete.extend({
/**
* Name of the media (this represent the title)
*/
name: zod.string(),
name: zod.string().max(256),
/**
* Description of the media
*/
description: zod.string().optional(),
description: zod.string().max(8192).optional(),
/**
* List of Id of the specific covers
*/
@ -33,15 +33,15 @@ export function isType(data: any): data is Type {
return false;
}
}
export const ZodTypeWrite = ZodGenericDataSoftDeleteWrite.extend({
export const ZodTypeUpdate = ZodGenericDataSoftDeleteUpdate.extend({
/**
* Name of the media (this represent the title)
*/
name: zod.string().optional(),
name: zod.string().max(256),
/**
* Description of the media
*/
description: zod.string().nullable().optional(),
description: zod.string().max(8192).nullable().optional(),
/**
* List of Id of the specific covers
*/
@ -49,14 +49,41 @@ export const ZodTypeWrite = ZodGenericDataSoftDeleteWrite.extend({
});
export type TypeWrite = zod.infer<typeof ZodTypeWrite>;
export type TypeUpdate = zod.infer<typeof ZodTypeUpdate>;
export function isTypeWrite(data: any): data is TypeWrite {
export function isTypeUpdate(data: any): data is TypeUpdate {
try {
ZodTypeWrite.parse(data);
ZodTypeUpdate.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodTypeWrite' error=${e}`);
console.log(`Fail to parse data type='ZodTypeUpdate' error=${e}`);
return false;
}
}
export const ZodTypeCreate = ZodGenericDataSoftDeleteCreate.extend({
/**
* Name of the media (this represent the title)
*/
name: zod.string().max(256),
/**
* Description of the media
*/
description: zod.string().max(8192).nullable().optional(),
/**
* List of Id of the specific covers
*/
covers: zod.array(ZodObjectId).nullable().optional(),
});
export type TypeCreate = zod.infer<typeof ZodTypeCreate>;
export function isTypeCreate(data: any): data is TypeCreate {
try {
ZodTypeCreate.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodTypeCreate' error=${e}`);
return false;
}
}

View File

@ -3,7 +3,7 @@
*/
import { z as zod } from "zod";
import {ZodUser, ZodUserWrite } from "./user";
import {ZodUser, ZodUserUpdate , ZodUserCreate } from "./user";
export const ZodUserKarideo = ZodUser;
@ -18,16 +18,3 @@ export function isUserKarideo(data: any): data is UserKarideo {
return false;
}
}
export const ZodUserKarideoWrite = ZodUserWrite;
export type UserKarideoWrite = zod.infer<typeof ZodUserKarideoWrite>;
export function isUserKarideoWrite(data: any): data is UserKarideoWrite {
try {
ZodUserKarideoWrite.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodUserKarideoWrite' error=${e}`);
return false;
}
}

View File

@ -4,15 +4,15 @@
import { z as zod } from "zod";
import {ZodLong} from "./long";
import {ZodFloat} from "./float";
import {ZodInteger} from "./integer";
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
import {ZodFloat} from "./float";
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteUpdate , ZodGenericDataSoftDeleteCreate } from "./generic-data-soft-delete";
export const ZodUserMediaAdvancement = ZodGenericDataSoftDelete.extend({
/**
* Foreign Key Id of the user
*/
userId: ZodLong.optional(),
userId: ZodLong.readonly().optional(),
/**
* Id of the media
*/
@ -43,38 +43,65 @@ export function isUserMediaAdvancement(data: any): data is UserMediaAdvancement
return false;
}
}
export const ZodUserMediaAdvancementWrite = ZodGenericDataSoftDeleteWrite.extend({
/**
* Foreign Key Id of the user
*/
userId: ZodLong.nullable().optional(),
export const ZodUserMediaAdvancementUpdate = ZodGenericDataSoftDeleteUpdate.extend({
/**
* Id of the media
*/
mediaId: ZodLong.optional(),
mediaId: ZodLong,
/**
* Percent of advancement in the media
*/
percent: ZodFloat.optional(),
percent: ZodFloat,
/**
* Number of second of advancement in the media
*/
time: ZodInteger.optional(),
time: ZodInteger,
/**
* Number of time this media has been read
*/
count: ZodInteger.optional(),
count: ZodInteger,
});
export type UserMediaAdvancementWrite = zod.infer<typeof ZodUserMediaAdvancementWrite>;
export type UserMediaAdvancementUpdate = zod.infer<typeof ZodUserMediaAdvancementUpdate>;
export function isUserMediaAdvancementWrite(data: any): data is UserMediaAdvancementWrite {
export function isUserMediaAdvancementUpdate(data: any): data is UserMediaAdvancementUpdate {
try {
ZodUserMediaAdvancementWrite.parse(data);
ZodUserMediaAdvancementUpdate.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodUserMediaAdvancementWrite' error=${e}`);
console.log(`Fail to parse data type='ZodUserMediaAdvancementUpdate' error=${e}`);
return false;
}
}
export const ZodUserMediaAdvancementCreate = ZodGenericDataSoftDeleteCreate.extend({
/**
* Id of the media
*/
mediaId: ZodLong,
/**
* Percent of advancement in the media
*/
percent: ZodFloat,
/**
* Number of second of advancement in the media
*/
time: ZodInteger,
/**
* Number of time this media has been read
*/
count: ZodInteger,
});
export type UserMediaAdvancementCreate = zod.infer<typeof ZodUserMediaAdvancementCreate>;
export function isUserMediaAdvancementCreate(data: any): data is UserMediaAdvancementCreate {
try {
ZodUserMediaAdvancementCreate.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodUserMediaAdvancementCreate' error=${e}`);
return false;
}
}

View File

@ -22,20 +22,3 @@ export function isUserOut(data: any): data is UserOut {
return false;
}
}
export const ZodUserOutWrite = zod.object({
id: ZodLong,
login: zod.string().nullable().optional(),
});
export type UserOutWrite = zod.infer<typeof ZodUserOutWrite>;
export function isUserOutWrite(data: any): data is UserOutWrite {
try {
ZodUserOutWrite.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodUserOutWrite' error=${e}`);
return false;
}
}

View File

@ -5,7 +5,7 @@ import { z as zod } from "zod";
import {ZodTimestamp} from "./timestamp";
import {ZodUUID} from "./uuid";
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteUpdate , ZodGenericDataSoftDeleteCreate } from "./generic-data-soft-delete";
export const ZodUser = ZodGenericDataSoftDelete.extend({
login: zod.string().min(3).max(128),
@ -30,8 +30,8 @@ export function isUser(data: any): data is User {
return false;
}
}
export const ZodUserWrite = ZodGenericDataSoftDeleteWrite.extend({
login: zod.string().min(3).max(128).optional(),
export const ZodUserUpdate = ZodGenericDataSoftDeleteUpdate.extend({
login: zod.string().min(3).max(128),
lastConnection: ZodTimestamp.nullable().optional(),
blocked: zod.boolean().nullable().optional(),
blockedReason: zod.string().max(512).nullable().optional(),
@ -42,14 +42,37 @@ export const ZodUserWrite = ZodGenericDataSoftDeleteWrite.extend({
});
export type UserWrite = zod.infer<typeof ZodUserWrite>;
export type UserUpdate = zod.infer<typeof ZodUserUpdate>;
export function isUserWrite(data: any): data is UserWrite {
export function isUserUpdate(data: any): data is UserUpdate {
try {
ZodUserWrite.parse(data);
ZodUserUpdate.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodUserWrite' error=${e}`);
console.log(`Fail to parse data type='ZodUserUpdate' error=${e}`);
return false;
}
}
export const ZodUserCreate = ZodGenericDataSoftDeleteCreate.extend({
login: zod.string().min(3).max(128),
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 type UserCreate = zod.infer<typeof ZodUserCreate>;
export function isUserCreate(data: any): data is UserCreate {
try {
ZodUserCreate.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodUserCreate' error=${e}`);
return false;
}
}

View File

@ -3,30 +3,29 @@
* @copyright 2024, Edouard DUPIN, all right reserved
* @license MPL-2
*/
import { RestErrorResponse, isRestErrorResponse } from "./model";
import { RestErrorResponse, isRestErrorResponse } from './model';
export enum HTTPRequestModel {
ARCHIVE = "ARCHIVE",
DELETE = "DELETE",
HEAD = "HEAD",
GET = "GET",
OPTION = "OPTION",
PATCH = "PATCH",
POST = "POST",
PUT = "PUT",
RESTORE = "RESTORE",
ARCHIVE = 'ARCHIVE',
DELETE = 'DELETE',
HEAD = 'HEAD',
GET = 'GET',
OPTION = 'OPTION',
PATCH = 'PATCH',
POST = 'POST',
PUT = 'PUT',
RESTORE = 'RESTORE',
}
export enum HTTPMimeType {
ALL = "*/*",
CSV = "text/csv",
IMAGE = "image/*",
IMAGE_JPEG = "image/jpeg",
IMAGE_PNG = "image/png",
JSON = "application/json",
MULTIPART = "multipart/form-data",
OCTET_STREAM = "application/octet-stream",
TEXT_PLAIN = "text/plain",
ALL = '*/*',
CSV = 'text/csv',
IMAGE = 'image/*',
IMAGE_JPEG = 'image/jpeg',
IMAGE_PNG = 'image/png',
JSON = 'application/json',
MULTIPART = 'multipart/form-data',
OCTET_STREAM = 'application/octet-stream',
TEXT_PLAIN = 'text/plain',
}
export interface RESTConfig {
@ -54,6 +53,14 @@ export interface ModelResponseHttp {
data: any;
}
export type ErrorRestApiCallback = (response: Response) => void;
let errorApiGlobalCallback: ErrorRestApiCallback | undefined = undefined;
export const setErrorApiGlobalCallback = (callback: ErrorRestApiCallback) => {
errorApiGlobalCallback = callback;
};
function isNullOrUndefined(data: any): data is undefined | null {
return data === undefined || data === null;
}
@ -78,6 +85,7 @@ export interface RESTRequestType {
data?: any;
params?: object;
queries?: object;
headers?: any;
callbacks?: RESTCallbacks;
}
@ -87,15 +95,15 @@ function replaceAll(input, searchValue, replaceValue) {
function removeTrailingSlashes(input: string): string {
if (isNullOrUndefined(input)) {
return "undefined";
return 'undefined';
}
return input.replace(/\/+$/, "");
return input.replace(/\/+$/, '');
}
function removeLeadingSlashes(input: string): string {
if (isNullOrUndefined(input)) {
return "";
return '';
}
return input.replace(/^\/+/, "");
return input.replace(/^\/+/, '');
}
export function RESTUrl({
@ -133,9 +141,9 @@ export function RESTUrl({
}
}
if (restConfig.token !== undefined && restModel.tokenInUrl === true) {
searchParams.append("Authorization", `Bearer ${restConfig.token}`);
searchParams.append('Authorization', `Bearer ${restConfig.token}`);
}
return generateUrl + "?" + searchParams.toString();
return generateUrl + '?' + searchParams.toString();
}
export function fetchProgress(
@ -159,7 +167,7 @@ export function fetchProgress(
return new Promise((resolve, reject) => {
// Stream the upload progress
if (progressUpload) {
xhr.io?.upload.addEventListener("progress", (dataEvent) => {
xhr.io?.upload.addEventListener('progress', (dataEvent) => {
if (dataEvent.lengthComputable) {
progressUpload(dataEvent.loaded, dataEvent.total);
}
@ -167,7 +175,7 @@ export function fetchProgress(
}
// Stream the download progress
if (progressDownload) {
xhr.io?.addEventListener("progress", (dataEvent) => {
xhr.io?.addEventListener('progress', (dataEvent) => {
if (dataEvent.lengthComputable) {
progressDownload(dataEvent.loaded, dataEvent.total);
}
@ -187,19 +195,19 @@ export function fetchProgress(
};
}
// Check if we have an internal Fail:
xhr.io?.addEventListener("error", () => {
xhr.io?.addEventListener('error', () => {
xhr.io = undefined;
reject(new TypeError("Failed to fetch"));
reject(new TypeError('Failed to fetch'));
});
// Capture the end of the stream
xhr.io?.addEventListener("loadend", () => {
xhr.io?.addEventListener('loadend', () => {
if (xhr.io?.readyState !== XMLHttpRequest.DONE) {
return;
}
if (xhr.io?.status === 0) {
//the stream has been aborted
reject(new TypeError("Fetch has been aborted"));
reject(new TypeError('Fetch has been aborted'));
return;
}
// Stream is ended, transform in a generic response:
@ -209,17 +217,17 @@ export function fetchProgress(
});
const headersArray = replaceAll(
xhr.io.getAllResponseHeaders().trim(),
"\r\n",
"\n"
).split("\n");
'\r\n',
'\n'
).split('\n');
headersArray.forEach(function (header) {
const firstColonIndex = header.indexOf(":");
const firstColonIndex = header.indexOf(':');
if (firstColonIndex !== -1) {
const key = header.substring(0, firstColonIndex).trim();
const value = header.substring(firstColonIndex + 1).trim();
response.headers.set(key, value);
} else {
response.headers.set(header, "");
response.headers.set(header, '');
}
});
xhr.io = undefined;
@ -241,27 +249,29 @@ export function RESTRequest({
data,
params,
queries,
headers = {},
callbacks,
}: RESTRequestType): Promise<ModelResponseHttp> {
// Create the URL PATH:
let generateUrl = RESTUrl({ restModel, restConfig, data, params, queries });
let headers: any = {};
if (restConfig.token !== undefined && restModel.tokenInUrl !== true) {
headers["Authorization"] = `Bearer ${restConfig.token}`;
headers['Authorization'] = `Bearer ${restConfig.token}`;
}
if (restModel.accept !== undefined) {
headers["Accept"] = restModel.accept;
headers['Accept'] = restModel.accept;
}
if (restModel.requestType !== HTTPRequestModel.GET &&
restModel.requestType !== HTTPRequestModel.ARCHIVE &&
restModel.requestType !== HTTPRequestModel.RESTORE
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 (restModel.contentType !== HTTPMimeType.MULTIPART &&
restModel.contentType !== undefined
) {
if (
restModel.contentType !== HTTPMimeType.MULTIPART &&
restModel.contentType !== undefined
) {
// 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;
}
}
let body = data;
@ -302,19 +312,27 @@ export function RESTRequest({
}
action
.then((response: Response) => {
if (
errorApiGlobalCallback &&
400 <= response.status &&
response.status <= 499
) {
// Detect an error and trigger the generic error callback:
errorApiGlobalCallback(response);
}
if (response.status >= 200 && response.status <= 299) {
const contentType = response.headers.get("Content-Type");
const contentType = response.headers.get('Content-Type');
if (
!isNullOrUndefined(restModel.accept) &&
restModel.accept !== contentType
) {
reject({
name: "Model accept type incompatible",
name: 'Model accept type incompatible',
time: Date().toString(),
status: 901,
message: `REST Content type are not compatible: ${restModel.accept} != ${contentType}`,
statusMessage: "Fetch error",
error: "rest-tools.ts Wrong type in the message return type",
statusMessage: 'Fetch error',
error: 'rest-tools.ts Wrong type in the message return type',
} as RestErrorResponse);
} else if (contentType === HTTPMimeType.JSON) {
response
@ -324,12 +342,12 @@ export function RESTRequest({
})
.catch((reason: Error) => {
reject({
name: "API serialization error",
name: 'API serialization error',
time: Date().toString(),
status: 902,
message: `REST parse json fail: ${reason}`,
statusMessage: "Fetch parse error",
error: "rest-tools.ts Wrong message model to parse",
statusMessage: 'Fetch parse error',
error: 'rest-tools.ts Wrong message model to parse',
} as RestErrorResponse);
});
} else {
@ -349,22 +367,22 @@ export function RESTRequest({
.text()
.then((dataError: string) => {
reject({
name: "API serialization error",
name: 'API serialization error',
time: Date().toString(),
status: 903,
message: `REST parse error json with wrong type fail. ${dataError}`,
statusMessage: "Fetch parse error",
error: "rest-tools.ts Wrong message model to parse",
statusMessage: 'Fetch parse error',
error: 'rest-tools.ts Wrong message model to parse',
} as RestErrorResponse);
})
.catch((reason: any) => {
reject({
name: "API serialization error",
name: 'API serialization error',
time: Date().toString(),
status: response.status,
message: `unmanaged error model: ??? with error: ${reason}`,
statusMessage: "Fetch ERROR parse error",
error: "rest-tools.ts Wrong message model to parse",
statusMessage: 'Fetch ERROR parse error',
error: 'rest-tools.ts Wrong message model to parse',
} as RestErrorResponse);
});
}
@ -374,22 +392,22 @@ export function RESTRequest({
.text()
.then((dataError: string) => {
reject({
name: "API serialization error",
name: 'API serialization error',
time: Date().toString(),
status: response.status,
message: `unmanaged error model: ${dataError} with error: ${reason}`,
statusMessage: "Fetch ERROR TEXT parse error",
error: "rest-tools.ts Wrong message model to parse",
statusMessage: 'Fetch ERROR TEXT parse error',
error: 'rest-tools.ts Wrong message model to parse',
} as RestErrorResponse);
})
.catch((reason: any) => {
reject({
name: "API serialization error",
name: 'API serialization error',
time: Date().toString(),
status: response.status,
message: `unmanaged error model: ??? with error: ${reason}`,
statusMessage: "Fetch ERROR TEXT FAIL",
error: "rest-tools.ts Wrong message model to parse",
statusMessage: 'Fetch ERROR TEXT FAIL',
error: 'rest-tools.ts Wrong message model to parse',
} as RestErrorResponse);
});
});
@ -400,12 +418,12 @@ export function RESTRequest({
reject(error);
} else {
reject({
name: "Request fail",
name: 'Request fail',
time: Date(),
status: 999,
message: error,
statusMessage: "Fetch catch error",
error: "rest-tools.ts detect an error in the fetch request",
statusMessage: 'Fetch catch error',
error: 'rest-tools.ts detect an error in the fetch request',
});
}
});
@ -426,12 +444,12 @@ export function RESTRequestJson<TYPE>(
resolve(value.data);
} else {
reject({
name: "Model check fail",
name: 'Model check fail',
time: Date().toString(),
status: 950,
error: "REST Fail to verify the data",
statusMessage: "API cast ERROR",
message: "api.ts Check type as fail",
error: 'REST Fail to verify the data',
statusMessage: 'API cast ERROR',
message: 'api.ts Check type as fail',
} as RestErrorResponse);
}
})

View File

@ -8,7 +8,7 @@ import { DataUrlAccess } from '@/utils/data-url-access';
import { Icon } from './Icon';
export type CoversProps = Omit<BoxProps, 'iconEmpty'> & {
data?: ObjectId[];
data?: readonly ObjectId[];
size?: BoxProps['width'];
iconEmpty?: ReactElement;
slideshow?: boolean;
@ -70,7 +70,9 @@ export const Covers = ({
src={url}
maxWidth={size}
maxHeight={size}
boxSize={size}
//boxSize={size}
width="100%"
height="100%"
onClick={onClick}
/>
);

View File

@ -1,26 +1,62 @@
import { ReactNode } from 'react';
import { Box, Button, ConditionalValue, Flex, HStack, IconButton, Span, Text, useDisclosure } from '@chakra-ui/react';
import { LuAlignJustify, LuArrowBigLeft, LuCircleUserRound, LuKeySquare, LuLogIn, LuLogOut, LuMoon, LuSettings, LuSun } from 'react-icons/lu';
import { MdHelp, MdHome, MdMore, MdOutlinePlaylistPlay, MdOutlineUploadFile, MdSupervisedUserCircle } from 'react-icons/md';
import {
Box,
Button,
ConditionalValue,
Flex,
HStack,
IconButton,
Span,
Text,
useBreakpointValue,
useDisclosure,
} from '@chakra-ui/react';
import {
LuAlignJustify,
LuArrowBigLeft,
LuCircleUserRound,
LuKeySquare,
LuLogIn,
LuLogOut,
LuMoon,
LuSettings,
LuSun,
} from 'react-icons/lu';
import {
MdHelp,
MdHome,
MdMore,
MdOutlinePlaylistPlay,
MdOutlineUploadFile,
MdRestartAlt,
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 {
DrawerBody,
DrawerContent,
DrawerHeader,
DrawerRoot,
} from '@/components/ui/drawer';
import {
MenuContent,
MenuItem,
MenuRoot,
MenuTrigger,
} from '@/components/ui/menu';
import { environment } from '@/environment';
import { useServiceContext } from '@/service/ServiceContext';
import { useSessionService } from '@/service/session';
import { colors } from '@/theme/colors';
import { requestOpenSite, requestSignIn, requestSignOut, requestSignUp } from '@/utils/sso';
import {
requestOpenSite,
requestSignIn,
requestSignOut,
requestSignUp,
} from '@/utils/sso';
export const TOP_BAR_HEIGHT = '50px';
@ -37,6 +73,8 @@ export const BUTTON_TOP_BAR_PROPERTY = {
export type TopBarProps = {
children?: ReactNode;
title?: string;
titleLink?: string;
titleIcon?: ReactNode;
};
const ButtonMenuLeft = ({
@ -74,15 +112,25 @@ const ButtonMenuLeft = ({
</>
);
};
export const TopBar = ({ title, children }: TopBarProps) => {
export const TopBar = ({
title,
titleLink,
titleIcon,
children,
}: TopBarProps) => {
const navigate = useNavigate();
const { colorMode, toggleColorMode } = useColorMode();
const { session } = useServiceContext();
const { clearToken } = useSessionService();
const { clearToken, isConnected } = useSessionService();
const backColor = useColorModeValue('back.100', 'back.800');
const drawerDisclose = useDisclosure();
const onChangeTheme = () => {
drawerDisclose.onOpen();
const isVisible = useBreakpointValue({ base: false, md: true });
const onOpenLeftMenu = () => {
if (!isConnected) {
onForceReload();
} else {
drawerDisclose.onOpen();
}
};
const onSignIn = (): void => {
clearToken();
@ -99,8 +147,13 @@ export const TopBar = ({ title, children }: TopBarProps) => {
const onKarso = (): void => {
requestOpenSite();
};
const onForceReload = (): void => {
// @ts-expect-error
window.location.reload(true);
};
return (
<Flex
minWidth="320px"
position="absolute"
top={0}
left={0}
@ -114,33 +167,32 @@ export const TopBar = ({ title, children }: TopBarProps) => {
boxShadow={'0px 2px 4px ' + colors.back[900]}
zIndex={200}
>
{session?.isConnected ?
<Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onChangeTheme}>
<Button {...BUTTON_TOP_BAR_PROPERTY} onClick={onOpenLeftMenu}>
<HStack>
<LuAlignJustify />
<Text paddingLeft="3px" fontWeight="bold">
{environment.applName}
</Text>
{isVisible && (
<Text paddingLeft="3px" fontWeight="bold">
{environment.applName}
</Text>
)}
</HStack>
</Button>
:(
<HStack {...BUTTON_TOP_BAR_PROPERTY} >
<LuAlignJustify />
<Span paddingLeft="3px" fontWeight="bold">{environment.applName}</Span>
</HStack>)
}
{title && (
<Text
<Flex
truncate
fontSize="20px"
fontWeight="bold"
textTransform="uppercase"
marginRight="auto"
userSelect="none"
color="brand.500"
onClick={titleLink ? () => navigate(titleLink) : undefined}
>
{title}
</Text>
<Flex gap="4px">
{titleIcon}
{title}
</Flex>
</Flex>
)}
{children}
<Flex right="0">
@ -205,6 +257,13 @@ export const TopBar = ({ title, children }: TopBarProps) => {
<MenuItem value="karso" valueText="Karso" onClick={onKarso}>
<LuKeySquare /> Karso (SSO)
</MenuItem>
<MenuItem
value="force_reload"
valueText="Karso"
onClick={onForceReload}
>
<MdRestartAlt /> force reload
</MenuItem>
{colorMode === 'light' ? (
<MenuItem
value="set-dark"
@ -226,52 +285,52 @@ export const TopBar = ({ title, children }: TopBarProps) => {
</MenuRoot>
)}
</Flex>
{session?.isConnected &&
<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">{environment.applName}</Span>
</HStack>
</DrawerHeader>
<DrawerBody paddingX="0px">
<Box marginY="3" />
<ButtonMenuLeft
onClickEnd={drawerDisclose.onClose}
dest="/"
title="Home"
icon={<MdHome />}
/>
<ButtonMenuLeft
onClickEnd={drawerDisclose.onClose}
dest="/on-air"
title="On air"
icon={<MdOutlinePlaylistPlay />}
/>
<ButtonMenuLeft
onClickEnd={drawerDisclose.onClose}
dest="/add"
title="Add Media"
icon={<MdOutlineUploadFile />}
/>
</DrawerBody>
</DrawerContent>
</DrawerRoot>
}
{session?.isConnected && (
<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">{environment.applName}</Span>
</HStack>
</DrawerHeader>
<DrawerBody paddingX="0px">
<Box marginY="3" />
<ButtonMenuLeft
onClickEnd={drawerDisclose.onClose}
dest="/"
title="Home"
icon={<MdHome />}
/>
<ButtonMenuLeft
onClickEnd={drawerDisclose.onClose}
dest="/on-air"
title="On air"
icon={<MdOutlinePlaylistPlay />}
/>
<ButtonMenuLeft
onClickEnd={drawerDisclose.onClose}
dest="/add"
title="Add Media"
icon={<MdOutlineUploadFile />}
/>
</DrawerBody>
</DrawerContent>
</DrawerRoot>
)}
</Flex>
);
};
};

View File

@ -1,6 +1,14 @@
import { useEffect, useRef, useState } from 'react';
import { Box, chakra, Flex, IconButton, SliderTrack, Spacer, Text, useBreakpointValue } from '@chakra-ui/react';
import {
Box,
Flex,
IconButton,
SliderTrack,
Spacer,
Text,
useBreakpointValue,
} from '@chakra-ui/react';
import {
MdFastForward,
MdFastRewind,
@ -16,7 +24,7 @@ import {
MdRepeat,
MdRepeatOne,
MdStop,
MdTrendingFlat
MdTrendingFlat,
} from 'react-icons/md';
import { useColorModeValue } from '@/components/ui/color-mode';
@ -70,15 +78,15 @@ export const VideoPlayer = ({}: AudioPlayerProps) => {
const [isRunning, setIsRunning] = useState(true);
useEffect(() => {
if (!isRunning || time <= 0) {
console.log(`exit timer`);
setIsRunning(false);
console.log(`exit timer`);
setIsRunning(false);
return;
}
const timer = setInterval(() => {
setTime((prevTime) => {
console.log(`current time : ${prevTime}`);
return prevTime - 1;
});
});
}, 1000);
return () => clearInterval(timer);
@ -97,9 +105,9 @@ export const VideoPlayer = ({}: AudioPlayerProps) => {
setIsRunning(true);
}
};
window.addEventListener("mousemove", resetTimerLocal);
window.addEventListener('mousemove', resetTimerLocal);
return () => {
window.removeEventListener("mousemove", resetTimerLocal);
window.removeEventListener('mousemove', resetTimerLocal);
};
}, [playMediaList, setTime, setIsRunning]);
@ -121,30 +129,33 @@ export const VideoPlayer = ({}: AudioPlayerProps) => {
useEffect(() => {
setMediaSource(
dataMedia && dataMedia?.dataId
? DataUrlAccess.getUrl(dataMedia?.dataId)
? DataUrlAccess.getUrl(dataMedia?.dataId, 'plop.mkv')
: ''
);
}, [dataMedia, setMediaSource]);
console.log(`mediaSource = ${mediaSource}`);
const backColor = useColorModeValue('back.100', 'back.800');
const configButton = isMobile ? {
borderRadius: 'full',
backgroundColor: 'transparent',
_hover: {
bgColor: 'brand.500',
},
width: '35px',
height: '35px',
padding: '2px',
} : {
borderRadius: 'full',
backgroundColor: 'transparent',
_hover: {
bgColor: 'brand.500',
},
width: '50px',
height: '50px',
padding: '5px',
};
const configButton = isMobile
? {
borderRadius: 'full',
backgroundColor: 'transparent',
_hover: {
bgColor: 'brand.500',
},
width: '35px',
height: '35px',
padding: '2px',
}
: {
borderRadius: 'full',
backgroundColor: 'transparent',
_hover: {
bgColor: 'brand.500',
},
width: '50px',
height: '50px',
padding: '5px',
};
useEffect(() => {
if (!videoRef || !videoRef.current) {
@ -237,6 +248,7 @@ export const VideoPlayer = ({}: AudioPlayerProps) => {
* Call when meta-data is updated
*/
function onChangeMetadata(): void {
console.log(`onChangeMetadata: ${videoRef.current?.duration}`);
const seconds = videoRef.current?.duration;
if (seconds !== undefined) {
setDuration(seconds);
@ -246,14 +258,16 @@ export const VideoPlayer = ({}: AudioPlayerProps) => {
if (!videoRef || !videoRef.current) {
return;
}
//console.log(`onTimeUpdate ${videoRef.current.currentTime}`);
console.log(`onTimeUpdate ${videoRef.current.currentTime}`);
setTimeProgress(videoRef.current.currentTime);
};
const onDurationChange = (event) => {};
const onChangeStateToPlay = () => {
console.log(`onChangeStateToPlay ...`);
setIsPlaying(true);
};
const onChangeStateToPause = () => {
console.log(`onChangeStateToPause ...`);
setIsPlaying(false);
};
const marks = () => {
@ -292,208 +306,227 @@ export const VideoPlayer = ({}: AudioPlayerProps) => {
setIsPiP(false);
}
} catch (error) {
console.error("Erreur avec Picture-in-Picture:", error);
console.error('Erreur avec Picture-in-Picture:', error);
}
}
};
const [isFullScreen, setIsFullScreen] = useState(false);
useEffect(() => {
const handleFullScreenChange = () => {
console.log(`changeFullScreen: ${!!document.fullscreenElement}`)
console.log(`changeFullScreen: ${!!document.fullscreenElement}`);
setIsFullScreen(!!document.fullscreenElement);
};
document.addEventListener("fullscreenchange", handleFullScreenChange);
document.addEventListener('fullscreenchange', handleFullScreenChange);
return () => {
document.removeEventListener("fullscreenchange", handleFullScreenChange);
document.removeEventListener('fullscreenchange', handleFullScreenChange);
};
}, []);
return (
<>
{!isNullOrUndefined(MediaOffset) && (
<>
<Flex
position="absolute"
//height="500px"
minHeight="150px"
paddingY="5px"
paddingX="10px"
marginX="15px"
bottom={0}
left={0}
right={0}
zIndex={1000}
borderWidth="1px"
borderColor="brand.900"
bgColor={isFullScreen ? "0x000000" : backColor}
borderTopRadius="10px"
direction="column"
ref={containerRef}
<Flex
position="absolute"
//height="500px"
minHeight="150px"
paddingY="5px"
paddingX="10px"
marginX="15px"
bottom={0}
left={0}
right={0}
zIndex={1000}
borderWidth="1px"
borderColor="brand.900"
bgColor={isFullScreen ? '0x000000' : backColor}
borderTopRadius="10px"
direction="column"
ref={containerRef}
>
<chakra.video
position={isFullScreen ? "absolute" : "relative"}
maxHeight={isFullScreen ? undefined : "30vh"}
maxWidth={isFullScreen ? undefined : "100%"}
height={isFullScreen ? "100%" : undefined}
width={isFullScreen ? "100%": undefined}
<Flex
position={isFullScreen ? 'absolute' : 'relative'}
maxHeight={isFullScreen ? undefined : '30vh'}
maxWidth={isFullScreen ? undefined : '100%'}
marginX="auto"
src={mediaSource}
ref={videoRef}
//preload={true}
onPlay={onChangeStateToPlay}
onPause={onChangeStateToPause}
onTimeUpdate={onTimeUpdate}
onDurationChange={onDurationChange}
onLoadedMetadata={onChangeMetadata}
autoPlay={true}
onEnded={onAudioEnded}
>
<video
height={isFullScreen ? '100%' : undefined}
width={isFullScreen ? '100%' : undefined}
src={mediaSource}
ref={videoRef}
onPlay={onChangeStateToPlay}
onPause={onChangeStateToPause}
onTimeUpdate={onTimeUpdate}
onDurationChange={onDurationChange}
onLoadedMetadata={onChangeMetadata}
autoPlay={true}
onEnded={onAudioEnded}
/>
{(!isFullScreen || (!isMobile || isRunning)) && <><Text
alignContent="left"
fontSize="20px"
fontWeight="bold"
userSelect="none"
marginRight="auto"
overflow="hidden"
// noOfLines={1}
>
{dataMedia?.name ?? '???'}
</Text>
<Text
alignContent="left"
fontSize="16px"
userSelect="none"
marginRight="auto"
overflow="hidden"
// noOfLines={1}
>
{dataSeries && dataSeries.name}
{dataSeason && dataSeason?.name}
{dataType && ` / ${dataType.name}`}
</Text>
{isFullScreen && <Spacer/>}
<Box width="full" paddingX="15px">
<Slider
defaultValue={[0]}
value={[timeProgress]}
min={0}
max={duration}
step={0.1}
onValueChange={(e) => onSeek(e.value)}
variant="outline"
colorPalette="brand"
marks={marks()}
//focusCapture={false}
>
<SliderTrack
bg="brand.200"
height="10px"
borderRadius="full"
></SliderTrack>
</Slider>
</Box>
<Flex>
<Text
alignContent="left"
fontSize="16px"
userSelect="none"
marginRight="auto"
overflow="hidden"
// noOfLines={1}
>
{formatTime(timeProgress)}
</Text>
<Text alignContent="left" fontSize="16px" userSelect="none">
{formatTime(duration)}
</Text>
</Flex>
{(!isFullScreen || !isMobile || isRunning) && (
<>
<Text
alignContent="left"
fontSize="20px"
fontWeight="bold"
userSelect="none"
marginRight="auto"
overflow="hidden"
// noOfLines={1}
>
{dataMedia?.name ?? '???'}
</Text>
<Text
alignContent="left"
fontSize="16px"
userSelect="none"
marginRight="auto"
overflow="hidden"
// noOfLines={1}
>
{dataSeries && dataSeries.name}
{dataSeason && dataSeason?.name}
{dataType && ` / ${dataType.name}`}
</Text>
{isFullScreen && <Spacer />}
<Box width="full" paddingX="15px">
<Slider
defaultValue={[0]}
value={[timeProgress]}
min={0}
max={duration}
step={0.1}
onValueChange={(e) => onSeek(e.value)}
variant="outline"
colorPalette="brand"
marks={marks()}
//focusCapture={false}
>
<SliderTrack
bg="brand.200"
height="10px"
borderRadius="full"
></SliderTrack>
</Slider>
</Box>
<Flex>
<Text
alignContent="left"
fontSize="16px"
userSelect="none"
marginRight="auto"
overflow="hidden"
// noOfLines={1}
>
{formatTime(timeProgress)}
</Text>
<Text alignContent="left" fontSize="16px" userSelect="none">
{formatTime(duration)}
</Text>
</Flex>
<Flex gap="5px">
<IconButton
{...configButton}
aria-label={'Play'}
onClick={onPlay}
variant="ghost"
>
{isPlaying ? (
<MdPause style={{ width: '100%', height: '100%' }} />
) : (
<MdPlayArrow style={{ width: '100%', height: '100%' }} />
)}
</IconButton>
<IconButton
{...configButton}
aria-label={'Stop'}
onClick={onStop}
variant="ghost"
>
<MdStop style={{ width: '100%', height: '100%' }} />
</IconButton>
<IconButton
{...configButton}
aria-label={'Previous Media'}
onClick={onNavigatePrevious}
marginLeft="auto"
variant="ghost"
>
<MdNavigateBefore
style={{ width: '100%', height: '100%' }}
/>{' '}
</IconButton>
<IconButton
{...configButton}
aria-label={'jump 15sec in past'}
onClick={onFastRewind}
variant="ghost"
>
<MdFastRewind style={{ width: '100%', height: '100%' }} />
</IconButton>
<IconButton
{...configButton}
aria-label={'jump 15sec in future'}
onClick={onFastForward}
variant="ghost"
>
<MdFastForward style={{ width: '100%', height: '100%' }} />
</IconButton>
<IconButton
{...configButton}
aria-label={'Next Media'}
marginRight="auto"
onClick={onNavigateNext}
variant="ghost"
>
<MdNavigateNext style={{ width: '100%', height: '100%' }} />
</IconButton>
<IconButton
{...configButton}
aria-label={'continue to the end'}
onClick={onTypePlay}
variant="ghost"
>
{playModeIcon[playingMode]}
</IconButton>
<IconButton
{...configButton}
aria-label={'Fullscreen'}
onClick={onFullScreen}
variant="ghost"
>
{!!document.fullscreenElement ? (
<MdFullscreenExit
style={{ width: '100%', height: '100%' }}
/>
) : (
<MdFullscreen style={{ width: '100%', height: '100%' }} />
)}
</IconButton>
{isPiPSupported && !isMobile && (
<IconButton
{...configButton}
aria-label={'Fullscreen'}
onClick={onPictureInPicture}
variant="ghost"
>
{isPiP ? (
<MdOutlinePictureInPictureAlt
style={{ width: '100%', height: '100%' }}
/>
) : (
<MdPictureInPictureAlt
style={{ width: '100%', height: '100%' }}
/>
)}
</IconButton>
)}
</Flex>
</>
)}
</Flex>
<Flex gap="5px">
<IconButton
{...configButton}
aria-label={'Play'}
onClick={onPlay}
variant="ghost"
>
{isPlaying ? (
<MdPause style={{ width: '100%', height: '100%' }} />
) : (
<MdPlayArrow style={{ width: '100%', height: '100%' }} />
)}
</IconButton>
<IconButton
{...configButton}
aria-label={'Stop'}
onClick={onStop}
variant="ghost"
>
<MdStop style={{ width: '100%', height: '100%' }} />
</IconButton>
<IconButton
{...configButton}
aria-label={'Previous Media'}
onClick={onNavigatePrevious}
marginLeft="auto"
variant="ghost"
>
<MdNavigateBefore style={{ width: '100%', height: '100%' }} />{' '}
</IconButton>
<IconButton
{...configButton}
aria-label={'jump 15sec in past'}
onClick={onFastRewind}
variant="ghost"
>
<MdFastRewind style={{ width: '100%', height: '100%' }} />
</IconButton>
<IconButton
{...configButton}
aria-label={'jump 15sec in future'}
onClick={onFastForward}
variant="ghost"
>
<MdFastForward style={{ width: '100%', height: '100%' }} />
</IconButton>
<IconButton
{...configButton}
aria-label={'Next Media'}
marginRight="auto"
onClick={onNavigateNext}
variant="ghost"
>
<MdNavigateNext style={{ width: '100%', height: '100%' }} />
</IconButton>
<IconButton
{...configButton}
aria-label={'continue to the end'}
onClick={onTypePlay}
variant="ghost"
>
{playModeIcon[playingMode]}
</IconButton>
<IconButton
{...configButton}
aria-label={'Fullscreen'}
onClick={onFullScreen}
variant="ghost"
>
{!!document.fullscreenElement ?
<MdFullscreenExit style={{ width: '100%', height: '100%' }} />:
<MdFullscreen style={{ width: '100%', height: '100%' }} />}
</IconButton>
{isPiPSupported && !isMobile &&
<IconButton
{...configButton}
aria-label={'Fullscreen'}
onClick={onPictureInPicture}
variant="ghost"
>
{isPiP ? <MdOutlinePictureInPictureAlt style={{ width: '100%', height: '100%' }} /> : <MdPictureInPictureAlt style={{ width: '100%', height: '100%' }} /> }
</IconButton>
}
</Flex>
</>}
</Flex>
</>
</>
)}
</>
);

View File

@ -45,7 +45,7 @@ export const DisplayMediaFull = ({
overflowX="hidden"
onClick={onClick}
>
<Spacer/>
<Spacer />
<Text
as="span"
alignContent="left"
@ -57,6 +57,7 @@ export const DisplayMediaFull = ({
// TODO: noOfLines={1}
color={MediaActive?.id === media.id ? 'green.700' : undefined}
>
{media.id}
{media.name} {media.episode && ` [${media.episode}]`}
</Text>
{/* {dataSeason && (

View File

@ -4,7 +4,7 @@ import { Button, Tabs, Text, useDisclosure } from '@chakra-ui/react';
import { MdAdminPanelSettings, MdDeleteForever, MdEdit } from 'react-icons/md';
import { useNavigate, useParams } from 'react-router-dom';
import { MediaResource, MediaWrite } from '@/back-api';
import { MediaResource, MediaUpdate, ZodMediaUpdate } from '@/back-api';
import { FormGroupShow } from '@/components/form/FormGroup';
import { FormInput } from '@/components/form/FormInput';
import { FormNumber } from '@/components/form/FormNumber';
@ -63,19 +63,24 @@ export const MediaEditPopUp = ({}: MediaEditPopUpProps) => {
};
const initialRef = useRef<HTMLButtonElement>(null);
const finalRef = useRef<HTMLButtonElement>(null);
const form = useFormidable<MediaWrite>({
const form = useFormidable<MediaUpdate>({
initialValues: dataMedia,
deltaConfig: { omit: ['covers'] },
});
const onSave = async (dataDelta: MediaWrite) => {
const onSave = async (data: MediaUpdate) => {
if (isNullOrUndefined(mediaIdInt)) {
return;
}
console.log(`onSave = ${JSON.stringify(dataDelta, null, 2)}`);
console.log(`onSave = ${JSON.stringify(data, null, 2)}`);
const { episode, ...rest } = data;
let episodeNumber: undefined | number = undefined;
if (episode !== undefined && episode !== null) {
episodeNumber = parseInt(`${episode}`, 10);
}
store.update(
MediaResource.patch({
restConfig: session.getRestConfig(),
data: dataDelta,
data: ZodMediaUpdate.parse({ episode: episodeNumber, ...rest }),
params: {
id: mediaIdInt,
},
@ -146,7 +151,7 @@ export const MediaEditPopUp = ({}: MediaEditPopUpProps) => {
>
{/* <DialogOverlay /> */}
<DialogContent>
<Formidable.From form={form} onSubmitDelta={onSave}>
<Formidable.From form={form} onSubmit={onSave}>
<DialogHeader>Edit Media</DialogHeader>
{/* <DialogCloseButton ref={finalRef} /> */}

View File

@ -9,7 +9,7 @@ import {
} from 'react-icons/md';
import { useNavigate, useParams } from 'react-router-dom';
import { SeasonResource, SeasonWrite } from '@/back-api';
import { SeasonResource, SeasonUpdate, ZodSeasonUpdate } from '@/back-api';
import { FormCovers } from '@/components/form/FormCovers';
import { FormGroupShow } from '@/components/form/FormGroup';
import { FormInput } from '@/components/form/FormInput';
@ -66,19 +66,19 @@ export const SeasonEditPopUp = ({}: SeasonEditPopUpProps) => {
};
const initialRef = useRef<HTMLButtonElement>(null);
const finalRef = useRef<HTMLButtonElement>(null);
const form = useFormidable<SeasonWrite>({
const form = useFormidable<SeasonUpdate>({
initialValues: dataSeason,
deltaConfig: { omit: ['covers'] },
});
const onSave = async (deltaData: SeasonWrite) => {
const onSave = async (data: SeasonUpdate) => {
if (isNullOrUndefined(seasonIdInt)) {
return;
}
console.log(`onSave = ${JSON.stringify(deltaData, null, 2)}`);
console.log(`onSave = ${JSON.stringify(data, null, 2)}`);
store.update(
SeasonResource.patch({
restConfig: session.getRestConfig(),
data: deltaData,
data: ZodSeasonUpdate.parse(data),
params: {
id: seasonIdInt,
},
@ -150,7 +150,7 @@ export const SeasonEditPopUp = ({}: SeasonEditPopUpProps) => {
data-testid="Season-edit-pop-up"
>
<DialogContent>
<Formidable.From form={form} onSubmitDelta={onSave}>
<Formidable.From form={form} onSubmit={onSave}>
<DialogHeader>Edit Season</DialogHeader>
{/* <DialogCloseButton ref={finalRef} /> */}

View File

@ -9,7 +9,7 @@ import {
} from 'react-icons/md';
import { useNavigate, useParams } from 'react-router-dom';
import { SeriesResource, SeriesWrite } from '@/back-api';
import { SeriesResource, SeriesUpdate, ZodSeriesUpdate } from '@/back-api';
import { FormCovers } from '@/components/form/FormCovers';
import { FormInput } from '@/components/form/FormInput';
import { FormTextarea } from '@/components/form/FormTextarea';
@ -62,19 +62,19 @@ export const SeriesEditPopUp = ({}: SeriesEditPopUpProps) => {
};
const initialRef = useRef<HTMLButtonElement>(null);
const finalRef = useRef<HTMLButtonElement>(null);
const form = useFormidable<SeriesWrite>({
const form = useFormidable<SeriesUpdate>({
initialValues: dataSeries,
deltaConfig: { omit: ['covers'] },
});
const onSave = async (dataDelta: SeriesWrite) => {
const onSave = async (data: SeriesUpdate) => {
if (isNullOrUndefined(seriesIdInt)) {
return;
}
console.log(`onSave = ${JSON.stringify(dataDelta, null, 2)}`);
console.log(`onSave = ${JSON.stringify(data, null, 2)}`);
store.update(
SeriesResource.patch({
restConfig: session.getRestConfig(),
data: dataDelta,
data: ZodSeriesUpdate.parse(data),
params: {
id: seriesIdInt,
},
@ -146,7 +146,7 @@ export const SeriesEditPopUp = ({}: SeriesEditPopUpProps) => {
>
{/* <DialogOverlay /> */}
<DialogContent>
<Formidable.From form={form} onSubmitDelta={onSave}>
<Formidable.From form={form} onSubmit={onSave}>
<DialogHeader>Edit Series</DialogHeader>
{/* <DialogCloseButton ref={finalRef} /> */}

View File

@ -9,7 +9,7 @@ import {
} from 'react-icons/md';
import { useNavigate, useParams } from 'react-router-dom';
import { TypeResource, TypeWrite } from '@/back-api';
import { TypeResource, TypeUpdate, ZodTypeUpdate } from '@/back-api';
import { FormCovers } from '@/components/form/FormCovers';
import { FormInput } from '@/components/form/FormInput';
import { FormTextarea } from '@/components/form/FormTextarea';
@ -65,19 +65,19 @@ export const TypeEditPopUp = ({}: TypeEditPopUpProps) => {
};
const initialRef = useRef<HTMLButtonElement>(null);
const finalRef = useRef<HTMLButtonElement>(null);
const form = useFormidable<TypeWrite>({
const form = useFormidable<TypeUpdate>({
initialValues: dataType,
deltaConfig: { omit: ['covers'] },
});
const onSave = async (dataDelta: TypeWrite) => {
const onSave = async (data: TypeUpdate) => {
if (isNullOrUndefined(typeIdInt)) {
return;
}
console.log(`onSave = ${JSON.stringify(dataDelta, null, 2)}`);
console.log(`onSave = ${JSON.stringify(data, null, 2)}`);
store.update(
TypeResource.patch({
restConfig: session.getRestConfig(),
data: dataDelta,
data: ZodTypeUpdate.parse(data),
params: {
id: typeIdInt,
},
@ -148,7 +148,7 @@ export const TypeEditPopUp = ({}: TypeEditPopUpProps) => {
>
{/* <DialogOverlay /> */}
<DialogContent>
<Formidable.From form={form} onSubmitDelta={onSave}>
<Formidable.From form={form} onSubmit={onSave}>
<DialogHeader>Edit Type</DialogHeader>
{/* <DialogCloseButton ref={finalRef} /> */}

View File

@ -60,7 +60,6 @@ export const environment = isDevelopmentEnvironment()
? environment_local
: environment_back_prod;
/**
* get the current REST api URL. Depend on the VITE_API_BASE_URL env variable.
* @returns The URL with http(s)://***

View File

@ -408,7 +408,7 @@ export const AddPage = () => {
episode: `${parsedElement[index].episode}`,
};
console.log(`data= ${JSON.stringify(data, null, 2)}`);
console.error('Not_ implemented');
console.error('Not_implemented');
storeMedia
.update(
MediaResource.uploadMedia({
@ -468,6 +468,18 @@ export const AddPage = () => {
);
};
const addNewSeries = (data: string): Promise<Series> => {
if (form.values['typeId'] === undefined) {
return new Promise((_resolve, reject) => {
reject({
name: 'form value is empty [typeId] ...',
time: Date().toString(),
status: 950,
error: 'REST Fail to verify the data',
statusMessage: 'catch ERROR',
message: 'form value is empty [typeId] ...',
} as RestErrorResponse);
});
}
return storeSeries.update(
SeriesResource.post({
restConfig: session.getRestConfig(),
@ -763,7 +775,7 @@ export const MediaDetectionDetail = ({ data }: { data: InspectionType }) => {
</Text>
</Table.Cell>
<Table.Cell>
<Text>{data.media.date}</Text>
<Text>{data.media.datePublication}</Text>
</Table.Cell>
<Table.Cell></Table.Cell>
</Table.Row>

View File

@ -1,47 +1,58 @@
import { useEffect } from 'react';
import { Center, Heading, Image, Text } from '@chakra-ui/react';
import { Center, Flex, Heading, Image, Text } from '@chakra-ui/react';
import { MdFactCheck, MdWarning } from 'react-icons/md';
import { useNavigate, useParams } from 'react-router-dom';
import { useEffectOnce } from 'react-use';
import avatar_generic from '@/assets/images/avatar_generic.svg';
import { Icon } from '@/components';
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
import { TopBar } from '@/components/TopBar/TopBar';
import { isDevelopmentEnvironment } from '@/environment';
import { toaster } from '@/components/ui/toaster';
import { useSessionService } from '@/service/session';
import { b64_to_utf8 } from '@/utils/sso';
export const SSOPage = () => {
const { data, keepConnected, token } = useParams();
console.log(`- data: ${data}`);
console.log(`- keepConnected: ${keepConnected}`);
console.log(`- token: ${token}`);
//const { data, keepConnected, token } = useState<string|undefined>();
// console.log(`- data: ${data}`);
// console.log(`- keepConnected: ${keepConnected}`);
// console.log(`- token: ${token}`);
const navigate = useNavigate();
const { isConnected, setToken, login, clearToken } = useSessionService();
useEffect(() => {
if (token) {
setToken(token);
} else {
clearToken();
const { isConnected, errorSession, setToken, login } = useSessionService();
useEffectOnce(() => {
if (token === undefined || token === '') {
return;
}
}, [token, setToken, clearToken]);
const delay = isDevelopmentEnvironment() ? 2000 : 2000;
try {
setToken(token);
} catch (e) {
toaster.create({
title: 'Connection Fail',
description: `invalid token data model.`,
type: 'error',
});
navigate('/');
}
});
const delay = 1000;
useEffect(() => {
if (isConnected) {
const destination = data ? b64_to_utf8(data) : '/';
console.log(`program redirect to: ${destination} (${delay}ms)`);
toaster.create({
title: 'Connection Succeed',
description: `Welcome back: ${login}.`,
type: 'success',
});
const destination = data ? b64_to_utf8(data) : '/home';
const destinationFinal = destination === '' ? '/home' : destination;
console.log(`program redirect to: '${destinationFinal}' (${delay}ms)`);
setTimeout(() => {
navigate(`/${destination}`);
// note: check if the "navigate" is in the dependence of the use effect!!!
navigate(`/${destinationFinal}`, { replace: true });
}, delay);
}
}, [isConnected]);
/*
const [searchParams] = useSearchParams();
console.log(`data: ${searchParams.get('data')}`);
console.log(`keepConnected: ${searchParams.get('keepConnected')}`);
console.log(`token: ${searchParams.get('token')}`);
const dataFromParam = useGetCreateActionParams();
console.log(`data group: ${JSON.stringify(dataFromParam, null, 2)}`);
*/
}, [isConnected, login, navigate]);
return (
<>
<TopBar />
@ -53,20 +64,52 @@ export const SSOPage = () => {
<Center w="full">
<Image src={avatar_generic} boxSize="150px" borderRadius="full" />
</Center>
{!isConnected && (
<Text>
<b>ERROR: </b> connection fail !
</Text>
{!isConnected && errorSession !== undefined && (
<>
<Flex color="red.500" fontSize="25px" gap={2} marginX="auto">
<Icon sizeIcon="55px">
<MdWarning />
</Icon>
<Text marginY="auto">
<b>ERROR: </b> {errorSession.message}
</Text>
</Flex>
{errorSession.description && (
<Text
whiteSpace="pre-line"
fontSize="25px"
gap={2}
marginX="auto"
>
{errorSession.description}
</Text>
)}
</>
)}
{!isConnected && errorSession === undefined && (
<Flex color="red.500" fontSize="25px" gap={2} marginX="auto">
<Icon sizeIcon="55px">
<MdWarning />
</Icon>
<Text marginY="auto">
<b>ERROR: </b> Not connected !
</Text>
</Flex>
)}
{isConnected && (
<>
<Text>
<b>Connected: </b> Redirect soon!
</Text>
<Text>
<b>Welcome back: </b> {login}
</Text>
</>
<Flex direction="column">
<Flex color="green.500" fontSize="25px" gap={2} marginX="auto">
<MdFactCheck />
<Text marginY="auto">
<b>Connected: </b> Redirect soon!
</Text>
</Flex>
<Flex fontSize="25px" gap={2} marginX="auto">
<Text marginY="auto">
<b>Welcome back: </b> {login}
</Text>
</Flex>
</Flex>
)}
</PageLayoutInfoCenter>
</>

View File

@ -22,7 +22,7 @@ export const useAdvancementServiceWrapped = (
{
restApiName: 'Advancement',
primaryKey: 'id',
available: session.token !== undefined,
available: false, //session.token !== undefined,
getsCall: () => {
return UserMediaAdvancementResource.gets({
restConfig: session.getRestConfig(),
@ -47,40 +47,43 @@ export type updateTimeProps = {
time: number;
total: number;
addCount: boolean;
}
};
export const useAdvancementUpdateTime = (id: number,
config: Omit<useQueryCallProps<UserMediaAdvancement, updateTimeProps>, 'queryFunction'>
= {}) => {
export const useAdvancementUpdateTime = (
id: number,
config: Omit<
useQueryCallProps<UserMediaAdvancement, updateTimeProps>,
'queryFunction'
> = {}
) => {
const { store } = useAdvancementService();
const { onSuccess, ...restConfig } = config;
const { getRestConfig } = useSessionService();
const { call, ...rest } = useQueryCall<
UserMediaAdvancement,
updateTimeProps
>({
queryFunction: (inputData) => {
const data = {
time: inputData.time,
percent: inputData.time / inputData.total,
addCount: inputData.addCount
};
return UserMediaAdvancementResource.patch({
restConfig: getRestConfig(),
params: {
id
},
data
});
},
onSuccess: (response) => {
if (onSuccess) {
onSuccess(response);
}
store.updateRaw(response);
},
...restConfig,
});
const { call, ...rest } = useQueryCall<UserMediaAdvancement, updateTimeProps>(
{
queryFunction: (inputData) => {
const data = {
time: inputData.time,
percent: inputData.time / inputData.total,
addCount: inputData.addCount,
};
return UserMediaAdvancementResource.patch({
restConfig: getRestConfig(),
params: {
id,
},
data,
});
},
onSuccess: (response) => {
if (onSuccess) {
onSuccess(response);
}
store.updateRaw(response);
},
...restConfig,
}
);
return { updateTime: call, ...rest };
};

View File

@ -24,7 +24,7 @@ export const useMediaServiceWrapped = (
{
restApiName: 'Media',
primaryKey: 'id',
available: session.token !== undefined,
available: session.isConnected,
getsCall: () => {
return MediaResource.gets({
restConfig: session.getRestConfig(),

View File

@ -25,7 +25,7 @@ export const useSeasonServiceWrapped = (
{
restApiName: 'Season',
primaryKey: 'id',
available: session.token !== undefined,
available: session.isConnected,
getsCall: () => {
return SeasonResource.gets({
restConfig: session.getRestConfig(),

View File

@ -26,7 +26,7 @@ export const useSeriesServiceWrapped = (
{
restApiName: 'Series',
primaryKey: 'id',
available: session.token !== undefined,
available: session.isConnected,
getsCall: () => {
return SeriesResource.gets({
restConfig: session.getRestConfig(),

View File

@ -25,7 +25,7 @@ export const useTypeServiceWrapped = (
{
restApiName: 'Type',
primaryKey: 'id',
available: session.token !== undefined,
available: session.isConnected,
getsCall: () => {
return TypeResource.gets({
restConfig: session.getRestConfig(),

View File

@ -1,21 +1,33 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { JwtPayload, RESTConfig } from '@/back-api';
import { createListCollection } from '@chakra-ui/react';
import {
JwtToken,
RESTConfig,
RestErrorResponse,
UserResource,
setErrorApiGlobalCallback,
} from '@/back-api';
import { PartRight } from '@/back-api/model/part-right';
import { toaster } from '@/components/ui/toaster';
import { environment, getApiUrl } from '@/environment';
import { useServiceContext } from '@/service/ServiceContext';
import { isBrowser } from '@/utils/layout';
import { parseToken } from '@/utils/sso';
import { createListCollection } from '@chakra-ui/react';
const TOKEN_KEY = 'karideo-token-key-storage';
const TOKEN_KEY = 'karusic-token-key-storage';
export const USERS_COLLECTION = createListCollection({
items: [
{ label: "admin", value: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxIiwiYXBwbGljYXRpb24iOiJrYXJ1c2ljIiwiaXNzIjoiS2FyQXV0aCIsInJpZ2h0Ijp7ImthcnVzaWMiOnsiQURNSU4iOnRydWUsIlVTRVIiOnRydWV9fSwibG9naW4iOiJIZWVyb1l1aSIsImV4cCI6MTcyNDIwNjc5NCwiaWF0IjoxNzI0MTY2ODM0fQ.TEST_SIGNATURE_FOR_LOCAL_TEST_AND_TEST_E2E" },
{ label: "NO_USER", value: "svelte" },
{
label: 'admin',
value:
'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxIiwiYXBwbGljYXRpb24iOiJrYXJ1c2ljIiwiaXNzIjoiS2FyQXV0aCIsInJpZ2h0Ijp7ImthcnVzaWMiOnsiQURNSU4iOnRydWUsIlVTRVIiOnRydWV9fSwibG9naW4iOiJIZWVyb1l1aSIsImV4cCI6MTcyNDIwNjc5NCwiaWF0IjoxNzI0MTY2ODM0fQ.TEST_SIGNATURE_FOR_LOCAL_TEST_AND_TEST_E2E',
},
{ label: 'NO_USER', value: 'svelte' },
],
})
});
export const USERS = {
admin:
'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxIiwiYXBwbGljYXRpb24iOiJrYXJ1c2ljIiwiaXNzIjoiS2FyQXV0aCIsInJpZ2h0Ijp7ImthcnVzaWMiOnsiQURNSU4iOnRydWUsIlVTRVIiOnRydWV9fSwibG9naW4iOiJIZWVyb1l1aSIsImV4cCI6MTcyNDIwNjc5NCwiaWF0IjoxNzI0MTY2ODM0fQ.TEST_SIGNATURE_FOR_LOCAL_TEST_AND_TEST_E2E',
@ -34,9 +46,43 @@ export function getRestConfig(): RESTConfig {
};
}
const getDeltaPrint = (date: Date): string => {
const now = new Date();
let deltaSeconds = Math.floor((date.getTime() - now.getTime()) / 1000);
const isPast = deltaSeconds < 0;
deltaSeconds = Math.abs(deltaSeconds);
const days = Math.floor(deltaSeconds / 86400);
deltaSeconds %= 86400;
const hours = Math.floor(deltaSeconds / 3600);
deltaSeconds %= 3600;
const minutes = Math.floor(deltaSeconds / 60);
const seconds = deltaSeconds % 60;
const result = [days > 0 ? `${days}j` : '', `${hours}h${minutes}:${seconds}s`]
.filter(Boolean)
.join(' ');
return `${result}${isPast ? ' ago' : ''}`;
};
const isInThePast = (date: Date): boolean => {
const now = new Date();
return date.getTime() < now.getTime();
};
export type SessionErrorType = {
message: string;
description?: string;
};
export type SessionServiceProps = {
token?: string;
isConnected: boolean;
errorSession?: SessionErrorType;
setToken: (token: string) => void;
clearToken: () => void;
login?: string;
@ -51,102 +97,165 @@ export const useSessionService = (): SessionServiceProps => {
};
export const useSessionServiceWrapped = (): SessionServiceProps => {
const [token, setToken] = useState<string | undefined>(
// Load the token from the system (init)
const [tokenStorage, setTokenStorage] = useState<string | undefined>(
isBrowser ? (localStorage.getItem(TOKEN_KEY) ?? undefined) : undefined
);
const [config, setConfig] = useState<JwtPayload | undefined>(undefined);
// Token that is ready to use
const [tokenStr, setTokenStr] = useState<string>('');
const [errorSession, setErrorSession] = useState<
SessionErrorType | undefined
>(undefined);
const [token, setToken] = useState<JwtToken | undefined>(undefined);
const updateRight = useCallback(() => {
const getRestConfigLocal = useCallback((): RESTConfig => {
return {
server: getApiUrl(),
token: tokenStr,
};
}, [tokenStr]);
console.log('Detect a new token...');
setConfig(undefined);
if (token === undefined) {
console.log(` ==> No User`);
localStorage.removeItem(TOKEN_KEY);
} else {
const dataParsedToken = parseToken(token)?.payload;
console.log(`Get right: ${JSON.stringify(dataParsedToken, null, 2)}`);
setConfig(dataParsedToken);
console.log(' ==> Login ... (try to keep right)');
if (isBrowser) {
localStorage.setItem(TOKEN_KEY, token);
const updateRight = useEffect(() => {
//console.log('Detect a new token...');
if (isBrowser) {
//console.log('update internal property');
if (tokenStorage === undefined) {
//console.log(` ==> No User`);
setToken(undefined);
setTokenStr('');
localStorage.removeItem(TOKEN_KEY);
} else {
//console.log(' ==> Login ... (try to keep right)');
let tokenParsed: JwtToken | undefined = undefined;
try {
tokenParsed = parseToken(tokenStorage);
} catch (e) {
setErrorSession({
message: 'Fail to parse the token',
});
return;
}
//console.log(`get new token: ${JSON.stringify(tokenParsed, null, 2)}`);
const exp = new Date(tokenParsed.payload.exp * 1000);
if (isInThePast(exp)) {
//console.log(`token expired at: exp: ${exp.toISOString()}: delta=${getDeltaPrint(exp)}`);
const iat = new Date(tokenParsed.payload.iat * 1000);
//console.log(`iat: ${iat.toISOString()}: delta=${getDeltaPrint(iat)}`);
setErrorSession({
message: 'The inserted token has expired',
description: `It expired at ${exp.toISOString()}\nSince: ${getDeltaPrint(exp)}`,
});
return;
}
// Validate token on the server:
UserResource.getMe({
restConfig: {
server: getApiUrl(),
token: tokenStorage,
},
})
.then((_response) => {
// if the login work well, then the token does not fail with authentication
//console.log('Authentication finished: ...');
setTokenStr(tokenStorage);
setToken(tokenParsed);
localStorage.setItem(TOKEN_KEY, tokenStorage);
})
.catch((error: RestErrorResponse) => {
setErrorSession({
message: 'The server reject the token.',
description: `name: ${error.name}\nmessage: "${error.message}"`,
});
setTokenStr('');
setToken(undefined);
localStorage.removeItem(TOKEN_KEY);
});
}
}
}, [localStorage, parseToken, token]);
}, [
localStorage,
parseToken,
setErrorSession,
setToken,
setTokenStr,
tokenStorage,
]);
const setTokenLocal = useCallback(
(token: string) => {
setToken(token);
updateRight();
(token?: string) => {
if (token ? token.startsWith('__') : false) {
token = undefined;
}
localStorage.removeItem(TOKEN_KEY);
setTokenStorage(token);
setTokenStr('');
setToken(undefined);
setErrorSession(undefined);
},
[updateRight, setToken]
);
const clearToken = useCallback(() => {
console.log('Clear Token');
localStorage.removeItem(TOKEN_KEY);
setTokenLocal(undefined);
setToken(undefined);
updateRight();
}, [updateRight, setToken]);
const hasReadRight = useCallback(
(group: RightGroup) => {
if (token === undefined) {
return false;
}
const right = config?.right[environment.applName];
if (right === undefined) {
return false;
}
return [PartRight.READ, PartRight.READ_WRITE].includes(
right?.[group] ?? 0
);
},
[config]
);
const hasWriteRight = useCallback(
(group: RightGroup) => {
if (token === undefined) {
return false;
}
const right = config?.right[environment.applName];
if (right === undefined) {
return false;
}
return [PartRight.READ, PartRight.READ_WRITE].includes(
right?.[group] ?? 0
);
},
[config]
);
const getRestConfig = useCallback((): RESTConfig => {
return {
server: getApiUrl(),
token: token ?? '',
};
}, [token]);
useEffect(() => {
updateRight();
}, [updateRight]);
setErrorApiGlobalCallback((response: Response) => {
if (response.status == 401) {
toaster.create({
title: 'API request rejected',
description: `API Authentication rejected by server.`,
type: 'error',
});
clearToken();
}
});
}, [clearToken]);
const hasReadRight = useCallback(
(part: RightGroup) => {
//console.log(`right = ${JSON.stringify(token?.payload?.right?.[environment.applName], null, 2)}`);
const right = token?.payload?.right?.[environment.applName];
if (right === undefined) {
return false;
}
return [PartRight.READ, PartRight.READ_WRITE].includes(right[part]);
},
[token]
);
const hasWriteRight = useCallback(
(part: RightGroup) => {
const right = token?.payload?.right?.[environment.applName];
if (right === undefined) {
return false;
}
return [PartRight.READ, PartRight.READ_WRITE].includes(right[part]);
},
[token]
);
return {
token,
token: tokenStr,
isConnected: token !== undefined,
errorSession,
setToken: setTokenLocal,
clearToken,
login: config?.login,
login: token?.payload?.login,
hasReadRight,
hasWriteRight,
getRestConfig,
getRestConfig: getRestConfigLocal,
};
};
export const useHasRight = (group: RightGroup) => {
export const useHasRight = (part: RightGroup) => {
const { token, hasReadRight, hasWriteRight } = useSessionService();
const isReadable = useMemo(() => {
console.log(`get is read for: ${group} ==> ${hasReadRight(group)}`);
return hasReadRight(group);
}, [token, hasReadRight, group]);
//console.log(`get is read for: ${part} ==> ${hasReadRight(part)}`);
return hasReadRight(part);
}, [token, hasReadRight, part]);
const isWritable = useMemo(() => {
return hasWriteRight(group);
}, [token, hasWriteRight, group]);
return hasWriteRight(part);
}, [token, hasWriteRight, part]);
return { isReadable, isWritable };
};

View File

@ -9,12 +9,20 @@ import { colors } from './colors';
const baseTheme: SystemConfig = {
globalCss: {
':root': {
// permit to "firefox" not reload the page when scroll on top
overscrollBehavior: 'contain',
},
body: {
// permit to "chromium" not reload the page when scroll on top
overscrollBehavior: 'contain',
// Prevents text selection
userSelect: 'none',
// Other ...
overflowY: 'none',
bg: { _light: 'back.50', _dark: 'back.700' },
color: { _light: 'text.900', _dark: 'text.50' },
fontFamily: 'Roboto, Helvetica, Arial, "sans-serif"',
userSelect: 'none' /* Prevents text selection */,
},
svg: {
width: '32px',

View File

@ -1,10 +0,0 @@
import { RESTConfig } from '@/back-api';
import { getApiUrl } from '@/environment';
import { getUserToken } from '@/service/session';
export function getRestConfig(): RESTConfig {
return {
server: getApiUrl(),
token: getUserToken() ?? '',
};
}

View File

@ -108,14 +108,14 @@ export function requestSignIn(name?: string): void {
console.log(
`Request sign-in: '${environment.ssoSignIn}' + '${hashLocalData(name)}'`
);
window.location.href = environment.ssoSignIn + hashLocalData(name) + "/";
window.location.href = environment.ssoSignIn + hashLocalData(name) + '/';
}
/**
* Request SSO Disconnect
*/
export function requestSignOut(name?: string): void {
const url = environment.ssoSignOut + hashLocalData(name);
console.log(`Request just to the SSO: ${url}`)
console.log(`Request just to the SSO: ${url}`);
// unlog from the SSO
window.location.href = url;
}
@ -124,7 +124,7 @@ export function requestSignOut(name?: string): void {
*/
export function requestSignUp(name?: string): void {
const url = environment.ssoSignUp + hashLocalData(name);
console.log(`Request just to the SSO: ${url}`)
console.log(`Request just to the SSO: ${url}`);
// unlog from the SSO
window.location.href = url;
}