[DEV] add checker

This commit is contained in:
Edouard DUPIN 2024-09-19 21:22:35 +02:00
parent 725cd54d92
commit e9af64405b
13 changed files with 425 additions and 215 deletions

View File

@ -20,7 +20,7 @@
<dependency> <dependency>
<groupId>kangaroo-and-rabbit</groupId> <groupId>kangaroo-and-rabbit</groupId>
<artifactId>archidata</artifactId> <artifactId>archidata</artifactId>
<version>0.14.2</version> <version>0.14.3-SNAPSHOT</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>

View File

@ -12,8 +12,10 @@ import org.kar.archidata.annotation.TypeScriptProgress;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.addOn.AddOnDataJson; import org.kar.archidata.dataAccess.addOn.AddOnDataJson;
import org.kar.archidata.dataAccess.addOn.AddOnManyToMany; import org.kar.archidata.dataAccess.addOn.AddOnManyToMany;
import org.kar.archidata.dataAccess.options.CheckFunction;
import org.kar.archidata.tools.DataTools; import org.kar.archidata.tools.DataTools;
import org.kar.karusic.model.Album; import org.kar.karusic.model.Album;
import org.kar.karusic.model.Album.AlbumChecker;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -33,6 +35,7 @@ import jakarta.ws.rs.core.MediaType;
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
public class AlbumResource { public class AlbumResource {
private static final Logger LOGGER = LoggerFactory.getLogger(AlbumResource.class); private static final Logger LOGGER = LoggerFactory.getLogger(AlbumResource.class);
static final AlbumChecker CHECKER = new AlbumChecker();
@GET @GET
@Path("{id}") @Path("{id}")
@ -53,8 +56,8 @@ public class AlbumResource {
@RolesAllowed("ADMIN") @RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@Operation(description = "Add an album (when all the data already exist)") @Operation(description = "Add an album (when all the data already exist)")
public Album post(@AsyncType(Album.class) final String jsonRequest) throws Exception { public Album post(final Album data) throws Exception {
return DataAccess.insertWithJson(Album.class, jsonRequest); return DataAccess.insert(data, new CheckFunction(CHECKER));
} }
@PATCH @PATCH
@ -63,7 +66,7 @@ public class AlbumResource {
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@Operation(description = "Update a specific album") @Operation(description = "Update a specific album")
public Album patch(@PathParam("id") final Long id, @AsyncType(Album.class) final String jsonRequest) throws Exception { public Album patch(@PathParam("id") final Long id, @AsyncType(Album.class) final String jsonRequest) throws Exception {
DataAccess.updateWithJson(Album.class, id, jsonRequest); DataAccess.updateWithJson(Album.class, id, jsonRequest, new CheckFunction(CHECKER));
return DataAccess.get(Album.class, id); return DataAccess.get(Album.class, id);
} }

View File

@ -11,8 +11,10 @@ import org.kar.archidata.annotation.FormDataOptional;
import org.kar.archidata.annotation.TypeScriptProgress; import org.kar.archidata.annotation.TypeScriptProgress;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.addOn.AddOnDataJson; import org.kar.archidata.dataAccess.addOn.AddOnDataJson;
import org.kar.archidata.dataAccess.options.CheckFunction;
import org.kar.archidata.tools.DataTools; import org.kar.archidata.tools.DataTools;
import org.kar.karusic.model.Artist; import org.kar.karusic.model.Artist;
import org.kar.karusic.model.Artist.ArtistChecker;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -31,6 +33,7 @@ import jakarta.ws.rs.core.MediaType;
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
public class ArtistResource { public class ArtistResource {
private static final Logger LOGGER = LoggerFactory.getLogger(ArtistResource.class); private static final Logger LOGGER = LoggerFactory.getLogger(ArtistResource.class);
static final ArtistChecker CHECKER = new ArtistChecker();
@GET @GET
@Path("{id}") @Path("{id}")
@ -48,8 +51,8 @@ public class ArtistResource {
@POST @POST
@RolesAllowed("ADMIN") @RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
public Artist post(@AsyncType(Artist.class) final String jsonRequest) throws Exception { public Artist post(final Artist data) throws Exception {
return DataAccess.insertWithJson(Artist.class, jsonRequest); return DataAccess.insert(data, new CheckFunction(CHECKER));
} }
@PATCH @PATCH
@ -57,7 +60,7 @@ public class ArtistResource {
@RolesAllowed("ADMIN") @RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
public Artist patch(@PathParam("id") final Long id, @AsyncType(Artist.class) final String jsonRequest) throws Exception { public Artist patch(@PathParam("id") final Long id, @AsyncType(Artist.class) final String jsonRequest) throws Exception {
DataAccess.updateWithJson(Artist.class, id, jsonRequest); DataAccess.updateWithJson(Artist.class, id, jsonRequest, new CheckFunction(CHECKER));
return DataAccess.get(Artist.class, id); return DataAccess.get(Artist.class, id);
} }

View File

@ -11,8 +11,10 @@ import org.kar.archidata.annotation.FormDataOptional;
import org.kar.archidata.annotation.TypeScriptProgress; import org.kar.archidata.annotation.TypeScriptProgress;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.addOn.AddOnDataJson; import org.kar.archidata.dataAccess.addOn.AddOnDataJson;
import org.kar.archidata.dataAccess.options.CheckFunction;
import org.kar.archidata.tools.DataTools; import org.kar.archidata.tools.DataTools;
import org.kar.karusic.model.Gender; import org.kar.karusic.model.Gender;
import org.kar.karusic.model.Gender.GenderChecker;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -31,6 +33,7 @@ import jakarta.ws.rs.core.MediaType;
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
public class GenderResource { public class GenderResource {
private static final Logger LOGGER = LoggerFactory.getLogger(GenderResource.class); private static final Logger LOGGER = LoggerFactory.getLogger(GenderResource.class);
static final GenderChecker CHECKER = new GenderChecker();
@GET @GET
@Path("{id}") @Path("{id}")
@ -48,8 +51,8 @@ public class GenderResource {
@POST @POST
@RolesAllowed("ADMIN") @RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
public Gender post(@AsyncType(Gender.class) final String jsonRequest) throws Exception { public Gender post(final Gender data) throws Exception {
return DataAccess.insertWithJson(Gender.class, jsonRequest); return DataAccess.insert(data, new CheckFunction(CHECKER));
} }
@PATCH @PATCH
@ -57,7 +60,7 @@ public class GenderResource {
@RolesAllowed("ADMIN") @RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
public Gender patch(@PathParam("id") final Long id, @AsyncType(Gender.class) final String jsonRequest) throws Exception { public Gender patch(@PathParam("id") final Long id, @AsyncType(Gender.class) final String jsonRequest) throws Exception {
DataAccess.updateWithJson(Gender.class, id, jsonRequest); DataAccess.updateWithJson(Gender.class, id, jsonRequest, new CheckFunction(CHECKER));
return DataAccess.get(Gender.class, id); return DataAccess.get(Gender.class, id);
} }

View File

@ -10,8 +10,10 @@ import org.kar.archidata.annotation.AsyncType;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.addOn.AddOnDataJson; import org.kar.archidata.dataAccess.addOn.AddOnDataJson;
import org.kar.archidata.dataAccess.addOn.AddOnManyToMany; import org.kar.archidata.dataAccess.addOn.AddOnManyToMany;
import org.kar.archidata.dataAccess.options.CheckFunction;
import org.kar.archidata.tools.DataTools; import org.kar.archidata.tools.DataTools;
import org.kar.karusic.model.Playlist; import org.kar.karusic.model.Playlist;
import org.kar.karusic.model.Track.TrackChecker;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -30,6 +32,7 @@ import jakarta.ws.rs.core.MediaType;
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
public class PlaylistResource { public class PlaylistResource {
private static final Logger LOGGER = LoggerFactory.getLogger(PlaylistResource.class); private static final Logger LOGGER = LoggerFactory.getLogger(PlaylistResource.class);
static final TrackChecker CHECKER = new TrackChecker();
@GET @GET
@Path("{id}") @Path("{id}")
@ -47,8 +50,8 @@ public class PlaylistResource {
@POST @POST
@RolesAllowed("ADMIN") @RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
public Playlist post(@AsyncType(Playlist.class) final String jsonRequest) throws Exception { public Playlist post(final Playlist data) throws Exception {
return DataAccess.insertWithJson(Playlist.class, jsonRequest); return DataAccess.insert(data, new CheckFunction(CHECKER));
} }
@PATCH @PATCH
@ -56,7 +59,7 @@ public class PlaylistResource {
@RolesAllowed("ADMIN") @RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
public Playlist patch(@PathParam("id") final Long id, @AsyncType(Playlist.class) final String jsonRequest) throws Exception { public Playlist patch(@PathParam("id") final Long id, @AsyncType(Playlist.class) final String jsonRequest) throws Exception {
DataAccess.updateWithJson(Playlist.class, id, jsonRequest); DataAccess.updateWithJson(Playlist.class, id, jsonRequest, new CheckFunction(CHECKER));
return DataAccess.get(Playlist.class, id); return DataAccess.get(Playlist.class, id);
} }

View File

@ -15,9 +15,11 @@ import org.kar.archidata.annotation.TypeScriptProgress;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.addOn.AddOnDataJson; import org.kar.archidata.dataAccess.addOn.AddOnDataJson;
import org.kar.archidata.dataAccess.addOn.AddOnManyToMany; import org.kar.archidata.dataAccess.addOn.AddOnManyToMany;
import org.kar.archidata.dataAccess.options.CheckFunction;
import org.kar.archidata.model.Data; import org.kar.archidata.model.Data;
import org.kar.archidata.tools.DataTools; import org.kar.archidata.tools.DataTools;
import org.kar.karusic.model.Track; import org.kar.karusic.model.Track;
import org.kar.karusic.model.Track.TrackChecker;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -37,6 +39,7 @@ import jakarta.ws.rs.core.Response;
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
public class TrackResource { public class TrackResource {
private static final Logger LOGGER = LoggerFactory.getLogger(TrackResource.class); private static final Logger LOGGER = LoggerFactory.getLogger(TrackResource.class);
static final TrackChecker CHECKER = new TrackChecker();
@GET @GET
@Path("{id}") @Path("{id}")
@ -54,17 +57,16 @@ public class TrackResource {
@POST @POST
@RolesAllowed("ADMIN") @RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
public Track post(@AsyncType(Track.class) final String jsonRequest) throws Exception { public Track post(final Track data) throws Exception {
return DataAccess.insertWithJson(Track.class, jsonRequest); return DataAccess.insert(data, new CheckFunction(CHECKER));
} }
@PATCH @PATCH
@Path("{id}") @Path("{id}")
@RolesAllowed("ADMIN") @RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
public Track patch(@PathParam("id") final Long id, @AsyncType(Track.class) final String jsonRequest) public Track patch(@PathParam("id") final Long id, @AsyncType(Track.class) final String jsonRequest) throws Exception {
throws Exception { DataAccess.updateWithJson(Track.class, id, jsonRequest, new CheckFunction(CHECKER));
DataAccess.updateWithJson(Track.class, id, jsonRequest);
return DataAccess.get(Track.class, id); return DataAccess.get(Track.class, id);
} }
@ -87,8 +89,7 @@ public class TrackResource {
@DELETE @DELETE
@Path("{id}/artist/{trackId}") @Path("{id}/artist/{trackId}")
@RolesAllowed("ADMIN") @RolesAllowed("ADMIN")
public Track removeTrack(@PathParam("id") final Long id, @PathParam("artistId") final Long artistId) public Track removeTrack(@PathParam("id") final Long id, @PathParam("artistId") final Long artistId) throws Exception {
throws Exception {
AddOnManyToMany.removeLink(Track.class, id, "artist", artistId); AddOnManyToMany.removeLink(Track.class, id, "artist", artistId);
return DataAccess.get(Track.class, id); return DataAccess.get(Track.class, id);
} }
@ -111,8 +112,7 @@ public class TrackResource {
@DELETE @DELETE
@Path("{id}/cover/{coverId}") @Path("{id}/cover/{coverId}")
@RolesAllowed("ADMIN") @RolesAllowed("ADMIN")
public Track removeCover(@PathParam("id") final Long id, @PathParam("coverId") final UUID coverId) public Track removeCover(@PathParam("id") final Long id, @PathParam("coverId") final UUID coverId) throws Exception {
throws Exception {
AddOnDataJson.removeLink(Track.class, id, "covers", coverId); AddOnDataJson.removeLink(Track.class, id, "covers", coverId);
return DataAccess.get(Track.class, id); return DataAccess.get(Track.class, id);
} }
@ -121,7 +121,6 @@ public class TrackResource {
@Path("upload/") @Path("upload/")
@RolesAllowed("ADMIN") @RolesAllowed("ADMIN")
@Consumes({ MediaType.MULTIPART_FORM_DATA }) @Consumes({ MediaType.MULTIPART_FORM_DATA })
// Formatter:off
@AsyncType(Track.class) @AsyncType(Track.class)
@TypeScriptProgress @TypeScriptProgress
public Response uploadTrack( // public Response uploadTrack( //
@ -133,7 +132,6 @@ public class TrackResource {
@FormDataParam("file") final InputStream fileInputStream, // @FormDataParam("file") final InputStream fileInputStream, //
@FormDataParam("file") final FormDataContentDisposition fileMetaData // @FormDataParam("file") final FormDataContentDisposition fileMetaData //
) { ) {
// Formatter:on
try { try {
// correct input string stream : // correct input string stream :
trackId = DataTools.multipartCorrection(trackId); trackId = DataTools.multipartCorrection(trackId);
@ -190,7 +188,7 @@ public class TrackResource {
trackElem.artists = new ArrayList<>(); trackElem.artists = new ArrayList<>();
trackElem.artists.add(artistId != null ? Long.parseLong(artistId) : null); trackElem.artists.add(artistId != null ? Long.parseLong(artistId) : null);
} }
trackElem = DataAccess.insert(trackElem); trackElem = DataAccess.insert(trackElem, new CheckFunction(CHECKER));
/* Old mode of artist insertion (removed due to the slowlest request of getting value if (artistElem != null) { DataAccess.addLink(Track.class, trackElem.id, "artist", artistElem.id); } */ /* Old mode of artist insertion (removed due to the slowlest request of getting value if (artistElem != null) { DataAccess.addLink(Track.class, trackElem.id, "artist", artistElem.id); } */
return Response.ok(trackElem).build(); return Response.ok(trackElem).build();
} catch (final Exception ex) { } catch (final Exception ex) {

View File

@ -6,6 +6,7 @@ import java.util.UUID;
import org.kar.archidata.annotation.DataIfNotExists; import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.annotation.DataJson; import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.dataAccess.options.CheckJPA;
import org.kar.archidata.model.Data; import org.kar.archidata.model.Data;
import org.kar.archidata.model.GenericDataSoftDelete; import org.kar.archidata.model.GenericDataSoftDelete;
@ -20,6 +21,12 @@ import jakarta.persistence.Table;
@DataIfNotExists @DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public class Album extends GenericDataSoftDelete { public class Album extends GenericDataSoftDelete {
public static class AlbumChecker extends CheckJPA<Album> {
public AlbumChecker() {
super(Album.class);
}
}
@Column(length = 256) @Column(length = 256)
public String name = null; public String name = null;
@Column(length = 0) @Column(length = 0)

View File

@ -6,6 +6,7 @@ import java.util.UUID;
import org.kar.archidata.annotation.DataIfNotExists; import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.annotation.DataJson; import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.dataAccess.options.CheckJPA;
import org.kar.archidata.model.Data; import org.kar.archidata.model.Data;
import org.kar.archidata.model.GenericDataSoftDelete; import org.kar.archidata.model.GenericDataSoftDelete;
@ -20,6 +21,12 @@ import jakarta.persistence.Table;
@DataIfNotExists @DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public class Artist extends GenericDataSoftDelete { public class Artist extends GenericDataSoftDelete {
public static class ArtistChecker extends CheckJPA<Artist> {
public ArtistChecker() {
super(Artist.class);
}
}
@Column(length = 256) @Column(length = 256)
public String name = null; public String name = null;
@Column(length = 0) @Column(length = 0)

View File

@ -17,6 +17,7 @@ import java.util.UUID;
import org.kar.archidata.annotation.DataIfNotExists; import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.annotation.DataJson; import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.dataAccess.options.CheckJPA;
import org.kar.archidata.model.Data; import org.kar.archidata.model.Data;
import org.kar.archidata.model.GenericDataSoftDelete; import org.kar.archidata.model.GenericDataSoftDelete;
@ -31,6 +32,12 @@ import jakarta.persistence.Table;
@DataIfNotExists @DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public class Gender extends GenericDataSoftDelete { public class Gender extends GenericDataSoftDelete {
public static class GenderChecker extends CheckJPA<Gender> {
public GenderChecker() {
super(Gender.class);
}
}
@Column(length = 256) @Column(length = 256)
public String name = null; public String name = null;
@Column(length = 0) @Column(length = 0)

View File

@ -17,6 +17,7 @@ import java.util.UUID;
import org.kar.archidata.annotation.DataIfNotExists; import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.annotation.DataJson; import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.dataAccess.options.CheckJPA;
import org.kar.archidata.model.Data; import org.kar.archidata.model.Data;
import org.kar.archidata.model.GenericDataSoftDelete; import org.kar.archidata.model.GenericDataSoftDelete;
@ -33,6 +34,12 @@ import jakarta.persistence.Table;
@DataIfNotExists @DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public class Playlist extends GenericDataSoftDelete { public class Playlist extends GenericDataSoftDelete {
public static class PlaylistChecker extends CheckJPA<Playlist> {
public PlaylistChecker() {
super(Playlist.class);
}
}
@Column(length = 256) @Column(length = 256)
public String name = null; public String name = null;
@Column(length = 0) @Column(length = 0)

View File

@ -17,6 +17,7 @@ import java.util.UUID;
import org.kar.archidata.annotation.DataIfNotExists; import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.annotation.DataJson; import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.dataAccess.options.CheckJPA;
import org.kar.archidata.model.Data; import org.kar.archidata.model.Data;
import org.kar.archidata.model.GenericDataSoftDelete; import org.kar.archidata.model.GenericDataSoftDelete;
@ -31,6 +32,13 @@ import jakarta.persistence.Table;
@DataIfNotExists @DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public class Track extends GenericDataSoftDelete { public class Track extends GenericDataSoftDelete {
public static class TrackChecker extends CheckJPA<Track> {
public TrackChecker() {
super(Track.class);
}
}
@Column(length = 256) @Column(length = 256)
public String name = null; public String name = null;
@Column(length = 0) @Column(length = 0)

View File

@ -86,10 +86,9 @@ export const useFormidable = <TYPE extends object = object>({
setIsModify, setIsModify,
setIsFormModified, setIsFormModified,
]); ]);
const reset = useCallback(() => { const restoreValues = useCallback(() => {
setValues({ ...initialData }); setValues({ ...initialData });
}, }, [setValues, initialData]);
[setValues, initialData]);
const setValuesExternal = useCallback( const setValuesExternal = useCallback(
(data: object) => { (data: object) => {
//console.log(`FORMIDABLE: setValuesExternal(${JSON.stringify(data)}) ==> keys=${Object.keys(data)}`); //console.log(`FORMIDABLE: setValuesExternal(${JSON.stringify(data)}) ==> keys=${Object.keys(data)}`);
@ -158,7 +157,7 @@ export const useFormidable = <TYPE extends object = object>({
getDeltaData, getDeltaData,
isFormModified, isFormModified,
isModify, isModify,
reset, restoreValues,
restoreValue, restoreValue,
setValues: setValuesExternal, setValues: setValuesExternal,
values, values,

View File

@ -1,26 +1,51 @@
import { Album, AlbumResource, Artist, ArtistResource, Gender, GenderResource, RestErrorResponse, Track, TrackResource } from '@/back-api'; import { useState } from 'react';
import { useFormidable } from '@/components/form/Formidable';
import {
Button,
Flex,
Input,
Table,
TableCaption,
TableContainer,
Tbody,
Td,
Text,
Tfoot,
Th,
Thead,
Tr,
} from '@chakra-ui/react';
import { LuTrash } from 'react-icons/lu';
import { MdCloudUpload, MdRemove } from 'react-icons/md';
import {
Album,
AlbumResource,
Artist,
ArtistResource,
Gender,
GenderResource,
RestErrorResponse,
Track,
TrackResource,
} from '@/back-api';
import { PageLayout } from '@/components/Layout/PageLayout';
import { TopBar } from '@/components/TopBar/TopBar';
import { FormInput } from '@/components/form/FormInput'; import { FormInput } from '@/components/form/FormInput';
import { FormSelect } from '@/components/form/FormSelect'; import { FormSelect } from '@/components/form/FormSelect';
import { PageLayout } from '@/components/Layout/PageLayout'; import { useFormidable } from '@/components/form/Formidable';
import { PopUpUploadProgress } from '@/components/popup/PopUpUploadProgress'; import { PopUpUploadProgress } from '@/components/popup/PopUpUploadProgress';
import { TopBar } from '@/components/TopBar/TopBar';
import { useAlbumService, useOrderedAlbums } from '@/service/Album'; import { useAlbumService, useOrderedAlbums } from '@/service/Album';
import { useArtistService, useOrderedArtists } from '@/service/Artist'; import { useArtistService, useOrderedArtists } from '@/service/Artist';
import { useGenderService, useOrderedGenders } from '@/service/Gender'; import { useGenderService, useOrderedGenders } from '@/service/Gender';
import { useServiceContext } from '@/service/ServiceContext'; import { useServiceContext } from '@/service/ServiceContext';
import { isNullOrUndefined } from '@/utils/validator'; import { isNullOrUndefined } from '@/utils/validator';
import { Flex, Text, Input, Table, TableCaption, TableContainer, Tbody, Td, Tfoot, Th, Thead, Tr, Button } from '@chakra-ui/react';
import { useState } from 'react';
import { LuTrash } from 'react-icons/lu';
import { MdCloudUpload, MdRemove } from 'react-icons/md';
export class ElementList { export class ElementList {
constructor( constructor(
public id?: number, public id?: number,
public label?: string) { public label?: string
) {
// nothing to do. // nothing to do.
} }
} }
@ -33,7 +58,8 @@ export class FileParsedElement {
public title: string, public title: string,
public artist?: string, public artist?: string,
public album?: string, public album?: string,
public trackId?: number) { public trackId?: number
) {
// nothing to do. // nothing to do.
} }
} }
@ -41,7 +67,8 @@ export class FileFailParsedElement {
constructor( constructor(
public id: number, public id: number,
public file: File, public file: File,
public reason: string) { public reason: string
) {
// nothing to do. // nothing to do.
} }
} }
@ -50,18 +77,20 @@ type FormInsertData = {
genderId?: number; genderId?: number;
artistId?: number; artistId?: number;
titleAlbum?: string; titleAlbum?: string;
} };
export const AddPage = () => { export const AddPage = () => {
const [parsedElement, setParsedElement] = useState<FileParsedElement[]>([]); const [parsedElement, setParsedElement] = useState<FileParsedElement[]>([]);
const [parsedFailedElement, setParsedFailedElement] = useState<FileFailParsedElement[] | undefined>(undefined); const [parsedFailedElement, setParsedFailedElement] = useState<
FileFailParsedElement[] | undefined
>(undefined);
const [needSend, setNeedSend] = useState<boolean>(false); const [needSend, setNeedSend] = useState<boolean>(false);
// list of all files already registered in the bdd to compare with the current list of files. // list of all files already registered in the bdd to compare with the current list of files.
const [listFileInBdd, setListFileInBdd] = useState<any[] | undefined>(undefined); const [listFileInBdd, setListFileInBdd] = useState<any[] | undefined>(
undefined
);
const { dataGenders } = useOrderedGenders(); const { dataGenders } = useOrderedGenders();
const { dataArtist } = useOrderedArtists(); const { dataArtist } = useOrderedArtists();
@ -80,20 +109,21 @@ export const AddPage = () => {
} }
let tmp = true; let tmp = true;
for (let iii = 0; iii < parsedElement.length; iii++) { for (let iii = 0; iii < parsedElement.length; iii++) {
if (isNullOrUndefined(parsedElement[iii].title) || parsedElement[iii].title === '') { if (
isNullOrUndefined(parsedElement[iii].title) ||
parsedElement[iii].title === ''
) {
tmp = false; tmp = false;
} }
} }
setNeedSend(tmp); setNeedSend(tmp);
} };
const onTitle = (data: FileParsedElement, value: any): void => { const onTitle = (data: FileParsedElement, value: any): void => {
data.title = value; data.title = value;
setParsedElement([...parsedElement]); setParsedElement([...parsedElement]);
updateNeedSend(); updateNeedSend();
} };
const removeElementFromList = (data: FileParsedElement, value: any): void => { const removeElementFromList = (data: FileParsedElement, value: any): void => {
const parsedElementTmp = [...parsedElement]; const parsedElementTmp = [...parsedElement];
@ -104,16 +134,22 @@ export const AddPage = () => {
} }
} }
setParsedElement(parsedElementTmp); setParsedElement(parsedElementTmp);
setParsedFailedElement((previous) => [...previous ?? [], new FileFailParsedElement(previous?.length ?? 0, data.file, 'Removed by user.')]); setParsedFailedElement((previous) => [
...(previous ?? []),
new FileFailParsedElement(
previous?.length ?? 0,
data.file,
'Removed by user.'
),
]);
updateNeedSend(); updateNeedSend();
} };
const onTrackId = (data: FileParsedElement, value: any): void => { const onTrackId = (data: FileParsedElement, value: any): void => {
data.trackId = value; data.trackId = value;
setParsedElement([...parsedElement]); setParsedElement([...parsedElement]);
updateNeedSend(); updateNeedSend();
} };
const clearData = () => { const clearData = () => {
setParsedElement([]); setParsedElement([]);
@ -122,7 +158,7 @@ export const AddPage = () => {
setSuggestedArtist(undefined); setSuggestedArtist(undefined);
setSuggestedAlbum(undefined); setSuggestedAlbum(undefined);
} };
const addFileWithMetaData = (file: File) => { const addFileWithMetaData = (file: File) => {
// parsedElement: FileParsedElement[] = []; // parsedElement: FileParsedElement[] = [];
@ -131,7 +167,7 @@ export const AddPage = () => {
let trackIdNumber: number | undefined = undefined; let trackIdNumber: number | undefined = undefined;
let title: string = ''; let title: string = '';
form.reset(); form.restoreValues();
console.log(`select file ${file.name}`); console.log(`select file ${file.name}`);
let tmpName = file.name.replace(/[ \t]*-[ \t]*/g, '-'); let tmpName = file.name.replace(/[ \t]*-[ \t]*/g, '-');
@ -143,7 +179,7 @@ export const AddPage = () => {
artist = splitElement[0]; artist = splitElement[0];
tmpName = tmpName.substring(artist.length + 1); tmpName = tmpName.substring(artist.length + 1);
} }
const splitElement2 = tmpName.split('\#'); const splitElement2 = tmpName.split('#');
if (splitElement2.length > 1) { if (splitElement2.length > 1) {
album = splitElement2[0]; album = splitElement2[0];
tmpName = tmpName.substring(album.length + 1); tmpName = tmpName.substring(album.length + 1);
@ -167,9 +203,13 @@ export const AddPage = () => {
console.log(`==>${JSON.stringify(tmp)}`); console.log(`==>${JSON.stringify(tmp)}`);
// add it in the list. // add it in the list.
return tmp; return tmp;
} };
const [suggestedArtist, setSuggestedArtist] = useState<string | undefined>(undefined); const [suggestedArtist, setSuggestedArtist] = useState<string | undefined>(
const [suggestedAlbum, setSuggestedAlbum] = useState<string | undefined>(undefined); undefined
);
const [suggestedAlbum, setSuggestedAlbum] = useState<string | undefined>(
undefined
);
const onChangeFile = (value: any): void => { const onChangeFile = (value: any): void => {
clearData(); clearData();
@ -188,38 +228,60 @@ export const AddPage = () => {
// clean different artist: // clean different artist:
for (let iii = 1; iii < parsedElementTmp.length; iii++) { for (let iii = 1; iii < parsedElementTmp.length; iii++) {
console.log(`check artist [${iii + 1}/${parsedElementTmp.length}] '${parsedElementTmp[0].artist} !== ${parsedElementTmp[iii].artist}'`); console.log(
`check artist [${iii + 1}/${parsedElementTmp.length}] '${parsedElementTmp[0].artist} !== ${parsedElementTmp[iii].artist}'`
);
if (parsedElementTmp[0].artist !== parsedElementTmp[iii].artist) { if (parsedElementTmp[0].artist !== parsedElementTmp[iii].artist) {
parsedFailedElementTmp.push(new FileFailParsedElement(parsedFailedElementTmp.length, parsedElementTmp[iii].file, 'Remove from list due to wrong artist value')); parsedFailedElementTmp.push(
console.log(`Remove from list (!= artist) : [${iii + 1}/${parsedElementTmp.length}] '${parsedElementTmp[iii].file.name}'`); new FileFailParsedElement(
parsedFailedElementTmp.length,
parsedElementTmp[iii].file,
'Remove from list due to wrong artist value'
)
);
console.log(
`Remove from list (!= artist) : [${iii + 1}/${parsedElementTmp.length}] '${parsedElementTmp[iii].file.name}'`
);
parsedElementTmp.splice(iii, 1); parsedElementTmp.splice(iii, 1);
iii--; iii--;
} }
} }
// clean different album: // clean different album:
for (let iii = 1; iii < parsedElementTmp.length; iii++) { for (let iii = 1; iii < parsedElementTmp.length; iii++) {
console.log(`check album [${iii + 1}/${parsedElementTmp.length}] '${parsedElementTmp[0].album} !== ${parsedElementTmp[iii].album}'`); console.log(
`check album [${iii + 1}/${parsedElementTmp.length}] '${parsedElementTmp[0].album} !== ${parsedElementTmp[iii].album}'`
);
if (parsedElementTmp[0].album !== parsedElementTmp[iii].album) { if (parsedElementTmp[0].album !== parsedElementTmp[iii].album) {
parsedFailedElementTmp.push(new FileFailParsedElement(parsedFailedElementTmp.length, parsedElementTmp[iii].file, 'Remove from list due to wrong album value')); parsedFailedElementTmp.push(
console.log(`Remove from list (!= album) : [${iii + 1}/${parsedElementTmp.length}] '${parsedElementTmp[iii].file.name}'`); new FileFailParsedElement(
parsedFailedElementTmp.length,
parsedElementTmp[iii].file,
'Remove from list due to wrong album value'
)
);
console.log(
`Remove from list (!= album) : [${iii + 1}/${parsedElementTmp.length}] '${parsedElementTmp[iii].file.name}'`
);
parsedElementTmp.splice(iii, 1); parsedElementTmp.splice(iii, 1);
iii--; iii--;
} }
} }
setParsedElement(parsedElementTmp); setParsedElement(parsedElementTmp);
setParsedFailedElement(parsedFailedElementTmp); setParsedFailedElement(parsedFailedElementTmp);
console.log(`check : ${JSON.stringify(parsedElementTmp[0])}`) console.log(`check : ${JSON.stringify(parsedElementTmp[0])}`);
// find artistId: // find artistId:
console.log(`try find artist : ${parsedElementTmp[0].artist}`); console.log(`try find artist : ${parsedElementTmp[0].artist}`);
let artistFound = false; let artistFound = false;
dataArtist.forEach((data) => { dataArtist.forEach((data) => {
if (data.name?.toLowerCase() === parsedElementTmp[0].artist?.toLowerCase()) { if (
data.name?.toLowerCase() === parsedElementTmp[0].artist?.toLowerCase()
) {
console.log(` find artist : ${data.id}`); console.log(` find artist : ${data.id}`);
form.setValues({ artistId: data.id }); form.setValues({ artistId: data.id });
artistFound = true; artistFound = true;
} }
}) });
if (!artistFound) { if (!artistFound) {
console.log(` set Suggested artist : ${parsedElementTmp[0].artist}`); console.log(` set Suggested artist : ${parsedElementTmp[0].artist}`);
setSuggestedArtist(parsedElementTmp[0].artist); setSuggestedArtist(parsedElementTmp[0].artist);
@ -230,18 +292,20 @@ export const AddPage = () => {
console.log(`try find album : ${parsedElementTmp[0].album}`); console.log(`try find album : ${parsedElementTmp[0].album}`);
let albumFound = false; let albumFound = false;
dataAlbums.forEach((data) => { dataAlbums.forEach((data) => {
if (data.name?.toLowerCase() === parsedElementTmp[0].album?.toLowerCase()) { if (
data.name?.toLowerCase() === parsedElementTmp[0].album?.toLowerCase()
) {
console.log(` find album : ${data.id}`); console.log(` find album : ${data.id}`);
form.setValues({ albumId: data.id }); form.setValues({ albumId: data.id });
albumFound = true; albumFound = true;
} }
}) });
if (!albumFound) { if (!albumFound) {
console.log(` set Suggested album : ${parsedElementTmp[0].album}`); console.log(` set Suggested album : ${parsedElementTmp[0].album}`);
setSuggestedAlbum(parsedElementTmp[0].album); setSuggestedAlbum(parsedElementTmp[0].album);
} }
updateNeedSend(); updateNeedSend();
} };
const [indexUpload, setIndexUpload] = useState<number | undefined>(undefined); const [indexUpload, setIndexUpload] = useState<number | undefined>(undefined);
const [listValues, setListValues] = useState<string[]>([]); const [listValues, setListValues] = useState<string[]>([]);
@ -254,35 +318,37 @@ export const AddPage = () => {
} }
const uploadNext = (): void => { const uploadNext = (): void => {
setIndexUpload((previous) => previous ?? -1 + 1);
setIndexUpload((previous) => (previous ?? -1 + 1));
TrackResource.uploadTrack({ TrackResource.uploadTrack({
restConfig: session.getRestConfig(), restConfig: session.getRestConfig(),
data: { data: {
title: parsedElement[0].title, title: parsedElement[0].title,
file: parsedElement[0].file, file: parsedElement[0].file,
albumId: form.values['albumId'], albumId: form.values['albumId'] ?? undefined,
artistId: form.values['artistId'], artistId: form.values['artistId'] ?? undefined,
genderId: form.values['genderId'], genderId: form.values['genderId'] ?? undefined,
trackId: parsedElement[0].trackId, trackId: parsedElement[0].trackId ?? undefined,
}, callbacks: { },
progressUpload: progressUpload callbacks: {
} progressUpload: progressUpload,
}).then((data: Track) => { },
// TODO: add the element in the local base... })
console.log(`element added: ${JSON.stringify(data, null, 2)}`); .then((data: Track) => {
}).catch((error: RestErrorResponse) => { // TODO: add the element in the local base...
// TODO: manage error console.log(`element added: ${JSON.stringify(data, null, 2)}`);
console.log(`element error: ${JSON.stringify(error, null, 2)}`); })
}); .catch((error: RestErrorResponse) => {
} // TODO: manage error
console.log(`element error: ${JSON.stringify(error, null, 2)}`);
});
};
const sendFile = (): void => { const sendFile = (): void => {
console.log(`Send file requested ... ${parsedElement.length}`); console.log(`Send file requested ... ${parsedElement.length}`);
setListValues(parsedElement.map((element) => element.file.name)); setListValues(parsedElement.map((element) => element.file.name));
uploadNext(); uploadNext();
} };
function onUploadAbort(): void { function onUploadAbort(): void {
//throw new Error('Function not implemented.'); //throw new Error('Function not implemented.');
@ -293,30 +359,35 @@ export const AddPage = () => {
} }
const addNewGender = (data: string): Promise<Gender> => { const addNewGender = (data: string): Promise<Gender> => {
return storeGender.update(GenderResource.post({ return storeGender.update(
restConfig: session.getRestConfig(), GenderResource.post({
data: { restConfig: session.getRestConfig(),
name: data, data: {
}, name: data,
})); },
} })
);
};
const addNewArtist = (data: string): Promise<Artist> => { const addNewArtist = (data: string): Promise<Artist> => {
return storeArtist.update(ArtistResource.post({ return storeArtist.update(
restConfig: session.getRestConfig(), ArtistResource.post({
data: { restConfig: session.getRestConfig(),
name: data, data: {
}, name: data,
})); },
} })
);
};
const addNewAlbum = (data: string): Promise<Album> => { const addNewAlbum = (data: string): Promise<Album> => {
return storeAlbum.update(AlbumResource.post({ return storeAlbum.update(
restConfig: session.getRestConfig(), AlbumResource.post({
data: { restConfig: session.getRestConfig(),
name: data, data: {
}, name: data,
})); },
} })
);
};
return ( return (
<> <>
@ -333,111 +404,205 @@ export const AddPage = () => {
<Flex> <Flex>
<Text flex={1}>format:</Text> <Text flex={1}>format:</Text>
<Text flex={4}> <Text flex={4}>
The format of the media permit to automatic find meta-data:<br /> The format of the media permit to automatic find meta-data:
Artist~album#idTrack-my name of my media.webm<br /> <br />
<b>example:</b> Clarika~Moi En Mieux#22-des bulles.webm</Text> Artist~album#idTrack-my name of my media.webm
<br />
<b>example:</b> Clarika~Moi En Mieux#22-des bulles.webm
</Text>
</Flex> </Flex>
<Flex> <Flex>
<Text flex={1}>Media:</Text> <Text flex={1}>Media:</Text>
<Input flex={4} type="file" placeholder="Select a media file" <Input
accept=".webm" multiple onChange={onChangeFile} /> flex={4}
type="file"
placeholder="Select a media file"
accept=".webm"
multiple
onChange={onChangeFile}
/>
</Flex> </Flex>
</Flex> </Flex>
{parsedElement && parsedElement.length !== 0 && (<> {parsedElement && parsedElement.length !== 0 && (
<Text fontSize="30px">Meta-data:</Text> <>
<FormSelect label="Gender" form={form} variableName='genderId' options={dataGenders} keyInputValue='name' addNewItem={addNewGender} /> <Text fontSize="30px">Meta-data:</Text>
<FormSelect label="Artist" form={form} variableName='artistId' options={dataArtist} keyInputValue='name' addNewItem={addNewArtist} suggestion={suggestedArtist} /> <FormSelect
<FormSelect label="Album" form={form} variableName='albumId' options={dataAlbums} keyInputValue='name' addNewItem={addNewAlbum} suggestion={suggestedAlbum} /> label="Gender"
<TableContainer> form={form}
<Table variant='striped' colorScheme='teal' background="gray.700"> variableName="genderId"
<Thead> options={dataGenders}
<Tr> keyInputValue="name"
<Th>track ID</Th> addNewItem={addNewGender}
<Th width="full">Title</Th> />
<Th>actions</Th> <FormSelect
</Tr> label="Artist"
</Thead> form={form}
<Tbody> variableName="artistId"
{parsedElement.map((data) => ( options={dataArtist}
<Tr key={data.file.name}> keyInputValue="name"
<Td><Input type="number" addNewItem={addNewArtist}
pattern="[0-9]{0-4}" suggestion={suggestedArtist}
placeholder="e?" />
value={data.trackId} <FormSelect
onChange={e => onTrackId(data, e.target.value)} label="Album"
backgroundColor={data.trackIdDetected === true ? "darkred" : undefined} form={form}
/></Td> variableName="albumId"
<Td><Input type="text" options={dataAlbums}
placeholder="Name of the Media" keyInputValue="name"
value={data.title} addNewItem={addNewAlbum}
onChange={e => onTitle(data, e.target.value)} suggestion={suggestedAlbum}
backgroundColor={data.title === '' ? "darkred" : undefined} />
/> <TableContainer>
{data.nameDetected === true && <Table
<><br /> variant="striped"
<Text as="span" color="@danger"> colorScheme="teal"
^^^This title already exist !!! background="gray.700"
</Text> >
</> <Thead>
}</Td>
<Td><Button onClick={e => removeElementFromList(data, e.target)}>
<LuTrash /> Remove
</Button></Td>
</Tr>))}
</Tbody>
</Table>
<Flex marginY="15px">
<Button variant="@primary" onClick={sendFile} disabled={!needSend} marginLeft="auto" marginRight="30px">
<MdCloudUpload /> Upload
</Button>
</Flex>
</TableContainer>
</>)}
{listFileInBdd && (<>
<TableContainer>
<Table variant='striped' colorScheme='teal' background="gray.700">
<Thead>
<Tr>
<Th>track ID</Th>
<Th width="full">Title</Th>
<Th>actions</Th>
</Tr>
</Thead>
<Tbody>
{listFileInBdd.map((data) => (
<Tr> <Tr>
<Td><Text color={data.episodeDetected === true ? "red" : undefined}>{data.trackId}</Text></Td> <Th>track ID</Th>
<Td><Text color={data.nameDetected === true ? "red" : undefined}>{data.title}</Text></Td> <Th width="full">Title</Th>
<Td></Td> <Th>actions</Th>
</Tr>))} </Tr>
</Tbody> </Thead>
</Table> <Tbody>
</TableContainer> {parsedElement.map((data) => (
</>)} <Tr key={data.file.name}>
<Td>
<Input
type="number"
pattern="[0-9]{0-4}"
placeholder="e?"
value={data.trackId}
onChange={(e) => onTrackId(data, e.target.value)}
backgroundColor={
data.trackIdDetected === true
? 'darkred'
: undefined
}
/>
</Td>
<Td>
<Input
type="text"
placeholder="Name of the Media"
value={data.title}
onChange={(e) => onTitle(data, e.target.value)}
backgroundColor={
data.title === '' ? 'darkred' : undefined
}
/>
{data.nameDetected === true && (
<>
<br />
<Text as="span" color="@danger">
^^^This title already exist !!!
</Text>
</>
)}
</Td>
<Td>
<Button
onClick={(e) =>
removeElementFromList(data, e.target)
}
>
<LuTrash /> Remove
</Button>
</Td>
</Tr>
))}
</Tbody>
</Table>
<Flex marginY="15px">
<Button
variant="@primary"
onClick={sendFile}
disabled={!needSend}
marginLeft="auto"
marginRight="30px"
>
<MdCloudUpload /> Upload
</Button>
</Flex>
</TableContainer>
</>
)}
{parsedFailedElement && (<> {listFileInBdd && (
<Text fontSize="30px">Rejected:</Text> <>
<TableContainer> <TableContainer>
<Table variant='striped' colorScheme='teal' background="gray.700"> <Table
<Thead> variant="striped"
<Tr> colorScheme="teal"
<Th maxWidth='80%'>file</Th> background="gray.700"
<Th>Reason</Th> >
</Tr> <Thead>
</Thead> <Tr>
<Tbody> <Th>track ID</Th>
{parsedFailedElement.map((data) => ( <Th width="full">Title</Th>
<Tr key={data.file.name}> <Th>actions</Th>
<Td>{data.file.name}</Td> </Tr>
<Td>{data.reason}</Td> </Thead>
</Tr>))} <Tbody>
</Tbody> {listFileInBdd.map((data) => (
</Table> <Tr>
</TableContainer> <Td>
</>)} <Text
color={
data.episodeDetected === true ? 'red' : undefined
}
>
{data.trackId}
</Text>
</Td>
<Td>
<Text
color={
data.nameDetected === true ? 'red' : undefined
}
>
{data.title}
</Text>
</Td>
<Td></Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</>
)}
{parsedFailedElement && (
<>
<Text fontSize="30px">Rejected:</Text>
<TableContainer>
<Table
variant="striped"
colorScheme="teal"
background="gray.700"
>
<Thead>
<Tr>
<Th maxWidth="80%">file</Th>
<Th>Reason</Th>
</Tr>
</Thead>
<Tbody>
{parsedFailedElement.map((data) => (
<Tr key={data.file.name}>
<Td>{data.file.name}</Td>
<Td>{data.reason}</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</>
)}
</Flex> </Flex>
{indexUpload !== undefined && {indexUpload !== undefined && (
<PopUpUploadProgress <PopUpUploadProgress
title="Upload File(s)" title="Upload File(s)"
currentSize={currentPosition} currentSize={currentPosition}
@ -445,10 +610,10 @@ export const AddPage = () => {
index={indexUpload} index={indexUpload}
elements={listValues} elements={listValues}
onAbort={onUploadAbort} onAbort={onUploadAbort}
onClose={OnUploadClose} /> onClose={OnUploadClose}
} />
</PageLayout > )}
</PageLayout>
</> </>
); );
}; };