102 Commits

Author SHA1 Message Date
979ec4b576 [FEAT] local theme integration 2025-01-23 18:09:57 +01:00
12223347d3 [FEAT] add many basic element, ux bad but build is OK 2025-01-22 00:30:16 +01:00
f9019ec508 [DEV] restart from scratch with no toolbox ==> maybe slower but funny 2025-01-21 00:54:27 +01:00
d52052de90 [DEV] test step between receipice and direct managemnt 2025-01-20 18:45:19 +01:00
91defa42c2 [FIX] chakra ui component is not full 2025-01-14 18:56:03 +01:00
2812d21782 [FEAT] doen not work: regacto of the Chakra-ui 3.3 ==> very bad port 2025-01-13 21:59:06 +01:00
eb5a366a12 [FEAT] add on air 2025-01-11 20:48:22 +01:00
6b801d250f [FIX] remove a stupid log... 2025-01-11 19:31:51 +01:00
01fad1b9d4 [FEAT] add a shuffle in the artist list.
This feature take a random on the artist and keep the 25 first and ge a single track for each artist.
2025-01-11 19:30:01 +01:00
371bea79f9 [FEAT] add a tool to manage a shuffle of an array 2025-01-11 19:28:51 +01:00
35725e1320 [FIX] sign-in in production mode 2025-01-11 19:28:30 +01:00
d6a8c7d23f [FIX] build version 2025-01-11 18:12:27 +01:00
3c604e9593 [VERSION] update dev tag version 2025-01-11 17:33:31 +01:00
43d8108270 [RELEASE] Release v1.1.0 2025-01-11 17:33:21 +01:00
1a883193d0 [DEPENDENCY] update release of archidata 2025-01-11 17:32:43 +01:00
78b1970ba9 [FEAT] (front) Update versions of dependency 2025-01-11 17:31:01 +01:00
746d5dff96 [FIX] test will not able to connect on port 80 2025-01-11 17:31:01 +01:00
8780ea8e63 [FIX] initialization error 2025-01-11 17:31:01 +01:00
8911eed0fb [FEAT] update NCU to only update vertion but not the major 2025-01-11 17:31:01 +01:00
1a3652472e [FIX] update the error response with OID instead of UUID 2025-01-11 17:31:01 +01:00
2e62577103 [FIX] (front) fix the environment semection in production mode 2025-01-11 17:31:01 +01:00
653e77160b [FEAT] update logger to well support the backend 2025-01-11 17:31:01 +01:00
3898a6bf4f [DEV] remove old logger system 2025-01-11 17:31:01 +01:00
448cf1569b [FIX] remove old loger system 2025-01-11 17:31:01 +01:00
d3e2b3e601 [VERSION] update dev tag version 2025-01-06 23:49:35 +01:00
8f026d47e1 [RELEASE] Release v1.0.4 2025-01-06 23:48:46 +01:00
de17fc228d [FIX] migration file error 2025-01-06 23:48:21 +01:00
3434a48573 [VERSION] update dev tag version 2025-01-06 23:30:02 +01:00
8f1934e323 [RELEASE] Release v1.0.2 2025-01-06 23:29:13 +01:00
1631d1cab9 [FIX] logger dependency error 2025-01-06 23:24:45 +01:00
b71ea1bcd8 [FEAT] some update 2025-01-06 00:50:38 +01:00
db1e387ac8 [FIX] set work again 2025-01-06 00:27:09 +01:00
762a1aeced [VERSION] update dev tag version 2025-01-05 22:06:58 +01:00
670b5537bb [RELEASE] Release v1.0.0 2025-01-05 22:06:38 +01:00
16e7197939 [FEAT] fix archidata_vertion for release 2025-01-05 22:03:44 +01:00
98d213b01e [FEAT] Bridge 'JDK logging API' to 'SLF4J' 2025-01-05 21:53:42 +01:00
0260b4d408 [FEAT] update to new API of archidata 2025-01-05 20:08:56 +01:00
f03a18c34c [RELEASE] prepare release 2024-09-20 19:51:17 +02:00
6107b95dcd [FEAT] better management of upload files 2024-09-20 22:19:10 +02:00
e9af64405b [DEV] add checker 2024-09-19 21:22:35 +02:00
725cd54d92 [FEAT] basic pop-up and upload a single file (can not read) 2024-09-01 23:10:40 +02:00
08eb0f878a [FEAT] ready gor update and suggestion 2024-09-01 23:10:40 +02:00
619e9aa4b8 [FEAT] update the Slector to support add element 2024-09-16 21:53:29 +02:00
6181022814 [FEAT] add basic page of the ADD media (kanva OK, no upload and check) 2024-09-16 00:06:37 +02:00
3f96b870a0 [FEAT] ready to generate production 2024-09-16 00:05:53 +02:00
5eba6d32b6 [DEV] build docker and correct some typing 2024-09-15 17:58:34 +02:00
a24484b5f9 [FEAT] ignore env_dev/data* 2024-09-15 17:58:34 +02:00
77f8527fc4 [FEAT] rename front2 as front 2024-09-15 17:58:24 +02:00
75cca15898 [FEAT] remove angular front 2024-09-15 17:56:48 +02:00
f0b2a859db [FEAT] correct some connection/disconnection of SSO 2024-09-15 17:56:48 +02:00
a6fe889d4c [FEAT] better covers, Icons and work on better display for the phones 2024-09-15 17:56:48 +02:00
ad7ff2c340 [FEAT] upgrade the api generation and search how to manage cover auto-change 2024-09-15 17:56:48 +02:00
1271885124 [FIX] correct the API generation 2024-09-15 17:56:48 +02:00
dde514a1a0 [FIX] missing migration 2024-09-15 17:56:48 +02:00
dc7131843d [FEAT] continue integration 2024-09-15 17:56:48 +02:00
091390e025 [FEAT] add edit of album and artist
Missing cover that might be studied
2024-09-15 17:56:48 +02:00
3377f80fbc [FEAT] generize the form model 2024-09-15 17:56:48 +02:00
4cf71f35b6 [FEAT] review some namings 2024-09-15 17:56:48 +02:00
faaf3b280b [FEAT] live edit, remove is operational with confirm pop-in.
need to upgrade the code to be reusable
2024-09-15 17:56:48 +02:00
0687369164 [DEV] test 2 2024-09-15 17:56:48 +02:00
147264eb7f [DEV] test 1 2024-09-15 17:56:48 +02:00
ec93c31acb Work on form and basic tools I need 2024-09-15 17:56:48 +02:00
873f0acff5 [DEV] integrate menue on thack 2024-09-15 17:56:48 +02:00
244f8a988e [DEV] remove all async it is too slow... 2024-09-15 17:56:48 +02:00
d49e1d06fe [FEAT] so many things .... 2024-09-15 17:56:48 +02:00
53c3cfefe3 [FEAT] corect the useMemo and better interface of audio player 2024-09-15 17:56:48 +02:00
90fb1ed411 [FEAT] add album direct diaplay 2024-09-15 17:56:48 +02:00
30bc82edb3 [FEAT] basic working karusic 2024-09-15 17:56:40 +02:00
1d4c547d89 [DEV] first vesion of karisic in react + chakra-ui 2024-08-22 11:29:57 +02:00
21d53b77f2 [FEAT] many renaming 2024-08-15 11:47:50 +02:00
3b27edfa3b [VERSION] update dev tag version 2024-05-12 12:03:31 +02:00
6155ca5c0b [RELEASE] Release v0.2.0 2024-05-12 12:03:31 +02:00
45eb56a6fe [DEV] update relase for island 2024-05-12 11:57:08 +02:00
9e0a834b48 [DEV] update readme 2024-04-15 00:57:00 +02:00
d5f335e1e7 [DEV] add progress callback 2024-04-15 00:56:45 +02:00
df384cc8be [DEV] correct version with some ibrary update 2024-04-13 09:03:05 +02:00
4b545ebbe2 [FIX] update dockerfile 2024-04-07 19:25:07 +02:00
eb3cd1b1fe [DEV] all is operational 2024-04-07 19:13:06 +02:00
95e2e03686 [DEV] all work well 2024-04-07 17:08:43 +02:00
2402fc31ed [DEV] it works without security 2024-04-07 17:05:36 +02:00
763875fbdf {DEV] still work 2024-04-07 09:02:17 +02:00
4ff2b247b4 [DEV] update to basic work 2024-04-06 13:08:12 +02:00
e4246750c7 [DEV] Run but does not realy start 2024-04-05 23:51:48 +02:00
04633240b3 [DEV] find how to fix the error but not Ok with the result 2024-04-03 00:34:47 +02:00
c76a339caf [DEV] update new model with remote library (not work) 2024-03-29 18:39:33 +01:00
8a5758af78 [DEV] remove submodule and correct first init 2024-03-23 07:27:27 +01:00
e05fe70f80 [DEV] ready for deply new model of data UUID 2024-03-19 23:17:05 +01:00
f6f9fa199a [DEV] update new version 2024-03-19 19:17:04 +01:00
a1684f0fc0 [DEV] update new model 2024-03-19 08:37:07 +01:00
cebf7d35fb [DEV] update all the model 2024-03-17 23:45:38 +01:00
5d8dab3742 [DEV] finish base of port but not tested 2024-03-15 07:59:23 +01:00
9a3f28553d [DEV] basic migration of to the new generation interface 2024-03-11 00:07:46 +01:00
48e14212db [DEV] implement partial migration to support new uuid for data and local cover of model 2024-03-04 00:14:08 +01:00
c3e2b7240b [DEV] migrate with succes to angular17 2024-02-26 00:01:37 +01:00
0af1bbfb52 [DEV] correct the backend can not insert trach artist ... fail t in archidata model 2024-02-26 00:01:05 +01:00
619973459a [DEV] continue migration to Angular17 2024-02-23 00:37:47 +01:00
d37ce25621 [DEV] update to angular17 2024-02-15 22:59:35 +01:00
b4157877c3 [DEV] add search in artist and album 2024-01-27 19:34:53 +01:00
883866d5f6 [DEV] update display better 2024-01-27 18:09:29 +01:00
247064e412 [DEV] remove test replace 2024-01-25 23:39:41 +01:00
70c63882e4 [DEV] corrrect the desplay of the player overflow (and some redesign) 2024-01-25 23:37:36 +01:00
9da93a2831 [DEV] update dev tag version 2024-01-19 22:50:09 +01:00
374 changed files with 23750 additions and 35049 deletions

3
.gitignore vendored
View File

@@ -53,6 +53,8 @@ testem.log
# System Files
.DS_Store
Thumbs.db
/env_dev/data
/env_dev/dataMongo
backPY/env
@@ -62,3 +64,4 @@ __pycache__
.design/
.vscode/
front/storybook-static

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "front/src/common"]
path = front/src/common
url = ../common_web.git

View File

@@ -1,7 +1,32 @@
#!/bin/bash
version_file="../version.txt"
# update new release dependency
cd back
mvn versions:set -DnewVersion=$(cat ../version.txt)
# update the Maven version number
mvn versions:set -DnewVersion=$(sed 's/dev/SNAPSHOT/g' $version_file)
if grep -q "DEV" "$version_file"; then
# update all versions release of dependency
mvn versions:use-latest-releases
# update our manage dependency as snapshoot
mvn versions:use-latest-versions -Dincludes=kangaroo-and-rabbit
else
# update our manage dependency as release (must be done before)
mvn versions:use-latest-releases -Dincludes=kangaroo-and-rabbit
fi
cd -
cd front
if grep -q "dev" "$version_file"; then
# update all dependency
pnpm install
pnpm run update_packages
else
# in case of release ==> can not do it automatically ...
echo not implemented
fi
cd -

View File

@@ -3,23 +3,31 @@
## buyilding-end install applications:
##
######################################################################################
FROM archlinux:base-devel AS builder
FROM archlinux:base-devel AS common
# update system
RUN pacman -Syu --noconfirm && pacman-db-upgrade \
&& pacman -S --noconfirm jdk-openjdk maven npm \
&& pacman -S --noconfirm jdk-openjdk \
&& pacman -Scc --noconfirm
WORKDIR /tmp
FROM common AS builder
# update system
RUN pacman -Syu --noconfirm && pacman-db-upgrade \
&& pacman -S --noconfirm maven npm pnpm \
&& pacman -Scc --noconfirm
ENV PATH /tmp/node_modules/.bin:$PATH
WORKDIR /tmp
######################################################################################
##
## Build back:
##
######################################################################################
FROM builder AS buildBack
COPY back/pom.xml /tmp
COPY back/src /tmp/src/
FROM builder AS build_back
COPY back/pom.xml ./
COPY back/Formatter.xml ./
COPY back/src ./src/
RUN mvn clean compile assembly:single
######################################################################################
@@ -27,26 +35,44 @@ RUN mvn clean compile assembly:single
## Build front:
##
######################################################################################
FROM builder AS buildFront
FROM builder AS dependency_front
ADD front/package-lock.json \
front/package.json \
front/karma.conf.js \
front/protractor.conf.js \
/tmp/
RUN echo "@kangaroo-and-rabbit:registry=https://gitea.atria-soft.org/api/packages/kangaroo-and-rabbit/npm/" > /root/.npmrc
ADD front/package.json \
front/pnpm-lock.yaml \
./
ADD front/src/theme ./src/theme
# install and cache app dependencies
RUN npm install
RUN pnpm install --prod=false
ADD front/e2e \
front/tsconfig.json \
front/tslint.json \
front/angular.json \
/tmp/
ADD front/src /tmp/src
###############################################################
## Install sources
###############################################################
FROM dependency_front AS load_sources_front
# generate build
RUN ng build --output-path=dist --configuration=production --base-href=/karusic/ --deploy-url=/karusic/
# JUST to get the vertion of the application and his sha...
COPY front/build.js \
front/version.txt \
front/tsconfig.json \
front/tsconfig.node.json \
front/vite.config.mts \
front/index.html \
./
COPY front/public ./public
COPY front/src ./src
#We are not in prod mode ==> we need to overwrite the production env.
ARG env=front/.env.production
COPY ${env} .env
###############################################################
## Build the sources
###############################################################
FROM load_sources_front AS build_front
# build in bundle mode all the application
RUN pnpm static:build
######################################################################################
##
@@ -54,9 +80,10 @@ RUN ng build --output-path=dist --configuration=production --base-href=/karusic/
##
######################################################################################
FROM bellsoft/liberica-openjdk-alpine:latest
# add wget to manage the health check...
RUN apk add --no-cache wget
#FROM bellsoft/liberica-openjdk-alpine:latest
## add wget to manage the health check...
#RUN apk add --no-cache wget
FROM common
#FROM archlinux:base
#RUN pacman -Syu --noconfirm && pacman-db-upgrade
@@ -67,10 +94,10 @@ RUN apk add --no-cache wget
## clean all the caches Need only on the release environment
#RUN pacman -Scc --noconfirm
ENV LANG=C.UTF-8
ENV LANG C.UTF-8
COPY --from=buildBack /tmp/out/maven/*.jar /application/application.jar
COPY --from=buildFront /tmp/dist /application/front/
COPY --from=build_back /tmp/out/maven/*.jar /application/application.jar
COPY --from=build_front /tmp/dist /application/front/
WORKDIR /application/

92
README.md Normal file
View File

@@ -0,0 +1,92 @@
Karideo
=======
**K**angaroo **A**nd **R**abbit (m)usic is a simple framework to propose music streaming for personal network
Run in local:
=============
so simple...
```{.bash}
# start the Bdd interface (no big data > 50Mo)
cd bdd
docker-compose up -d
# start the REST API
cd back
docker-compose up -d
# start the front API
cd ../front
docker-compose up -d
```
convert in an angular application:
https://betterprogramming.pub/how-to-convert-your-angular-application-to-a-native-mobile-app-android-and-ios-c212b38976df
Link with the external sub-library (front)
------------------------------------------
Link:
```bash
cd front
pnpm run link_kar_cw
```
un-link:
```bash
cd front
pnpm run unlink_kar_cw
```
Tools in production mode
========================
Changing the Log Level
----------------------
In a production environment, you can adjust the log level to help diagnose bugs more effectively.
The available log levels are:
| **Log Level Tag** | **org.kar.karusic** | **org.kar.archidata** | **other** |
| ----------------- | ------------------- | --------------------- | --------- |
| `prod` | INFO | INFO | INFO |
| `prod-debug` | DEBUG | INFO | INFO |
| `prod-trace` | TRACE | DEBUG | INFO |
| `prod-trace-full` | TRACE | TRACE | INFO |
| `dev` | TRACE | DEBUG | INFO |
Manual set in production:
=========================
Connect on the registry
------------------------
To log-in and log-out from the registry:
```bash
export REGISTRY_ADDRESS=gitea.atria-soft.org
docker login -u <<YOUR_USER_NAME>> ${REGISTRY_ADDRESS}
docker logout ${REGISTRY_ADDRESS}
```
pull the root image of dockers
------------------------------
```bash
docker pull archlinux:base-devel
docker pull bellsoft/liberica-openjdk-alpine:latest
```
Create the version
------------------
Execute in the local folder: (use ```dev``` for development and ```latest``` for production release)
```bash
export TAG_DOCKER=latest
export REGISTRY_ADDRESS=gitea.atria-soft.org
docker build -t ${REGISTRY_ADDRESS}/kangaroo-and-rabbit/karusic:${TAG_DOCKER} .
docker push ${REGISTRY_ADDRESS}/kangaroo-and-rabbit/karusic:${TAG_DOCKER}
```

View File

@@ -1,7 +1,8 @@
FROM maven:3-openjdk-18 AS build
FROM maven:3-openjdk-23 AS build
COPY pom.xml /tmp/
COPY src /tmp/src/
COPY Formatter.xml /tmp/
WORKDIR /tmp/
RUN mvn clean compile assembly:single

View File

@@ -1,4 +0,0 @@

View File

@@ -1,13 +1,13 @@
<?xml version="1.0"?>
<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>
<artifactId>karusic</artifactId>
<version>0.1.0</version>
<properties>
<maven.compiler.version>3.1</maven.compiler.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<modelVersion>4.0.0</modelVersion>
<groupId>org.kar</groupId>
<artifactId>karusic</artifactId>
<version>1.1.1-SNAPSHOT</version>
<properties>
<maven.compiler.version>3.13.0</maven.compiler.version>
<maven.compiler.source>23</maven.compiler.source>
<maven.compiler.target>23</maven.compiler.target>
<maven.dependency.version>3.1.1</maven.dependency.version>
</properties>
<repositories>
@@ -20,13 +20,35 @@
<dependency>
<groupId>kangaroo-and-rabbit</groupId>
<artifactId>archidata</artifactId>
<version>0.6.0</version>
<version>0.21.0</version>
</dependency>
<!-- Loopback of logger JDK logging API to SLF4J -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<artifactId>jul-to-slf4j</artifactId>
<version>2.0.9</version>
</dependency>
<!-- generic logger of SLF4J to console (in color) -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.11</version>
</dependency>
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>2.12.2</version>
</dependency>
<dependency>
<groupId>org.codehaus.janino</groupId>
<artifactId>janino</artifactId>
<version>3.1.9</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.18.1</version>
</dependency>
<!--
************************************************************
** TEST dependency **
@@ -35,15 +57,25 @@
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.10.1</version>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.10.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<version>2.24.1</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.5.0</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src</sourceDirectory>
@@ -81,7 +113,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<version>4.0.0-beta-1</version>
<executions>
<execution>
<id>attach-sources</id>
@@ -95,10 +127,12 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<version>3.2.5</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.7.1</version>
<configuration>
<archive>
<manifest>
@@ -110,94 +144,21 @@
</descriptorRefs>
</configuration>
</plugin>
<!-- Create coverage -->
<!--
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.10</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>jacoco-check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>PACKAGE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.50</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
-->
<!-- Java-doc generation for stand-alone site -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version>
<version>3.3.0</version>
<configuration>
<show>private</show>
<nohelp>true</nohelp>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>exec-application</id>
<phase>package</phase>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>org.kar.karusic.WebLauncher</mainClass>
</configuration>
</plugin>
<!-- Check the style of the code -->
<!--
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<configLocation>CheckStyle.xml</configLocation>
<consoleOutput>true</consoleOutput>
<failOnViolation>true</failOnViolation>
<failsOnError>true</failsOnError>
<includeTestSourceDirectory>true</includeTestSourceDirectory>
</configuration>
</plugin>
<plugin>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<version>2.12.2</version>
<version>2.24.1</version>
<configuration>
<encoding>UTF-8</encoding>
<lineEnding>LF</lineEnding>
@@ -221,7 +182,15 @@
</execution>
</executions>
</plugin>
-->
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>4.8.5.0</version>
<configuration>
<includeFilterFile>spotbugs-security-include.xml</includeFilterFile>
<excludeFilterFile>spotbugs-security-exclude.xml</excludeFilterFile>
</configuration>
</plugin>
</plugins>
</build>
<!-- Generate Java-docs As Part Of Project Reports -->
@@ -230,7 +199,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version>
<version>3.3.0</version>
<configuration>
<show>public</show>
</configuration>

258
back/pom.xml.versionsBackup Normal file
View File

@@ -0,0 +1,258 @@
<?xml version="1.0"?>
<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>
<artifactId>karusic</artifactId>
<version>0.1.0</version>
<properties>
<maven.compiler.version>3.1</maven.compiler.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</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>
<artifactId>archidata</artifactId>
<version>0.14.3-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.1.0-alpha1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.18.0-rc1</version>
</dependency>
<!--
************************************************************
** TEST dependency **
************************************************************
-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<version>2.24.1</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.5.0</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src</sourceDirectory>
<testSourceDirectory>test/src</testSourceDirectory>
<directory>${project.basedir}/out/maven/</directory>
<resources>
<resource>
<directory>src/resources</directory>
</resource>
</resources>
<testResources>
<testResource>
<directory>${basedir}/test/resources</directory>
</testResource>
</testResources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.version}</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.4.0</version>
<configuration>
<mainClass>org.kar.karusic.WebLauncher</mainClass>
</configuration>
</plugin>
<!-- Create the source bundle -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- junit results -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>fully.qualified.MainClass</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<!-- Create coverage -->
<!--
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.10</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>jacoco-check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>PACKAGE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.50</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
-->
<!-- Java-doc generation for stand-alone site -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<show>private</show>
<nohelp>true</nohelp>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>exec-application</id>
<phase>package</phase>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>org.kar.karusic.WebLauncher</mainClass>
</configuration>
</plugin>
<!-- Check the style of the code -->
<plugin>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<version>2.23.0</version>
<configuration>
<encoding>UTF-8</encoding>
<lineEnding>LF</lineEnding>
<configFile>Formatter.xml</configFile>
<directories>
<directory>src/</directory>
<directory>test/src</directory>
</directories>
<includes>
<include>**/*.java</include>
</includes>
<excludes>
<exclude>module-info.java</exclude>
</excludes>
</configuration>
<executions>
<execution>
<goals>
<goal>validate</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>4.8.5.0</version>
<configuration>
<includeFilterFile>spotbugs-security-include.xml</includeFilterFile>
<excludeFilterFile>spotbugs-security-exclude.xml</excludeFilterFile>
<!--<plugins>
<plugin>
<groupId>com.h3xstream.findsecbugs</groupId>
<artifactId>findsecbugs-plugin</artifactId>
<version>1.12.0</version>
</plugin>
</plugins>
-->
</configuration>
</plugin>
</plugins>
</build>
<!-- Generate Java-docs As Part Of Project Reports -->
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<show>public</show>
</configuration>
</plugin>
</plugins>
</reporting>
</project>

View File

@@ -0,0 +1,27 @@
package org.kar.karusic.CodecBson;
import java.util.UUID;
import org.bson.BsonReader;
import org.bson.BsonWriter;
import org.bson.codecs.Codec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;
public class UUIDCodec implements Codec<UUID> {
@Override
public UUID decode(final BsonReader reader, final DecoderContext decoderContext) {
return UUID.fromString(reader.readString());
}
@Override
public void encode(final BsonWriter writer, final UUID value, final EncoderContext encoderContext) {
writer.writeString(value.toString());
}
@Override
public Class<UUID> getEncoderClass() {
return UUID.class;
}
}

View File

@@ -1,23 +1,29 @@
package org.kar.karusic;
import java.net.URI;
import java.util.Iterator;
import java.util.logging.LogManager;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriter;
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.GlobalConfiguration;
import org.kar.archidata.UpdateJwtPublicKey;
import org.kar.archidata.api.DataResource;
import org.kar.archidata.catcher.ExceptionCatcher;
import org.kar.archidata.catcher.FailExceptionCatcher;
import org.kar.archidata.catcher.InputExceptionCatcher;
import org.kar.archidata.catcher.SystemExceptionCatcher;
import org.kar.archidata.api.ProxyResource;
import org.kar.archidata.catcher.GenericCatcher;
import org.kar.archidata.db.DbConfig;
import org.kar.archidata.exception.DataAccessException;
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.karusic.api.AlbumResource;
import org.kar.karusic.api.ArtistResource;
import org.kar.karusic.api.Front;
@@ -29,8 +35,13 @@ import org.kar.karusic.api.UserResource;
import org.kar.karusic.filter.KarusicAuthenticationFilter;
import org.kar.karusic.migration.Initialization;
import org.kar.karusic.migration.Migration20231126;
import org.kar.karusic.migration.Migration20240225;
import org.kar.karusic.migration.Migration20240226;
import org.kar.karusic.migration.Migration20240907;
import org.kar.karusic.migration.Migration20250104;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;
import jakarta.ws.rs.core.UriBuilder;
@@ -38,15 +49,15 @@ public class WebLauncher {
final static Logger LOGGER = LoggerFactory.getLogger(WebLauncher.class);
protected UpdateJwtPublicKey keyUpdater = null;
protected HttpServer server = null;
public WebLauncher() {
ConfigBaseVariable.bdDatabase = "karusic";
}
private static URI getBaseURI() {
return UriBuilder.fromUri(ConfigBaseVariable.getlocalAddress()).build();
}
public void migrateDB() throws Exception {
WebLauncher.LOGGER.info("Create migration engine");
final MigrationEngine migrationEngine = new MigrationEngine();
@@ -54,16 +65,24 @@ public class WebLauncher {
migrationEngine.setInit(new Initialization());
WebLauncher.LOGGER.info("Add migration since last version");
migrationEngine.add(new Migration20231126());
migrationEngine.add(new Migration20240225());
migrationEngine.add(new Migration20240226());
migrationEngine.add(new Migration20240907());
migrationEngine.add(new Migration20250104());
WebLauncher.LOGGER.info("Migrate the DB [START]");
migrationEngine.migrateWaitAdmin(GlobalConfiguration.dbConfig);
migrationEngine.migrateWaitAdmin(new DbConfig());
WebLauncher.LOGGER.info("Migrate the DB [STOP]");
}
public static void main(final String[] args) throws Exception {
// Loop-back of logger JDK logging API to SLF4J
LogManager.getLogManager().reset();
SLF4JBridgeHandler.install();
WebLauncher.LOGGER.info("[START] application wake UP");
final WebLauncher launcher = new WebLauncher();
launcher.migrateDB();
launcher.process();
WebLauncher.LOGGER.info("end-configure the server & wait finish process:");
Thread.currentThread().join();
@@ -71,13 +90,40 @@ public class WebLauncher {
launcher.stopOther();
WebLauncher.LOGGER.info("STOP the REST server:");
}
public void process() throws InterruptedException {
public void plop(final String aaa) {
// List available Image Readers
WebLauncher.LOGGER.trace("Available Image Readers:");
final Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName(aaa);
while (readers.hasNext()) {
final ImageReader reader = readers.next();
WebLauncher.LOGGER.trace("Reader: " + reader.getOriginatingProvider().getDescription(null));
WebLauncher.LOGGER.trace("Reader CN: " + reader.getOriginatingProvider().getPluginClassName());
// ImageIO.deregisterServiceProvider(reader.getOriginatingProvider());
}
// List available Image Writers
WebLauncher.LOGGER.trace("\nAvailable Image Writers:");
final Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(aaa);
while (writers.hasNext()) {
final ImageWriter writer = writers.next();
WebLauncher.LOGGER.trace("Writer: " + writer.getOriginatingProvider().getDescription(null));
WebLauncher.LOGGER.trace("Writer CN: " + writer.getOriginatingProvider().getPluginClassName());
}
}
public void process() throws InterruptedException, DataAccessException {
ImageIO.scanForPlugins();
plop("jpeg");
plop("png");
plop("webmp");
plop("webp");
// ===================================================================
// Configure resources
// ===================================================================
final ResourceConfig rc = new ResourceConfig();
// add multipart models ..
rc.register(MultiPartFeature.class);
// global authentication system
@@ -87,10 +133,7 @@ public class WebLauncher {
// global authentication system
rc.register(KarusicAuthenticationFilter.class);
// register exception catcher
rc.register(InputExceptionCatcher.class);
rc.register(SystemExceptionCatcher.class);
rc.register(FailExceptionCatcher.class);
rc.register(ExceptionCatcher.class);
GenericCatcher.addAll(rc);
// add default resource:
rc.register(UserResource.class);
rc.register(AlbumResource.class);
@@ -99,22 +142,25 @@ public class WebLauncher {
rc.register(PlaylistResource.class);
rc.register(TrackResource.class);
rc.register(DataResource.class);
rc.register(ProxyResource.class);
rc.register(HealthCheck.class);
rc.register(Front.class);
ContextGenericTools.addJsr310(rc);
// add jackson to be discover when we are ins standalone server
rc.register(JacksonFeature.class);
// enable this to show low level request
//rc.property(LoggingFeature.LOGGING_FEATURE_LOGGER_LEVEL_SERVER, Level.WARNING.getName());
//System.out.println("Connect on the BDD:");
//System.out.println(" getDBHost: '" + ConfigVariable.getDBHost() + "'");
//System.out.println(" getDBPort: '" + ConfigVariable.getDBPort() + "'");
//System.out.println(" getDBLogin: '" + ConfigVariable.getDBLogin() + "'");
//System.out.println(" getDBPassword: '" + ConfigVariable.getDBPassword() + "'");
//System.out.println(" getDBName: '" + ConfigVariable.getDBName() + "'");
System.out.println(" ==> " + GlobalConfiguration.dbConfig);
// rc.property(LoggingFeature.LOGGING_FEATURE_LOGGER_LEVEL_SERVER, Level.WARNING.getName());
// System.out.println("Connect on the BDD:");
// System.out.println(" getDBHost: '" + ConfigVariable.getDBHost() + "'");
// System.out.println(" getDBPort: '" + ConfigVariable.getDBPort() + "'");
// System.out.println(" getDBLogin: '" + ConfigVariable.getDBLogin() + "'");
// System.out.println(" getDBPassword: '" + ConfigVariable.getDBPassword() + "'");
// System.out.println(" getDBName: '" + ConfigVariable.getDBName() + "'");
System.out.println(" ==> " + new DbConfig());
System.out.println("OAuth service " + getBaseURI());
this.server = GrizzlyHttpServerFactory.createHttpServer(getBaseURI(), rc);
final HttpServer serverLink = this.server;
@@ -125,13 +171,13 @@ public class WebLauncher {
serverLink.shutdownNow();
}
}, "shutdownHook"));
// ===================================================================
// start periodic update of the token ...
// ===================================================================
this.keyUpdater = new UpdateJwtPublicKey();
this.keyUpdater.start();
// ===================================================================
// run JERSEY
// ===================================================================
@@ -143,14 +189,14 @@ public class WebLauncher {
e.printStackTrace();
}
}
public void stop() {
if (this.server != null) {
this.server.shutdownNow();
this.server = null;
}
}
public void stopOther() {
this.keyUpdater.kill();
try {

View File

@@ -1,29 +1,62 @@
package org.kar.karusic;
import java.util.List;
import java.util.logging.LogManager;
import org.kar.archidata.api.DataResource;
import org.kar.archidata.api.ProxyResource;
import org.kar.archidata.exception.DataAccessException;
import org.kar.archidata.externalRestApi.AnalyzeApi;
import org.kar.archidata.externalRestApi.TsGenerateApi;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.kar.karusic.api.AlbumResource;
import org.kar.karusic.api.ArtistResource;
import org.kar.karusic.api.Front;
import org.kar.karusic.api.GenderResource;
import org.kar.karusic.api.HealthCheck;
import org.kar.karusic.api.PlaylistResource;
import org.kar.karusic.api.TrackResource;
import org.kar.karusic.api.UserResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;
public class WebLauncherLocal extends WebLauncher {
final Logger logger = LoggerFactory.getLogger(WebLauncherLocal.class);
private static final Logger LOGGER = LoggerFactory.getLogger(WebLauncherLocal.class);
private WebLauncherLocal() {}
public static void main(final String[] args) throws InterruptedException {
public static void generateObjects() throws Exception {
LOGGER.info("Generate APIs");
final List<Class<?>> listOfResources = List.of(AlbumResource.class, ArtistResource.class, Front.class, GenderResource.class, HealthCheck.class, PlaylistResource.class, UserResource.class,
TrackResource.class, DataResource.class, ProxyResource.class);
final AnalyzeApi api = new AnalyzeApi();
api.addAllApi(listOfResources);
TsGenerateApi.generateApi(api, "../front/src/back-api/");
LOGGER.info("Generate APIs (DONE)");
}
public static void main(final String[] args) throws Exception {
// Loop-back of logger JDK logging API to SLF4J
LogManager.getLogManager().reset();
SLF4JBridgeHandler.install();
// Generate the APIs in type-script
generateObjects();
final WebLauncherLocal launcher = new WebLauncherLocal();
launcher.process();
launcher.logger.info("end-configure the server & wait finish process:");
launcher.LOGGER.info("end-configure the server & wait finish process:");
Thread.currentThread().join();
launcher.logger.info("STOP the REST server:");
launcher.LOGGER.info("STOP the REST server:");
}
@Override
public void process() throws InterruptedException {
public void process() throws InterruptedException, DataAccessException {
if (true) {
// for local test:
ConfigBaseVariable.apiAdress = "http://0.0.0.0:19080/karusic/api/";
//ConfigBaseVariable.ssoAdress = "https://atria-soft.org/karso/api/";
// ConfigBaseVariable.ssoAdress = "https://atria-soft.org/karso/api/";
ConfigBaseVariable.dbPort = "3906";
ConfigBaseVariable.testMode = "true";
}
try {
super.migrateDB();

View File

@@ -2,14 +2,24 @@ package org.kar.karusic.api;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;
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.FormDataOptional;
import org.kar.archidata.annotation.TypeScriptProgress;
import org.kar.archidata.dataAccess.DBAccess;
import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.addOn.AddOnManyToMany;
import org.kar.archidata.dataAccess.addOnSQL.AddOnDataJson;
import org.kar.archidata.dataAccess.options.CheckFunction;
import org.kar.archidata.tools.DataTools;
import org.kar.karusic.model.Album;
import org.kar.karusic.model.Album.AlbumChecker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
@@ -20,80 +30,116 @@ import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@Path("/album")
@Produces({ MediaType.APPLICATION_JSON })
public class AlbumResource {
private static final Logger LOGGER = LoggerFactory.getLogger(AlbumResource.class);
static final AlbumChecker CHECKER = new AlbumChecker();
@GET
@Path("{id}")
@RolesAllowed("USER")
public static Album getWithId(@PathParam("id") final Long id) throws Exception {
@Operation(description = "Get a specific Album with his ID")
public Album get(@PathParam("id") final Long id) throws Exception {
return DataAccess.get(Album.class, id);
// return this.morphiaService.getDatastore().find(Album.class).filter(Filters.eq("id", id)).first();
}
@GET
@RolesAllowed("USER")
public List<Album> get() throws Exception {
@Operation(description = "Get all the available Albums")
public List<Album> gets() throws Exception {
return DataAccess.gets(Album.class);
// final Query<Album> query = this.morphiaService.getDatastore().find(Album.class);
// return query.stream().toList();
}
@POST
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
public Album post(final String jsonRequest) throws Exception {
return DataAccess.insertWithJson(Album.class, jsonRequest);
@Operation(description = "Add an album (when all the data already exist)")
public Album post(final Album data) throws Exception {
// TODO: how to manage the checker ???
// final Album ret = this.morphiaService.getDatastore().save(data);
// return ret;
/* final MongoCollection<Track> trackCollection = db.getCollection("TTRACLK", Track.class); final InsertOneResult res = trackCollection.insertOne(plop); LOGGER.warn("plpop {}", res); final
* ObjectId ploppppp = res.getInsertedId().asObjectId().getValue(); LOGGER.warn("plpop 2522 {}", res.getInsertedId().asObjectId().getValue()); final Track ret =
* trackCollection.find(Filters.eq("_id", res.getInsertedId().asObjectId().getValue())) .first(); System.out.println("Grade found:\t" + ret); */
return DataAccess.insert(data, new CheckFunction(CHECKER));
}
@PATCH
@Path("{id}")
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
public Album put(@PathParam("id") final Long id, final String jsonRequest) throws Exception {
DataAccess.updateWithJson(Album.class, id, jsonRequest);
@Operation(description = "Update a specific album")
public Album patch(@PathParam("id") final Long id, @AsyncType(Album.class) final String jsonRequest) throws Exception {
// final Query<Album> query = this.morphiaService.getDatastore().find(Album.class).filter(Filters.eq("id", id));
// final UpdateOperations<Album> ops = this.morphiaService.getDatastore().createUpdateOperations(Album.class)
// .set("name", master.getName());
// this.morphiaService.getDatastore().update(query, ops);
// return Response.ok(master).build();
DataAccess.updateWithJson(Album.class, id, jsonRequest, new CheckFunction(CHECKER));
return DataAccess.get(Album.class, id);
}
// @PUT
// @Path("{id}")
// @RolesAllowed("ADMIN")
// @Consumes(MediaType.APPLICATION_JSON)
// @Operation(description = "Update a specific album")
// public Album put(@PathParam("id") final Long id, final Album album)
// throws Exception {
// final Query<Album> query = this.morphiaService.getDatastore().find(Album.class).filter(Filters.eq("id", id));
// final UpdateOperations<Album> ops = this.morphiaService.getDatastore().createUpdateOperations(Album.class)
// .set("name", album.getName());
// this.morphiaService.getDatastore().update(query, ops);
// return Response.ok(album).build();
// }
@DELETE
@Path("{id}")
@RolesAllowed("ADMIN")
public Response delete(@PathParam("id") final Long id) throws Exception {
@Operation(description = "Remove a specific album")
public void remove(@PathParam("id") final Long id) throws Exception {
DataAccess.delete(Album.class, id);
return Response.ok().build();
// this.morphiaService.getDatastore().find(Album.class).filter(Filters.eq("id", id)).delete();
}
/* @POST
* @Path("{id}/track/{trackId}")
* @RolesAllowed("ADMIN")
* @Consumes({ MediaType.MULTIPART_FORM_DATA })
* @Operation(description = "Add a Track on a specific album") public Album addTrack(@PathParam("id") final Long id, @PathParam("trackId") final Long trackId) throws Exception {
* AddOnManyToMany.removeLink(this.dam, Album.class, id, "track", trackId); return this.dam.get(Album.class, id); } */
@POST
@Path("{id}/add_track/{trackId}")
@Path("{id}/cover")
@RolesAllowed("ADMIN")
@Consumes({ MediaType.MULTIPART_FORM_DATA })
public Album addTrack(@PathParam("id") final Long id, @PathParam("trackId") final Long trackId) throws Exception {
AddOnManyToMany.removeLink(Album.class, id, "track", trackId);
return DataAccess.get(Album.class, id);
@Operation(description = "Add a cover on a specific album")
@TypeScriptProgress
public Album uploadCover(@PathParam("id") final Long id, @FormDataOptional @FormDataParam("uri") final String uri, @FormDataOptional @FormDataParam("file") final InputStream fileInputStream,
@FormDataOptional @FormDataParam("file") final FormDataContentDisposition fileMetaData) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
if (uri != null) {
DataTools.uploadCoverFromUri(db, Album.class, id, uri);
} else {
DataTools.uploadCover(db, Album.class, id, fileInputStream, fileMetaData);
}
return db.get(Album.class, id);
}
}
@GET
@Path("{id}/rm_track/{trackId}")
@DELETE
@Path("{id}/cover/{coverId}")
@RolesAllowed("ADMIN")
public Album removeTrack(@PathParam("id") final Long id, @PathParam("trackId") final Long trackId) throws Exception {
AddOnManyToMany.removeLink(Album.class, id, "track", trackId);
return DataAccess.get(Album.class, id);
}
@POST
@Path("{id}/add_cover")
@RolesAllowed("ADMIN")
@Consumes({ MediaType.MULTIPART_FORM_DATA })
public Response uploadCover(@PathParam("id") final Long id, @FormDataParam("fileName") final String fileName, @FormDataParam("file") final InputStream fileInputStream,
@FormDataParam("file") final FormDataContentDisposition fileMetaData) {
return DataTools.uploadCover(Album.class, id, fileName, fileInputStream, fileMetaData);
}
@GET
@Path("{id}/rm_cover/{coverId}")
@RolesAllowed("ADMIN")
public Response removeCover(@PathParam("id") final Long id, @PathParam("coverId") final Long coverId) throws Exception {
AddOnManyToMany.removeLink(Album.class, id, "cover", coverId);
return Response.ok(DataAccess.get(Album.class, id)).build();
@Operation(description = "Remove a cover on a specific album")
public Album removeCover(@PathParam("id") final Long id, @PathParam("coverId") final UUID coverId) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
AddOnDataJson.removeLink(db, Album.class, "id", id, "covers", coverId);
return db.get(Album.class, id);
}
}
}

View File

@@ -3,12 +3,21 @@ package org.kar.karusic.api;
import java.io.InputStream;
import java.util.List;
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.FormDataOptional;
import org.kar.archidata.annotation.TypeScriptProgress;
import org.kar.archidata.dataAccess.DBAccess;
import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.addOn.AddOnManyToMany;
import org.kar.archidata.dataAccess.addOnSQL.AddOnDataJson;
import org.kar.archidata.dataAccess.options.CheckFunction;
import org.kar.archidata.tools.DataTools;
import org.kar.karusic.model.Artist;
import org.kar.karusic.model.Artist.ArtistChecker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.Consumes;
@@ -20,63 +29,73 @@ import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@Path("/artist")
@Produces({ MediaType.APPLICATION_JSON })
public class ArtistResource {
private static final Logger LOGGER = LoggerFactory.getLogger(ArtistResource.class);
static final ArtistChecker CHECKER = new ArtistChecker();
@GET
@Path("{id}")
@RolesAllowed("USER")
public static Artist getWithId(@PathParam("id") final Long id) throws Exception {
public Artist get(@PathParam("id") final Long id) throws Exception {
return DataAccess.get(Artist.class, id);
}
@GET
@RolesAllowed("USER")
public List<Artist> get() throws Exception {
public List<Artist> gets() throws Exception {
return DataAccess.gets(Artist.class);
}
@POST
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
public Artist put(final String jsonRequest) throws Exception {
return DataAccess.insertWithJson(Artist.class, jsonRequest);
public Artist post(final Artist data) throws Exception {
return DataAccess.insert(data, new CheckFunction(CHECKER));
}
@PATCH
@Path("{id}")
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
public Artist put(@PathParam("id") final Long id, final String jsonRequest) throws Exception {
DataAccess.updateWithJson(Artist.class, id, jsonRequest);
public Artist patch(@PathParam("id") final Long id, @AsyncType(Artist.class) final String jsonRequest) throws Exception {
DataAccess.updateWithJson(Artist.class, id, jsonRequest, new CheckFunction(CHECKER));
return DataAccess.get(Artist.class, id);
}
@DELETE
@Path("{id}")
@RolesAllowed("ADMIN")
public Response delete(@PathParam("id") final Long id) throws Exception {
public void remove(@PathParam("id") final Long id) throws Exception {
DataAccess.delete(Artist.class, id);
return Response.ok().build();
}
@POST
@Path("{id}/add_cover")
@Path("{id}/cover")
@RolesAllowed("ADMIN")
@Consumes({ MediaType.MULTIPART_FORM_DATA })
public Response uploadCover(@PathParam("id") final Long id, @FormDataParam("fileName") final String fileName, @FormDataParam("file") final InputStream fileInputStream,
@FormDataParam("file") final FormDataContentDisposition fileMetaData) {
return DataTools.uploadCover(Artist.class, id, fileName, fileInputStream, fileMetaData);
@TypeScriptProgress
public Artist uploadCover(@PathParam("id") final Long id, @FormDataOptional @FormDataParam("uri") final String uri, @FormDataOptional @FormDataParam("file") final InputStream fileInputStream,
@FormDataOptional @FormDataParam("file") final FormDataContentDisposition fileMetaData) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
if (uri != null) {
DataTools.uploadCoverFromUri(db, Artist.class, id, uri);
} else {
DataTools.uploadCover(db, Artist.class, id, fileInputStream, fileMetaData);
}
return db.get(Artist.class, id);
}
}
@GET
@Path("{id}/rm_cover/{coverId}")
@DELETE
@Path("{id}/cover/{coverId}")
@RolesAllowed("ADMIN")
public Response removeCover(@PathParam("id") final Long id, @PathParam("coverId") final Long coverId) throws Exception {
AddOnManyToMany.removeLink(Artist.class, id, "cover", coverId);
return Response.ok(DataAccess.get(Artist.class, id)).build();
public Artist removeCover(@PathParam("id") final Long id, @PathParam("coverId") final ObjectId coverId) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
AddOnDataJson.removeLink(db, Artist.class, "id", id, "covers", coverId);
return db.get(Artist.class, id);
}
}
}

View File

@@ -3,12 +3,21 @@ package org.kar.karusic.api;
import java.io.InputStream;
import java.util.List;
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.FormDataOptional;
import org.kar.archidata.annotation.TypeScriptProgress;
import org.kar.archidata.dataAccess.DBAccess;
import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.addOn.AddOnManyToMany;
import org.kar.archidata.dataAccess.addOnSQL.AddOnDataJson;
import org.kar.archidata.dataAccess.options.CheckFunction;
import org.kar.archidata.tools.DataTools;
import org.kar.karusic.model.Gender;
import org.kar.karusic.model.Gender.GenderChecker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.Consumes;
@@ -20,63 +29,73 @@ import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@Path("/gender")
@Produces({ MediaType.APPLICATION_JSON })
public class GenderResource {
private static final Logger LOGGER = LoggerFactory.getLogger(GenderResource.class);
static final GenderChecker CHECKER = new GenderChecker();
@GET
@Path("{id}")
@RolesAllowed("USER")
public static Gender getWithId(@PathParam("id") final Long id) throws Exception {
public Gender get(@PathParam("id") final Long id) throws Exception {
return DataAccess.get(Gender.class, id);
}
@GET
@RolesAllowed("USER")
public List<Gender> get() throws Exception {
public List<Gender> gets() throws Exception {
return DataAccess.gets(Gender.class);
}
@POST
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
public Gender put(final String jsonRequest) throws Exception {
return DataAccess.insertWithJson(Gender.class, jsonRequest);
public Gender post(final Gender data) throws Exception {
return DataAccess.insert(data, new CheckFunction(CHECKER));
}
@PATCH
@Path("{id}")
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
public Gender put(@PathParam("id") final Long id, final String jsonRequest) throws Exception {
DataAccess.updateWithJson(Gender.class, id, jsonRequest);
public Gender patch(@PathParam("id") final Long id, @AsyncType(Gender.class) final String jsonRequest) throws Exception {
DataAccess.updateWithJson(Gender.class, id, jsonRequest, new CheckFunction(CHECKER));
return DataAccess.get(Gender.class, id);
}
@DELETE
@Path("{id}")
@RolesAllowed("ADMIN")
public Response delete(@PathParam("id") final Long id) throws Exception {
public void remove(@PathParam("id") final Long id) throws Exception {
DataAccess.delete(Gender.class, id);
return Response.ok().build();
}
@POST
@Path("{id}/add_cover")
@Path("{id}/cover")
@RolesAllowed("ADMIN")
@Consumes({ MediaType.MULTIPART_FORM_DATA })
public Response uploadCover(@PathParam("id") final Long id, @FormDataParam("fileName") final String fileName, @FormDataParam("file") final InputStream fileInputStream,
@FormDataParam("file") final FormDataContentDisposition fileMetaData) {
return DataTools.uploadCover(Gender.class, id, fileName, fileInputStream, fileMetaData);
@TypeScriptProgress
public Gender uploadCover(@PathParam("id") final Long id, @FormDataOptional @FormDataParam("uri") final String uri, @FormDataOptional @FormDataParam("file") final InputStream fileInputStream,
@FormDataOptional @FormDataParam("file") final FormDataContentDisposition fileMetaData) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
if (uri != null) {
DataTools.uploadCoverFromUri(db, Gender.class, id, uri);
} else {
DataTools.uploadCover(db, Gender.class, id, fileInputStream, fileMetaData);
}
return db.get(Gender.class, id);
}
}
@GET
@Path("{id}/rm_cover/{coverId}")
@DELETE
@Path("{id}/cover/{coverId}")
@RolesAllowed("ADMIN")
public Response removeCover(@PathParam("id") final Long id, @PathParam("coverId") final Long coverId) throws Exception {
AddOnManyToMany.removeLink(Gender.class, id, "cover", coverId);
return Response.ok(DataAccess.get(Gender.class, id)).build();
public Gender removeCover(@PathParam("id") final Long id, @PathParam("coverId") final ObjectId coverId) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
AddOnDataJson.removeLink(db, Gender.class, "id", id, "covers", coverId);
return db.get(Gender.class, id);
}
}
}

View File

@@ -15,8 +15,8 @@ import jakarta.ws.rs.core.Response;
@Produces(MediaType.APPLICATION_JSON)
public class HealthCheck {
public record HealthResult(
String value) {};
public record HealthResult(String value) {
};
@GET
@PermitAll

View File

@@ -3,12 +3,19 @@ package org.kar.karusic.api;
import java.io.InputStream;
import java.util.List;
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.dataAccess.DBAccess;
import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.addOn.AddOnManyToMany;
import org.kar.archidata.dataAccess.addOnSQL.AddOnDataJson;
import org.kar.archidata.dataAccess.options.CheckFunction;
import org.kar.archidata.tools.DataTools;
import org.kar.karusic.model.Playlist;
import org.kar.karusic.model.Track.TrackChecker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.Consumes;
@@ -20,80 +27,89 @@ import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@Path("/playlist")
@Produces({ MediaType.APPLICATION_JSON })
public class PlaylistResource {
private static final Logger LOGGER = LoggerFactory.getLogger(PlaylistResource.class);
static final TrackChecker CHECKER = new TrackChecker();
@GET
@Path("{id}")
@RolesAllowed("USER")
public static Playlist getWithId(@PathParam("id") final Long id) throws Exception {
public Playlist get(@PathParam("id") final Long id) throws Exception {
return DataAccess.get(Playlist.class, id);
}
@GET
@RolesAllowed("USER")
public List<Playlist> get() throws Exception {
public List<Playlist> gets() throws Exception {
return DataAccess.gets(Playlist.class);
}
@POST
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
public Playlist put(final String jsonRequest) throws Exception {
return DataAccess.insertWithJson(Playlist.class, jsonRequest);
public Playlist post(final Playlist data) throws Exception {
return DataAccess.insert(data, new CheckFunction(CHECKER));
}
@PATCH
@Path("{id}")
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
public Playlist put(@PathParam("id") final Long id, final String jsonRequest) throws Exception {
DataAccess.updateWithJson(Playlist.class, id, jsonRequest);
public Playlist patch(@PathParam("id") final Long id, @AsyncType(Playlist.class) final String jsonRequest) throws Exception {
DataAccess.updateWithJson(Playlist.class, id, jsonRequest, new CheckFunction(CHECKER));
return DataAccess.get(Playlist.class, id);
}
@DELETE
@Path("{id}")
@RolesAllowed("ADMIN")
public Response delete(@PathParam("id") final Long id) throws Exception {
public void remove(@PathParam("id") final Long id) throws Exception {
DataAccess.delete(Playlist.class, id);
return Response.ok().build();
}
@POST
@Path("{id}/add_track/{trackId}")
@Path("{id}/track/{trackId}")
@RolesAllowed("ADMIN")
@Consumes({ MediaType.MULTIPART_FORM_DATA })
public Playlist addTrack(@PathParam("id") final Long id, @PathParam("trackId") final Long trackId) throws Exception {
AddOnManyToMany.removeLink(Playlist.class, id, "track", trackId);
return DataAccess.get(Playlist.class, id);
try (DBAccess db = DBAccess.createInterface()) {
AddOnDataJson.removeLink(db, Playlist.class, "id", id, "track", trackId);
return db.get(Playlist.class, id);
}
}
@GET
@Path("{id}/rm_track/{trackId}")
@DELETE
@Path("{id}/track/{trackId}")
@RolesAllowed("ADMIN")
public Playlist removeTrack(@PathParam("id") final Long id, @PathParam("trackId") final Long trackId) throws Exception {
AddOnManyToMany.removeLink(Playlist.class, id, "track", trackId);
return DataAccess.get(Playlist.class, id);
try (DBAccess db = DBAccess.createInterface()) {
AddOnDataJson.removeLink(db, Playlist.class, "id", id, "track", trackId);
return db.get(Playlist.class, id);
}
}
@POST
@Path("{id}/add_cover")
@Path("{id}/cover")
@RolesAllowed("ADMIN")
@Consumes({ MediaType.MULTIPART_FORM_DATA })
public Response uploadCover(@PathParam("id") final Long id, @FormDataParam("fileName") final String fileName, @FormDataParam("file") final InputStream fileInputStream,
@FormDataParam("file") final FormDataContentDisposition fileMetaData) {
return DataTools.uploadCover(Playlist.class, id, fileName, fileInputStream, fileMetaData);
@AsyncType(Playlist.class)
public void uploadCover(@PathParam("id") final Long id, @FormDataParam("file") final InputStream fileInputStream, @FormDataParam("file") final FormDataContentDisposition fileMetaData)
throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
DataTools.uploadCover(db, Playlist.class, id, fileInputStream, fileMetaData);
}
}
@GET
@Path("{id}/rm_cover/{coverId}")
@DELETE
@Path("{id}/cover/{coverId}")
@RolesAllowed("ADMIN")
public Response removeCover(@PathParam("id") final Long id, @PathParam("coverId") final Long coverId) throws Exception {
AddOnManyToMany.removeLink(Playlist.class, id, "cover", coverId);
return Response.ok(DataAccess.get(Playlist.class, id)).build();
public Playlist removeCover(@PathParam("id") final Long id, @PathParam("coverId") final ObjectId coverId) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
AddOnDataJson.removeLink(db, Playlist.class, "id", id, "covers", coverId);
return DataAccess.get(Playlist.class, id);
}
}
}

View File

@@ -6,18 +6,22 @@ import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
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.FormDataOptional;
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.addOn.AddOnManyToMany;
import org.kar.archidata.dataAccess.options.Condition;
import org.kar.archidata.dataAccess.addOnSQL.AddOnDataJson;
import org.kar.archidata.dataAccess.options.CheckFunction;
import org.kar.archidata.model.Data;
import org.kar.archidata.tools.DataTools;
import org.kar.karusic.model.Album;
import org.kar.karusic.model.Artist;
import org.kar.karusic.model.Gender;
import org.kar.karusic.model.Track;
import org.kar.karusic.model.Track.TrackChecker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.Consumes;
@@ -34,123 +38,135 @@ import jakarta.ws.rs.core.Response;
@Path("/track")
@Produces({ MediaType.APPLICATION_JSON })
public class TrackResource {
private static final Logger LOGGER = LoggerFactory.getLogger(TrackResource.class);
static final TrackChecker CHECKER = new TrackChecker();
@GET
@Path("{id}")
@RolesAllowed("USER")
public static Track getWithId(@PathParam("id") final Long id) throws Exception {
public Track get(@PathParam("id") final Long id) throws Exception {
return DataAccess.get(Track.class, id);
}
@GET
@RolesAllowed("USER")
public List<Track> get() throws Exception {
public List<Track> gets() throws Exception {
return DataAccess.gets(Track.class);
}
@POST
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
public Track create(final String jsonRequest) throws Exception {
return DataAccess.insertWithJson(Track.class, jsonRequest);
public Track post(final Track data) throws Exception {
return DataAccess.insert(data, new CheckFunction(CHECKER));
}
@PATCH
@Path("{id}")
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
public Track put(@PathParam("id") final Long id, final String jsonRequest) throws Exception {
DataAccess.updateWithJson(Track.class, id, jsonRequest);
public Track patch(@PathParam("id") final Long id, @AsyncType(Track.class) final String jsonRequest) throws Exception {
DataAccess.updateWithJson(Track.class, id, jsonRequest, new CheckFunction(CHECKER));
return DataAccess.get(Track.class, id);
}
@DELETE
@Path("{id}")
@RolesAllowed("ADMIN")
public Response delete(@PathParam("id") final Long id) throws Exception {
public void remove(@PathParam("id") final Long id) throws Exception {
DataAccess.delete(Track.class, id);
return Response.ok().build();
}
@POST
@Path("{id}/add_artist/{artistId}")
@Path("{id}/artist/{artistId}")
@RolesAllowed("ADMIN")
@Consumes({ MediaType.MULTIPART_FORM_DATA })
public Track addTrack(@PathParam("id") final Long id, @PathParam("artistId") final Long artistId) throws Exception {
AddOnManyToMany.removeLink(Track.class, id, "artist", artistId);
return DataAccess.get(Track.class, id);
try (DBAccess db = DBAccess.createInterface()) {
AddOnDataJson.removeLink(db, Track.class, "id", id, "artist", artistId);
return DataAccess.get(Track.class, id);
}
}
@GET
@Path("{id}/rm_artist/{trackId}")
@DELETE
@Path("{id}/artist/{trackId}")
@RolesAllowed("ADMIN")
public Track removeTrack(@PathParam("id") final Long id, @PathParam("artistId") final Long artistId) throws Exception {
AddOnManyToMany.removeLink(Track.class, id, "artist", artistId);
return DataAccess.get(Track.class, id);
try (DBAccess db = DBAccess.createInterface()) {
AddOnDataJson.removeLink(db, Track.class, "id", id, "artist", artistId);
return DataAccess.get(Track.class, id);
}
}
@POST
@Path("{id}/add_cover")
@Path("{id}/cover")
@RolesAllowed("ADMIN")
@Consumes({ MediaType.MULTIPART_FORM_DATA })
public Response uploadCover(@PathParam("id") final Long id, @FormDataParam("fileName") final String fileName, @FormDataParam("file") final InputStream fileInputStream,
@FormDataParam("file") final FormDataContentDisposition fileMetaData) {
return DataTools.uploadCover(Track.class, id, fileName, fileInputStream, fileMetaData);
}
@GET
@Path("{id}/rm_cover/{coverId}")
@RolesAllowed("ADMIN")
public Response removeCover(@PathParam("id") final Long id, @PathParam("coverId") final Long coverId) throws Exception {
AddOnManyToMany.removeLink(Track.class, id, "cover", coverId);
return Response.ok(DataAccess.get(Track.class, id)).build();
}
@POST
@Path("/upload/")
@RolesAllowed("ADMIN")
@Consumes({ MediaType.MULTIPART_FORM_DATA })
public Response uploadFile(@FormDataParam("fileName") String fileName, @FormDataParam("gender") String gender, @FormDataParam("artist") String artist,
//@FormDataParam("seriesId") String seriesId, Not used ...
@FormDataParam("album") String album, @FormDataParam("trackId") String trackId, @FormDataParam("title") String title, @FormDataParam("file") final InputStream fileInputStream,
@FormDataParam("file") final FormDataContentDisposition fileMetaData) {
try {
// correct input string stream :
fileName = DataTools.multipartCorrection(fileName);
gender = DataTools.multipartCorrection(gender);
artist = DataTools.multipartCorrection(artist);
album = DataTools.multipartCorrection(album);
trackId = DataTools.multipartCorrection(trackId);
title = DataTools.multipartCorrection(title);
//public NodeSmall uploadFile(final FormDataMultiPart form) {
System.out.println("Upload media file: " + fileMetaData);
System.out.println(" - fileName: " + fileName);
System.out.println(" - gender: " + gender);
System.out.println(" - artist: " + artist);
System.out.println(" - album: " + album);
System.out.println(" - trackId: " + trackId);
System.out.println(" - title: " + title);
System.out.println(" - fileInputStream: " + fileInputStream);
System.out.println(" - fileMetaData: " + fileMetaData);
System.out.flush();
/*
if (typeId == null) {
return Response.status(406).
entity("Missong Input 'type'").
type("text/plain").
build();
@TypeScriptProgress
public Track uploadCover(@PathParam("id") final Long id, @FormDataParam("uri") final String uri, @FormDataParam("file") final InputStream fileInputStream,
@FormDataParam("file") final FormDataContentDisposition fileMetaData) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
if (uri != null) {
DataTools.uploadCoverFromUri(db, Track.class, id, uri);
} else {
DataTools.uploadCover(db, Track.class, id, fileInputStream, fileMetaData);
}
*/
return DataAccess.get(Track.class, id);
}
}
@DELETE
@Path("{id}/cover/{coverId}")
@RolesAllowed("ADMIN")
public Track removeCover(@PathParam("id") final Long id, @PathParam("coverId") final ObjectId coverId) throws Exception {
try (DBAccess db = DBAccess.createInterface()) {
AddOnDataJson.removeLink(db, Track.class, "id", id, "covers", coverId);
return db.get(Track.class, id);
}
}
@POST
@Path("upload/")
@RolesAllowed("ADMIN")
@Consumes({ MediaType.MULTIPART_FORM_DATA })
@AsyncType(Track.class)
@TypeScriptProgress
public Response uploadTrack( //
@FormDataParam("title") String title, //
@FormDataOptional @AsyncType(Long.class) @FormDataParam("genderId") String genderId, //
@FormDataOptional @AsyncType(Long.class) @FormDataParam("artistId") String artistId, //
@FormDataOptional @AsyncType(Long.class) @FormDataParam("albumId") String albumId, //
@FormDataOptional @AsyncType(Long.class) @FormDataParam("trackId") String trackId, //
@FormDataParam("file") final InputStream fileInputStream, //
@FormDataParam("file") final FormDataContentDisposition fileMetaData //
) {
try (DBAccess db = DBAccess.createInterface()) {
// correct input string stream :
trackId = DataTools.multipartCorrection(trackId);
albumId = DataTools.multipartCorrection(albumId);
artistId = DataTools.multipartCorrection(artistId);
genderId = DataTools.multipartCorrection(genderId);
title = DataTools.multipartCorrection(title);
// public NodeSmall uploadFile(final FormDataMultiPart form) {
LOGGER.info("Upload media file: " + fileMetaData);
LOGGER.info(" > genderId: " + genderId);
LOGGER.info(" > artistId: " + artistId);
LOGGER.info(" > albumId: " + albumId);
LOGGER.info(" > trackId: " + trackId);
LOGGER.info(" > title: " + title);
LOGGER.info(" > fileInputStream: " + fileInputStream);
LOGGER.info(" > fileMetaData: " + fileMetaData);
/* if (typeId == null) { return Response.status(406). entity("Missong Input 'type'"). type("text/plain"). build(); } */
final long tmpUID = DataTools.getTmpDataId();
final String sha512 = DataTools.saveTemporaryFile(fileInputStream, tmpUID);
Data data = DataTools.getWithSha512(sha512);
Data data = DataTools.getWithSha512(db, sha512);
if (data == null) {
System.out.println("Need to add the data in the BDD ... ");
System.out.flush();
LOGGER.info("Need to add the data in the BDD ... ");
try {
data = DataTools.createNewData(tmpUID, fileName, sha512);
data = DataTools.createNewData(db, tmpUID, fileMetaData.getFileName(), sha512);
} catch (final IOException ex) {
DataTools.removeTemporaryFile(tmpUID);
ex.printStackTrace();
@@ -161,80 +177,33 @@ public class TrackResource {
return Response.notModified("Error in SQL insertion ...").build();
}
} else if (data.deleted) {
System.out.println("Data already exist but deleted");
System.out.flush();
DataTools.undelete(data.id);
LOGGER.info("Data already exist but deleted");
DataTools.undelete(db, data.oid);
data.deleted = false;
} else {
System.out.println("Data already exist ... all good");
System.out.flush();
LOGGER.info("Data already exist ... all good");
}
// Fist step: retrieve all the Id of each parents:...
System.out.println("Find typeNode");
Gender genderElem = null;
if (gender != null) {
genderElem = DataAccess.getWhere(Gender.class, new Condition(new QueryCondition("name", "=", gender)));
if (genderElem == null) {
genderElem = new Gender();
genderElem.name = gender;
genderElem = DataAccess.insert(genderElem);
}
}
// NodeSmall typeNode = TypeResource.getWithId(Long.parseLong(typeId));
// if (typeNode == null) {
// DataTools.removeTemporaryFile(tmpUID);
// return Response.notModified("TypeId does not exist ...").build();
// }
System.out.println(" ==> " + genderElem);
Artist artistElem = null;
if (artist != null) {
artistElem = DataAccess.getWhere(Artist.class, new Condition(new QueryCondition("name", "=", artist)));
if (artistElem == null) {
artistElem = new Artist();
artistElem.name = artist;
artistElem = DataAccess.insert(artistElem);
}
}
System.out.println(" ==> " + artistElem);
Album albumElem = null;
if (album != null) {
albumElem = DataAccess.getWhere(Album.class, new Condition(new QueryCondition("name", "=", album)));
if (albumElem == null) {
albumElem = new Album();
albumElem.name = album;
albumElem = DataAccess.insert(albumElem);
}
}
System.out.println(" ==> " + album);
System.out.println("add media");
LOGGER.info("add media");
Track trackElem = new Track();
trackElem.name = title;
trackElem.track = trackId != null ? Long.parseLong(trackId) : null;
trackElem.albumId = albumElem != null ? albumElem.id : null;
trackElem.genderId = genderElem != null ? genderElem.id : null;
trackElem.dataId = data.id;
// Now list of artis has an internal management:
if (artistElem != null) {
trackElem.albumId = albumId != null ? Long.parseLong(albumId) : null;
trackElem.genderId = genderId != null ? Long.parseLong(genderId) : null;
trackElem.dataId = data.oid;
// Now list of artist has an internal management:
if (artistId != null) {
trackElem.artists = new ArrayList<>();
trackElem.artists.add(artistElem.id);
trackElem.artists.add(artistId != null ? Long.parseLong(artistId) : null);
}
trackElem = DataAccess.insert(trackElem);
/*
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);
}
*/
trackElem = DataAccess.insert(trackElem, new CheckFunction(CHECKER));
/* Old mode of artist insertion (removed due to the slowlest request of getting value if (artistElem != null) { this.dam.addLink(Track.class, trackElem.id, "artist", artistElem.id); } */
return Response.ok(trackElem).build();
} catch (final Exception ex) {
System.out.println("Catch an unexpected error ... " + ex.getMessage());
LOGGER.info("Catch an unexpected error ... {}", ex.getMessage());
ex.printStackTrace();
return Response.status(417).entity("Back-end error : " + ex.getMessage()).type("text/plain").build();
}
}
}

View File

@@ -1,9 +1,12 @@
package org.kar.karusic.api;
import java.util.List;
import java.util.Map;
import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.filter.GenericContext;
import org.kar.karusic.api.UserResourceModel.PartRight;
import org.kar.karusic.api.UserResourceModel.UserMe;
import org.kar.karusic.model.UserKarusic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -20,29 +23,29 @@ import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.SecurityContext;
@Path("/users")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces(MediaType.APPLICATION_JSON)
public class UserResource {
final Logger logger = LoggerFactory.getLogger(UserResource.class);
private static final Logger LOGGER = LoggerFactory.getLogger(UserResource.class);
@JsonInclude(JsonInclude.Include.NON_NULL)
public class UserOut {
public long id;
public String login;
public UserOut(final long id, final String login) {
this.id = id;
this.login = login;
}
}
public UserResource() {}
// curl http://localhost:9993/api/users
@GET
@RolesAllowed("ADMIN")
public List<UserKarusic> getUsers() {
System.out.println("getUsers");
public List<UserKarusic> gets() {
LOGGER.info("getUsers");
try {
return DataAccess.gets(UserKarusic.class);
} catch (final Exception e) {
@@ -51,17 +54,17 @@ public class UserResource {
}
return null;
}
// curl http://localhost:9993/api/users/3
@GET
@Path("{id}")
@RolesAllowed("ADMIN")
public UserKarusic getUsers(@Context final SecurityContext sc, @PathParam("id") final long userId) {
System.out.println("getUser " + userId);
public UserKarusic get(@Context final SecurityContext sc, @PathParam("id") final long userId) {
LOGGER.info("getUser {}", userId);
final GenericContext gc = (GenericContext) sc.getUserPrincipal();
System.out.println("===================================================");
System.out.println("== USER ? " + gc.userByToken.name);
System.out.println("===================================================");
LOGGER.info("===================================================");
LOGGER.info("== USER {} ", gc.userByToken.name);
LOGGER.info("===================================================");
try {
return DataAccess.get(UserKarusic.class, userId);
} catch (final Exception e) {
@@ -70,14 +73,19 @@ public class UserResource {
}
return null;
}
@GET
@Path("me")
@RolesAllowed("USER")
public UserOut getMe(@Context final SecurityContext sc) {
this.logger.debug("getMe()");
public UserMe getMe(@Context final SecurityContext sc) {
LOGGER.debug("getMe()");
final GenericContext gc = (GenericContext) sc.getUserPrincipal();
this.logger.debug("== USER ? {}", gc.userByToken);
return new UserOut(gc.userByToken.id, gc.userByToken.name);
LOGGER.debug("== USER ? {}", gc.userByToken);
return new UserMe(gc.userByToken.id, gc.userByToken.name, //
Map.of(gc.userByToken.name, //
Map.of("admin", PartRight.READ_WRITE, //
"user", PartRight.READ_WRITE), //
"karusic", //
Map.of("user", PartRight.READ)));
}
}

View File

@@ -0,0 +1,12 @@
package org.kar.karusic.api.UserResourceModel;
import java.util.HashMap;
import org.kar.archidata.annotation.NoWriteSpecificMode;
@NoWriteSpecificMode
public class ModuleAuthorizations extends HashMap<String, PartRight> {
private static final long serialVersionUID = 1L;
public ModuleAuthorizations() {}
}

View File

@@ -0,0 +1,29 @@
package org.kar.karusic.api.UserResourceModel;
import com.fasterxml.jackson.annotation.JsonValue;
public enum PartRight {
READ(1), //
WRITE(2), //
READ_WRITE(3);
private final int value;
PartRight(final int value) {
this.value = value;
}
@JsonValue
public int getValue() {
return this.value;
}
public static PartRight fromValue(final int value) {
for (final PartRight species : PartRight.values()) {
if (species.getValue() == value) {
return species;
}
}
throw new IllegalArgumentException("PartRight: Unknown value: " + value);
}
}

View File

@@ -0,0 +1,24 @@
package org.kar.karusic.api.UserResourceModel;
import java.util.Map;
import org.kar.archidata.annotation.NoWriteSpecificMode;
import io.swagger.v3.oas.annotations.media.Schema;
@NoWriteSpecificMode
public class UserMe {
public long id;
public String login;
@Schema(description = "Map<EntityName, Map<PartName, Right>>")
public Map<String, Map<String, PartRight>> rights;
public UserMe() {}
public UserMe(final long id, final String login, final Map<String, Map<String, PartRight>> rights) {
this.id = id;
this.login = login;
this.rights = rights;
}
}

View File

@@ -15,9 +15,9 @@ import jakarta.annotation.Priority;
@Priority(Priorities.AUTHENTICATION)
public class KarusicAuthenticationFilter extends AuthenticationFilter {
final Logger logger = LoggerFactory.getLogger(KarusicAuthenticationFilter.class);
public KarusicAuthenticationFilter() {
public KarusicAuthenticationFilter() {
super("karusic");
}
}

View File

@@ -1,60 +0,0 @@
package org.kar.karusic.internal;
//import io.scenarium.logger.LogLevel;
//import io.scenarium.logger.Logger;
public class Log {
// private static final String LIB_NAME = "logger";
// private static final String LIB_NAME_DRAW = Logger.getDrawableName(LIB_NAME);
// private static final boolean PRINT_CRITICAL = Logger.getNeedPrint(LIB_NAME, LogLevel.CRITICAL);
// private static final boolean PRINT_ERROR = Logger.getNeedPrint(LIB_NAME, LogLevel.ERROR);
// private static final boolean PRINT_WARNING = Logger.getNeedPrint(LIB_NAME, LogLevel.WARNING);
// private static final boolean PRINT_INFO = Logger.getNeedPrint(LIB_NAME, LogLevel.INFO);
// private static final boolean PRINT_DEBUG = Logger.getNeedPrint(LIB_NAME, LogLevel.DEBUG);
// private static final boolean PRINT_VERBOSE = Logger.getNeedPrint(LIB_NAME, LogLevel.VERBOSE);
// private static final boolean PRINT_TODO = Logger.getNeedPrint(LIB_NAME, LogLevel.TODO);
// private static final boolean PRINT_PRINT = Logger.getNeedPrint(LIB_NAME, LogLevel.PRINT);
//
// private Log() {}
//
// public static void print(String data) {
// if (PRINT_PRINT)
// Logger.print(LIB_NAME_DRAW, data);
// }
//
// public static void todo(String data) {
// if (PRINT_TODO)
// Logger.todo(LIB_NAME_DRAW, data);
// }
//
// public static void critical(String data) {
// if (PRINT_CRITICAL)
// Logger.critical(LIB_NAME_DRAW, data);
// }
//
// public static void error(String data) {
// if (PRINT_ERROR)
// Logger.error(LIB_NAME_DRAW, data);
// }
//
// public static void warning(String data) {
// if (PRINT_WARNING)
// Logger.warning(LIB_NAME_DRAW, data);
// }
//
// public static void info(String data) {
// if (PRINT_INFO)
// Logger.info(LIB_NAME_DRAW, data);
// }
//
// public static void debug(String data) {
// if (PRINT_DEBUG)
// Logger.debug(LIB_NAME_DRAW, data);
// }
//
// public static void verbose(String data) {
// if (PRINT_VERBOSE)
// Logger.verbose(LIB_NAME_DRAW, data);
// }
}

View File

@@ -1,5 +1,8 @@
package org.kar.karusic.migration;
import java.util.List;
import org.kar.archidata.dataAccess.DBAccess;
import org.kar.archidata.migration.MigrationSqlStep;
import org.kar.archidata.model.Data;
import org.kar.archidata.model.User;
@@ -8,59 +11,56 @@ import org.kar.karusic.model.Artist;
import org.kar.karusic.model.Gender;
import org.kar.karusic.model.Playlist;
import org.kar.karusic.model.Track;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Initialization extends MigrationSqlStep {
private static final Logger LOGGER = LoggerFactory.getLogger(Initialization.class);
public static final int KARSO_INITIALISATION_ID = 1;
public static final List<Class<?>> CLASSES_BASE = List.of(Album.class, Artist.class, Data.class, Gender.class, Playlist.class, Track.class, User.class);
@Override
public String getName() {
return "Initialization";
}
public Initialization() {
}
@Override
public void generateStep() throws Exception {
addClass(Album.class);
addClass(Artist.class);
addClass(Data.class);
addClass(Gender.class);
addClass(Playlist.class);
addClass(Track.class);
addClass(User.class);
addAction("""
INSERT INTO `gender` (`id`, `name`, `description`) VALUES
(1, 'Variété française', NULL),
(2, 'Pop', NULL),
(3, 'inconnue', NULL),
(4, 'Disco', NULL),
(5, 'Enfants', NULL),
(6, 'Portugaise', NULL),
(7, 'Apprentissage', NULL),
(8, 'Blues', NULL),
(9, 'Jazz', NULL),
(10, 'Chanson Noël', NULL),
(11, 'DubStep', NULL),
(12, 'Rap français', NULL),
(13, 'Classique', NULL),
(14, 'Rock', NULL),
(15, 'Electro', NULL),
(16, 'Celtique', NULL),
(17, 'Country', NULL),
(18, 'Variété Québéquoise', NULL),
(19, 'Médiéval', NULL),
(20, 'Variété Italienne', NULL),
(21, 'Comédie Musicale', NULL),
(22, 'Vianney', NULL),
(23, 'Bande Original', NULL),
(24, 'Bande Originale', NULL),
(25, 'Variété Belge', NULL),
(26, 'Gospel', NULL);
""");
for (final Class<?> clazz : CLASSES_BASE) {
addClass(clazz);
}
addAction((final DBAccess da) -> {
final List<Gender> data = List.of(//
new Gender(1L, "Variété française"), //
new Gender(2L, "Pop"), //
new Gender(3L, "inconnue"), //
new Gender(4L, "Disco"), //
new Gender(5L, "Enfants"), //
new Gender(6L, "Portugaise"), //
new Gender(7L, "Apprentissage"), //
new Gender(8L, "Blues"), //
new Gender(9L, "Jazz"), //
new Gender(10L, "Chanson Noël"), //
new Gender(11L, "DubStep"), //
new Gender(12L, "Rap français"), //
new Gender(13L, "Classique"), //
new Gender(14L, "Rock"), //
new Gender(15L, "Electro"), //
new Gender(16L, "Celtique"), //
new Gender(17L, "Country"), //
new Gender(18L, "Variété Québéquoise"), //
new Gender(19L, "Médiéval"), //
new Gender(20L, "Variété Italienne"), //
new Gender(21L, "Comédie Musicale"), //
new Gender(22L, "Vianney"), //
new Gender(23L, "Bande Original"), //
new Gender(24L, "Bande Originale"), //
new Gender(25L, "Variété Belge"), //
new Gender(26L, "Gospel"));
da.insertMultiple(data);
});
// set start increment element to permit to add after default elements
addAction("""
ALTER TABLE `album` AUTO_INCREMENT = 1000;
@@ -68,9 +68,6 @@ public class Initialization extends MigrationSqlStep {
addAction("""
ALTER TABLE `artist` AUTO_INCREMENT = 1000;
""", "mysql");
addAction("""
ALTER TABLE `data` AUTO_INCREMENT = 1000;
""", "mysql");
addAction("""
ALTER TABLE `gender` AUTO_INCREMENT = 1000;
""", "mysql");
@@ -84,5 +81,26 @@ public class Initialization extends MigrationSqlStep {
ALTER TABLE `user` AUTO_INCREMENT = 1000;
""", "mysql");
}
public static void dropAll(final DBAccess da) {
for (final Class<?> element : CLASSES_BASE) {
try {
da.drop(element);
} catch (final Exception ex) {
LOGGER.error("Fail to drop table !!!!!!");
ex.printStackTrace();
}
}
}
public static void cleanAll(final DBAccess da) {
for (final Class<?> element : CLASSES_BASE) {
try {
da.cleanAll(element);
} catch (final Exception ex) {
LOGGER.error("Fail to clean table !!!!!!");
ex.printStackTrace();
}
}
}
}

View File

@@ -11,159 +11,4 @@ public class Migration20231126 extends MigrationSqlStep {
return "migration-2023-11-26: reorder the migration for the new API of archidata";
}
public Migration20231126() {
}
@Override
public void generateStep() throws Exception {
// update migration update (last one)
addAction("""
ALTER TABLE `KAR_migration`
CHANGE `id` `id` bigint NOT NULL COMMENT 'Primary key of the base' AUTO_INCREMENT FIRST,
CHANGE `create_date` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Create time of the object' AFTER `id`,
CHANGE `modify_date` `updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'When update the object' AFTER `createdAt`,
CHANGE `deleted` `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'When delete, they are not removed, they are just set in a deleted state' AFTER `updatedAt`,
ADD `version` int NOT NULL DEFAULT '2' AFTER `deleted`,
CHANGE `name` `name` varchar(256) COLLATE 'utf8mb4_0900_ai_ci' NULL COMMENT 'Name of the migration' AFTER `version`,
CHANGE `terminated` `terminated` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'if the migration is well terminated or not' AFTER `name`,
CHANGE `stepId` `stepId` int NULL COMMENT 'index in the migration progression' AFTER `terminated`,
CHANGE `count` `count` int NULL COMMENT 'number of element in the migration' AFTER `stepId`,
CHANGE `log` `log` text COLLATE 'utf8mb3_general_ci' NULL COMMENT 'Log generate by the migration' AFTER `count`;
""");
addAction("""
ALTER TABLE `album`
CHANGE `id` `id` bigint NOT NULL COMMENT 'Primary key of the base' AUTO_INCREMENT FIRST,
CHANGE `create_date` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Create time of the object' AFTER `id`,
CHANGE `modify_date` `updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'When update the object' AFTER `createdAt`,
CHANGE `deleted` `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'When delete, they are not removed, they are just set in a deleted state' AFTER `updatedAt`,
CHANGE `name` `name` varchar(256) COLLATE 'utf8mb4_0900_ai_ci' NULL AFTER `deleted`,
CHANGE `description` `description` text COLLATE 'utf8mb3_general_ci' NULL AFTER `name`,
CHANGE `publication` `publication` date NULL AFTER `description`;
""");
addAction("""
ALTER TABLE `album_link_cover`
CHANGE `create_date` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) AFTER `id`,
CHANGE `modify_date` `updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) AFTER `createdAt`,
CHANGE `deleted` `deleted` tinyint(1) NOT NULL DEFAULT '0' AFTER `updatedAt`,
CHANGE `album_id` `object1id` bigint NOT NULL AFTER `deleted`,
CHANGE `cover_id` `object2id` bigint NOT NULL AFTER `object1id`;
""");
addAction("""
ALTER TABLE `artist`
CHANGE `id` `id` bigint NOT NULL COMMENT 'Primary key of the base' AUTO_INCREMENT FIRST,
CHANGE `create_date` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Create time of the object' AFTER `id`,
CHANGE `modify_date` `updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'When update the object' AFTER `createdAt`,
CHANGE `deleted` `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'When delete, they are not removed, they are just set in a deleted state' AFTER `updatedAt`,
CHANGE `firstName` `firstName` varchar(256) COLLATE 'utf8mb4_0900_ai_ci' NULL AFTER `description`,
CHANGE `surname` `surname` varchar(256) COLLATE 'utf8mb4_0900_ai_ci' NULL AFTER `firstName`,
CHANGE `birth` `birth` date NULL AFTER `surname`,
CHANGE `death` `death` date NULL AFTER `birth`;
""");
addAction("""
ALTER TABLE `artist_link_cover`
CHANGE `create_date` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) AFTER `id`,
CHANGE `modify_date` `updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) AFTER `createdAt`,
CHANGE `deleted` `deleted` tinyint(1) NOT NULL DEFAULT '0' AFTER `updatedAt`,
CHANGE `artist_id` `object1id` bigint NOT NULL AFTER `deleted`,
CHANGE `cover_id` `object2id` bigint NOT NULL AFTER `object1id`;
""");
addAction("""
ALTER TABLE `data`
CHANGE `id` `id` bigint NOT NULL COMMENT 'Primary key of the base' AUTO_INCREMENT FIRST,
CHANGE `create_date` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Create time of the object' AFTER `id`,
CHANGE `modify_date` `updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'When update the object' AFTER `createdAt`,
CHANGE `deleted` `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'When delete, they are not removed, they are just set in a deleted state' AFTER `updatedAt`,
CHANGE `sha512` `sha512` varchar(128) COLLATE 'utf8mb4_0900_ai_ci' NOT NULL COMMENT 'Sha512 of the data' AFTER `deleted`,
CHANGE `mimeType` `mimeType` varchar(128) COLLATE 'utf8mb4_0900_ai_ci' NOT NULL COMMENT 'Mime -type of the media' AFTER `sha512`,
CHANGE `size` `size` bigint NOT NULL COMMENT 'Size in Byte of the data' AFTER `mimeType`;
""");
addAction("""
ALTER TABLE `gender`
CHANGE `id` `id` bigint NOT NULL COMMENT 'Primary key of the base' AUTO_INCREMENT FIRST,
CHANGE `create_date` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Create time of the object' AFTER `id`,
CHANGE `modify_date` `updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'When update the object' AFTER `createdAt`,
CHANGE `name` `name` varchar(256) COLLATE 'utf8mb4_0900_ai_ci' NULL AFTER `deleted`,
CHANGE `description` `description` text COLLATE 'utf8mb3_general_ci' NULL AFTER `name`;
""");
addAction("""
ALTER TABLE `gender_link_cover`
CHANGE `create_date` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) AFTER `id`,
CHANGE `modify_date` `updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) AFTER `createdAt`,
CHANGE `deleted` `deleted` tinyint(1) NOT NULL DEFAULT '0' AFTER `updatedAt`,
CHANGE `gender_id` `object1id` bigint NOT NULL AFTER `deleted`,
CHANGE `cover_id` `object2id` bigint NOT NULL AFTER `object1id`;
""");
addAction("""
ALTER TABLE `playlist`
CHANGE `id` `id` bigint NOT NULL COMMENT 'Primary key of the base' AUTO_INCREMENT FIRST,
CHANGE `create_date` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Create time of the object' AFTER `id`,
CHANGE `modify_date` `updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'When update the object' AFTER `createdAt`,
CHANGE `name` `name` varchar(256) COLLATE 'utf8mb4_0900_ai_ci' NULL AFTER `deleted`,
CHANGE `description` `description` text COLLATE 'utf8mb3_general_ci' NULL AFTER `name`;
""");
addAction("""
ALTER TABLE `playlist_link_cover`
CHANGE `create_date` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) AFTER `id`,
CHANGE `modify_date` `updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) AFTER `createdAt`,
CHANGE `deleted` `deleted` tinyint(1) NOT NULL DEFAULT '0' AFTER `updatedAt`,
CHANGE `playlist_id` `object1id` bigint NOT NULL AFTER `deleted`,
CHANGE `cover_id` `object2id` bigint NOT NULL AFTER `object1id`;
""");
addAction("""
ALTER TABLE `playlist_link_track`
CHANGE `create_date` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) AFTER `id`,
CHANGE `modify_date` `updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) AFTER `createdAt`,
CHANGE `deleted` `deleted` tinyint(1) NOT NULL DEFAULT '0' AFTER `updatedAt`,
CHANGE `playlist_id` `object1id` bigint NOT NULL AFTER `deleted`,
CHANGE `track_id` `object2id` bigint NOT NULL AFTER `object1id`;
""");
addAction("""
ALTER TABLE `track`
CHANGE `id` `id` bigint NOT NULL COMMENT 'Primary key of the base' AUTO_INCREMENT FIRST,
CHANGE `create_date` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Create time of the object' AFTER `id`,
CHANGE `modify_date` `updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'When update the object' AFTER `createdAt`,
CHANGE `deleted` `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'When delete, they are not removed, they are just set in a deleted state' AFTER `updatedAt`,
CHANGE `name` `name` varchar(256) COLLATE 'utf8mb4_0900_ai_ci' NULL AFTER `deleted`,
CHANGE `description` `description` text COLLATE 'utf8mb3_general_ci' NULL AFTER `name`,
CHANGE `genderId` `genderId` bigint NULL AFTER `description`,
CHANGE `albumId` `albumId` bigint NULL AFTER `genderId`,
CHANGE `track` `track` bigint NULL AFTER `albumId`,
CHANGE `dataId` `dataId` bigint NULL AFTER `track`,
CHANGE `artists` `artists` text COLLATE 'utf8mb4_0900_ai_ci' NULL AFTER `dataId`;
""");
addAction("""
ALTER TABLE `track_link_cover`
CHANGE `create_date` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) AFTER `id`,
CHANGE `modify_date` `updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) AFTER `createdAt`,
CHANGE `deleted` `deleted` tinyint(1) NOT NULL DEFAULT '0' AFTER `updatedAt`,
CHANGE `track_id` `object1id` bigint NOT NULL AFTER `deleted`,
CHANGE `cover_id` `object2id` bigint NOT NULL AFTER `object1id`;
""");
addAction("""
ALTER TABLE `user`
CHANGE `id` `id` bigint NOT NULL COMMENT 'Primary key of the base' AUTO_INCREMENT FIRST,
CHANGE `create_date` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Create time of the object' AFTER `id`,
CHANGE `modify_date` `updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'When update the object' AFTER `createdAt`,
CHANGE `deleted` `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'When delete, they are not removed, they are just set in a deleted state' AFTER `updatedAt`,
CHANGE `login` `login` varchar(128) COLLATE 'utf8mb4_0900_ai_ci' NULL AFTER `deleted`,
CHANGE `lastConnection` `lastConnection` timestamp(3) NULL AFTER `login`,
CHANGE `admin` `admin` tinyint(1) NOT NULL DEFAULT '0' AFTER `lastConnection`,
CHANGE `blocked` `blocked` tinyint(1) NOT NULL DEFAULT '0' AFTER `admin`,
CHANGE `removed` `removed` tinyint(1) NOT NULL DEFAULT '0' AFTER `blocked`;
""");
addAction("""
CREATE TABLE `user_link_cover` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'Primary key of the base' ,
`createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Create time of the object' ,
`updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT 'When update the object' ,
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'When delete, they are not removed, they are just set in a deleted state' ,
`object1Id` bigint NOT NULL COMMENT 'Object reference 1' ,
`object2Id` bigint NOT NULL COMMENT 'Object reference 2' ,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
""");
}
}

View File

@@ -0,0 +1,14 @@
package org.kar.karusic.migration;
import org.kar.archidata.migration.MigrationSqlStep;
public class Migration20240225 extends MigrationSqlStep {
public static final int KARSO_INITIALISATION_ID = 1;
@Override
public String getName() {
return "migration-2024-02-25: change model of thrack to use real json";
}
}

View File

@@ -0,0 +1,17 @@
package org.kar.karusic.migration;
import org.kar.archidata.migration.MigrationSqlStep;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Migration20240226 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-2024-02-26: convert base with UUID";
}
}

View File

@@ -0,0 +1,34 @@
package org.kar.karusic.migration;
import org.kar.archidata.migration.MigrationSqlStep;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Migration20240907 extends MigrationSqlStep {
private static final Logger LOGGER = LoggerFactory.getLogger(Migration20240907.class);
public static final int KARSO_INITIALISATION_ID = 1;
@Override
public String getName() {
return "migration-2024-09-07: convert data id in uuid";
}
public Migration20240907() {
}
@Override
public void generateStep() throws Exception {
addAction("""
ALTER TABLE `data` DROP INDEX `PRIMARY`;
""");
addAction("""
ALTER TABLE `data` CHANGE `id` `uuid` binary(16) DEFAULT (UUID_TO_BIN(UUID(), TRUE));
""");
addAction("""
ALTER TABLE `data` ADD PRIMARY KEY `uuid` (`uuid`);
""");
}
}

View File

@@ -0,0 +1,144 @@
package org.kar.karusic.migration;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
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.karusic.migration.model.CoverConversion;
import org.kar.karusic.migration.model.MediaConversion;
import org.kar.karusic.migration.model.OIDConversion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Migration20250104 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-01-04: convert base from UUID to OID";
}
@Override
public void generateStep() throws Exception {
// Create a simple function to create objectId in the DB (for manual insertion ...)
// addAction("""
// DELIMITER //
//
// CREATE FUNCTION generate_objectid()
// RETURNS BINARY(12)
// DETERMINISTIC
// BEGIN
// DECLARE ts BINARY(4);
// DECLARE random_part BINARY(5);
// DECLARE counter BINARY(3);
// SET ts = UNHEX(HEX(UNIX_TIMESTAMP()));
// SET random_part = UNHEX(HEX(FLOOR(RAND() * POW(2, 40))));
// SET counter = UNHEX(HEX(FLOOR(RAND() * POW(2, 24))));
// RETURN CONCAT(ts, random_part, counter);
// END //
//
// DELIMITER ;
// """);
addAction("""
ALTER TABLE `data` ADD `_id` binary(12) AFTER `uuid`;
""");
addAction((final DBAccess da) -> {
final List<OIDConversion> datas = da.gets(OIDConversion.class, new AccessDeletedItems(), new OverrideTableName("data"));
for (final OIDConversion elem : datas) {
elem._id = new ObjectId();
}
for (final OIDConversion elem : datas) {
da.update(elem, elem.uuid, List.of("_id"), new OverrideTableName("data"));
}
});
final List<String> tableToTransform = List.of("album", "artist", "gender", "track", "user");
for (final String tableName : tableToTransform) {
addAction("ALTER TABLE `" + tableName + "` ADD `covers_oid` text NULL;");
addAction((final DBAccess da) -> {
final List<OIDConversion> datas = da.gets(OIDConversion.class, new AccessDeletedItems(), new OverrideTableName("data"));
final List<CoverConversion> tableCoverTransforms = da.gets(CoverConversion.class, new AccessDeletedItems(), new OverrideTableName(tableName));
LOGGER.info("Get somes data: {} {}", datas.size(), tableCoverTransforms.size());
for (final CoverConversion tableTransform : tableCoverTransforms) {
final List<ObjectId> values = new ArrayList<>();
if (tableTransform.covers == null) {
continue;
}
for (final UUID link : tableTransform.covers) {
for (final OIDConversion data : datas) {
if (data.uuid.equals(link)) {
values.add(data._id);
break;
}
}
}
if (values.size() != 0) {
tableTransform.covers_oid = values;
LOGGER.info(" update: {}: {} => {}", tableTransform.id, tableTransform.covers, tableTransform.covers_oid);
da.update(tableTransform, tableTransform.id, List.of("covers_oid"), new OverrideTableName(tableName));
}
}
});
addAction("ALTER TABLE `" + tableName + "` DROP `covers`;");
addAction("ALTER TABLE `" + tableName + "` CHANGE `covers_oid` `covers` text NULL;");
}
addAction("""
ALTER TABLE `track` ADD `dataOid` binary(12) AFTER dataId;
""");
addAction((final DBAccess da) -> {
final List<OIDConversion> datas = da.gets(OIDConversion.class, new AccessDeletedItems(), new OverrideTableName("data"));
final List<MediaConversion> medias = da.gets(MediaConversion.class, new AccessDeletedItems(), new OverrideTableName("track"));
for (final MediaConversion media : medias) {
for (final OIDConversion data : datas) {
if (data.uuid.equals(media.dataId)) {
media.dataOid = data._id;
da.update(media, media.id, List.of("dataOid"), new OverrideTableName("track"));
break;
}
}
}
});
addAction("""
ALTER TABLE `track` DROP `dataId`;
""");
addAction("""
ALTER TABLE `track` CHANGE `dataOid` `dataId` binary(12) NOT NULL;
""");
// Move the files...
addAction((final DBAccess da) -> {
final List<OIDConversion> datas = da.gets(OIDConversion.class, new AccessDeletedItems(), new OverrideTableName("data"));
for (final OIDConversion data : datas) {
final String origin = DataResource.getFileDataOld(data.uuid);
final String destination = DataResource.getFileData(data._id);
LOGGER.info("move file = {}", origin);
LOGGER.info(" ==> {}", destination);
try {
Files.move(Paths.get(origin), Paths.get(destination), StandardCopyOption.ATOMIC_MOVE);
} catch (final NoSuchFileException ex) {
LOGGER.warn("Fail to move file : {}", ex.getMessage());
}
}
});
addAction("""
ALTER TABLE `data` DROP `uuid`;
""");
// addAction("""
// ALTER TABLE `data` CHANGE `_id` `_id` BINARY(12) DEFAULT (generate_objectid());
// """);
addAction("""
ALTER TABLE `data` ADD PRIMARY KEY `_id` (`_id`);
""");
}
}

View File

@@ -0,0 +1,18 @@
package org.kar.karusic.migration.model;
import java.util.List;
import java.util.UUID;
import org.bson.types.ObjectId;
import org.kar.archidata.annotation.DataJson;
import jakarta.persistence.Id;
public class CoverConversion {
@Id
public Long id = null;
@DataJson
public List<UUID> covers = null;
@DataJson
public List<ObjectId> covers_oid = null;
}

View File

@@ -0,0 +1,14 @@
package org.kar.karusic.migration.model;
import java.util.UUID;
import org.bson.types.ObjectId;
import jakarta.persistence.Id;
public class MediaConversion {
@Id
public Long id = null;
public UUID dataId = null;
public ObjectId dataOid = null;
}

View File

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

View File

@@ -1,16 +1,41 @@
package org.kar.karusic.model;
import java.time.LocalDate;
import java.util.List;
import org.bson.types.ObjectId;
import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.dataAccess.options.CheckJPA;
import org.kar.archidata.model.Data;
import org.kar.archidata.model.GenericDataSoftDelete;
import com.fasterxml.jackson.annotation.JsonInclude;
import dev.morphia.annotations.Entity;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
@Entity("Album")
@Table(name = "album")
@DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Album extends NodeSmall {
public class Album extends GenericDataSoftDelete {
public static class AlbumChecker extends CheckJPA<Album> {
public AlbumChecker() {
super(Album.class);
}
}
@Column(length = 256)
public String name = null;
@Column(length = 0)
public String description = null;
@Schema(description = "List of Id of the specific covers")
@DataJson(targetEntity = Data.class)
@Nullable
public List<ObjectId> covers = null;
public LocalDate publication = null;
}

View File

@@ -1,18 +1,42 @@
package org.kar.karusic.model;
import java.time.LocalDate;
import java.util.List;
import org.bson.types.ObjectId;
import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.dataAccess.options.CheckJPA;
import org.kar.archidata.model.Data;
import org.kar.archidata.model.GenericDataSoftDelete;
import com.fasterxml.jackson.annotation.JsonInclude;
import dev.morphia.annotations.Entity;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
@Entity("Artist")
@Table(name = "artist")
@DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Artist extends NodeSmall {
public class Artist extends GenericDataSoftDelete {
public static class ArtistChecker extends CheckJPA<Artist> {
public ArtistChecker() {
super(Artist.class);
}
}
@Column(length = 256)
public String name = null;
@Column(length = 0)
public String description = null;
@Schema(description = "List of Id of the specific covers")
@DataJson(targetEntity = Data.class)
@Nullable
public List<ObjectId> covers = null;
@Column(length = 256)
public String firstName = null;
@Column(length = 256)

View File

@@ -1,55 +0,0 @@
package org.kar.karusic.model;
/*
CREATE TABLE `data` (
`id` bigint NOT NULL COMMENT 'table ID' AUTO_INCREMENT PRIMARY KEY,
`deleted` BOOLEAN NOT NULL DEFAULT false,
`create_date` datetime NOT NULL DEFAULT now() COMMENT 'Time the element has been created',
`modify_date` datetime NOT NULL DEFAULT now() COMMENT 'Time the element has been update',
`sha512` varchar(129) COLLATE 'utf8_general_ci' NOT NULL,
`mime_type` varchar(128) COLLATE 'utf8_general_ci' NOT NULL,
`size` bigint,
`original_name` TEXT
) AUTO_INCREMENT=64;
*/
import java.sql.ResultSet;
import java.sql.SQLException;
public class DataSmall {
public Long id;
public String sha512;
public String mimeType;
public Long size;
public DataSmall() {
}
public DataSmall(ResultSet rs) {
int iii = 1;
try {
this.id = rs.getLong(iii++);
this.sha512 = rs.getString(iii++);
this.mimeType = rs.getString(iii++);
this.size = rs.getLong(iii++);
} catch (SQLException ex) {
ex.printStackTrace();
}
}
public String getTableSql() {
return """
DROP TABLE IF EXISTS `data`;
CREATE TABLE `data` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'table ID',
`deleted` tinyint(1) NOT NULL DEFAULT '0',
`create_date` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Time the element has been created',
`modify_date` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Time the element has been update',
`sha512` varchar(129) CHARACTER SET utf8mb3 COLLATE utf8_general_ci NOT NULL,
`mime_type` varchar(128) CHARACTER SET utf8mb3 COLLATE utf8_general_ci NOT NULL,
`size` bigint DEFAULT NULL,
`original_name` text,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
""";
}
}

View File

@@ -12,15 +12,48 @@ CREATE TABLE `node` (
) AUTO_INCREMENT=10;
*/
import java.util.List;
import org.bson.types.ObjectId;
import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.dataAccess.options.CheckJPA;
import org.kar.archidata.model.Data;
import org.kar.archidata.model.GenericDataSoftDelete;
import com.fasterxml.jackson.annotation.JsonInclude;
import dev.morphia.annotations.Entity;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
@Entity("Gender")
@Table(name = "gender")
@DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Gender extends NodeSmall {
public class Gender extends GenericDataSoftDelete {
public static class GenderChecker extends CheckJPA<Gender> {
public GenderChecker() {
super(Gender.class);
}
}
@Column(length = 256)
public String name = null;
@Column(length = 0)
public String description = null;
@Schema(description = "List of Id of the specific covers")
@DataJson(targetEntity = Data.class)
@Nullable
public List<ObjectId> covers = null;
public Gender() {}
public Gender(final Long id, final String name) {
this.id = id;
this.name = name;
}
}

View File

@@ -1,104 +0,0 @@
package org.kar.karusic.model;
/*
CREATE TABLE `node` (
`id` bigint NOT NULL COMMENT 'table ID' AUTO_INCREMENT PRIMARY KEY,
`deleted` BOOLEAN NOT NULL DEFAULT false,
`create_date` datetime NOT NULL DEFAULT now() COMMENT 'Time the element has been created',
`modify_date` datetime NOT NULL DEFAULT now() COMMENT 'Time the element has been update',
`type` enum("TYPE", "UNIVERSE", "SERIES", "SEASON") NOT NULL DEFAULT 'TYPE',
`name` TEXT COLLATE 'utf8_general_ci' NOT NULL,
`description` TEXT COLLATE 'utf8_general_ci',
`parent_id` bigint
) AUTO_INCREMENT=10;
*/
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class MediaSmall {
public class MediaStreamProperty {
public Long id;
public Long timeSecond;
public Long width;
public Long height;
public Map<String, Long> videos = new HashMap<>();
public Map<String, Long> audios = new HashMap<>();
public Map<String, Long> subtitles = new HashMap<>();
}
public Long id;
public String name;
public String description;
public Long dataId;
public Long typeId;
public Long universeId;
public Long seriesId;
public Long seasonId;
public Integer episode;
public Integer date;
public Integer time;
public String ageLimit;
public List<Long> covers = null;
public MediaStreamProperty media;
public MediaSmall(ResultSet rs) {
int iii = 1;
try {
this.id = rs.getLong(iii++);
this.name = rs.getString(iii++);
this.description = rs.getString(iii++);
this.dataId = rs.getLong(iii++);
if (rs.wasNull()) {
this.dataId = null;
}
this.typeId = rs.getLong(iii++);
if (rs.wasNull()) {
this.typeId = null;
}
this.universeId = rs.getLong(iii++);
if (rs.wasNull()) {
this.universeId = null;
}
this.seriesId = rs.getLong(iii++);
if (rs.wasNull()) {
this.seriesId = null;
}
this.seasonId = rs.getLong(iii++);
if (rs.wasNull()) {
this.seasonId = null;
}
this.episode = rs.getInt(iii++);
if (rs.wasNull()) {
this.episode = null;
}
this.date = rs.getInt(iii++);
if (rs.wasNull()) {
this.date = null;
}
this.time = rs.getInt(iii++);
if (rs.wasNull()) {
this.time = null;
}
this.ageLimit = rs.getString(iii++);
String coversString = rs.getString(iii++);
if (!rs.wasNull()) {
covers = new ArrayList<>();
String[] elements = coversString.split("-");
for (String elem : elements) {
Long tmp = Long.parseLong(elem);
covers.add(tmp);
}
}
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}

View File

@@ -1,34 +0,0 @@
package org.kar.karusic.model;
/*
CREATE TABLE `node` (
`id` bigint NOT NULL COMMENT 'table ID' AUTO_INCREMENT PRIMARY KEY,
`deleted` BOOLEAN NOT NULL DEFAULT false,
`create_date` datetime NOT NULL DEFAULT now() COMMENT 'Time the element has been created',
`modify_date` datetime NOT NULL DEFAULT now() COMMENT 'Time the element has been update',
`type` enum("TYPE", "UNIVERS", "SERIE", "SAISON", "MEDIA") NOT NULL DEFAULT 'TYPE',
`name` TEXT COLLATE 'utf8_general_ci' NOT NULL,
`description` TEXT COLLATE 'utf8_general_ci',
`parent_id` bigint
) AUTO_INCREMENT=10;
*/
import java.util.List;
import org.kar.archidata.model.Data;
import org.kar.archidata.model.GenericDataSoftDelete;
import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.persistence.Column;
import jakarta.persistence.FetchType;
import jakarta.persistence.ManyToMany;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class NodeSmall extends GenericDataSoftDelete {
@Column(length = 256)
public String name = null;
@Column(length = 0)
public String description = null;
@ManyToMany(fetch = FetchType.LAZY, targetEntity = Data.class)
public List<Long> covers = null;
}

View File

@@ -14,18 +14,42 @@ CREATE TABLE `node` (
import java.util.List;
import org.bson.types.ObjectId;
import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.dataAccess.options.CheckJPA;
import org.kar.archidata.model.Data;
import org.kar.archidata.model.GenericDataSoftDelete;
import com.fasterxml.jackson.annotation.JsonInclude;
import dev.morphia.annotations.Entity;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable;
import jakarta.persistence.Column;
import jakarta.persistence.FetchType;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
@Entity("Playlist")
@Table(name = "playlist")
@DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Playlist extends NodeSmall {
public class Playlist extends GenericDataSoftDelete {
public static class PlaylistChecker extends CheckJPA<Playlist> {
public PlaylistChecker() {
super(Playlist.class);
}
}
@Column(length = 256)
public String name = null;
@Column(length = 0)
public String description = null;
@Schema(description = "List of Id of the specific covers")
@DataJson(targetEntity = Data.class)
@Nullable
public List<ObjectId> covers = null;
@ManyToMany(fetch = FetchType.LAZY, targetEntity = Track.class)
public List<Long> tracks = null;
public List<ObjectId> tracks = null;
}

View File

@@ -1,10 +1,10 @@
package org.kar.karusic.model;
public enum State {
// User has remove his account
REMOVED,
// User has been blocked his account
BLOCKED,
// generic user
USER
// User has remove his account
REMOVED,
// User has been blocked his account
BLOCKED,
// generic user
USER
}

View File

@@ -10,31 +10,54 @@ CREATE TABLE `node` (
`description` TEXT COLLATE 'utf8_general_ci',
`parent_id` bigint
) AUTO_INCREMENT=10;
*/
*/
import java.util.List;
import org.bson.types.ObjectId;
import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.annotation.addOn.SQLTableExternalForeinKeyAsList;
import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.dataAccess.options.CheckJPA;
import org.kar.archidata.model.Data;
import org.kar.archidata.model.GenericDataSoftDelete;
import com.fasterxml.jackson.annotation.JsonInclude;
import dev.morphia.annotations.Entity;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
@Entity("Track")
@Table(name = "track")
@DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Track extends NodeSmall {
public class Track extends GenericDataSoftDelete {
public static class TrackChecker extends CheckJPA<Track> {
public TrackChecker() {
super(Track.class);
}
}
@Column(length = 256)
public String name = null;
@Column(length = 0)
public String description = null;
@Schema(description = "List of Id of the specific covers")
@DataJson(targetEntity = Data.class)
@Nullable
public List<ObjectId> covers = null;
public Long genderId = null;
public Long albumId = null;
public Long track = null;
public Long dataId = null;
//@ManyToMany(fetch = FetchType.LAZY, targetEntity = Artist.class)
@SQLTableExternalForeinKeyAsList
public ObjectId dataId = null;
// @ManyToMany(fetch = FetchType.LAZY, targetEntity = Artist.class)
@DataJson
@Column(length = 0)
public List<Long> artists = null;
@Override
public String toString() {
return "Track [id=" + this.id + ", deleted=" + this.deleted + ", createdAt=" + this.createdAt + ", updatedAt=" + this.updatedAt + ", name=" + this.name + ", description=" + this.description

View File

@@ -3,13 +3,12 @@ package org.kar.karusic.util;
public class ConfigVariable {
public static final String BASE_NAME = "ORG_KARUSIC_";
public static String getFrontFolder() {
String out = System.getenv(BASE_NAME + "FRONT_FOLDER");
if (out == null) {
return "/application/front";
}
return out;
}
public static String getFrontFolder() {
String out = System.getenv(BASE_NAME + "FRONT_FOLDER");
if (out == null) {
return "/application/front";
}
return out;
}
}

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- environment detection (defaut: dev) -->
<property name="LOG_LEVEL_ENV" value="${LOG_LEVEL:-dev}" />
<!-- Appender for development -->
<if condition="property(&quot;LOG_LEVEL_ENV&quot;).equals(&quot;dev&quot;)">
<then>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%green(%d{HH:mm:ss.SSS}) %highlight(%-5level) %-30((%file:%line\)): %msg%n</pattern>
</encoder>
</appender>
<logger name="org.kar.karusic" level="TRACE" />
<logger name="org.kar.archidata" level="DEBUG" />
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
</then>
</if>
<!-- Appender for production -->
<if condition="property(&quot;LOG_LEVEL_ENV&quot;).matches(&quot;^prod.*&quot;)">
<then>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%thread] %level %logger - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
</then>
</if>
<if condition="property(&quot;LOG_LEVEL_ENV&quot;).equals(&quot;prod-debug&quot;)">
<then>
<logger name="org.kar.karusic" level="DEBUG" />
</then>
</if>
<if condition="property(&quot;LOG_LEVEL_ENV&quot;).equals(&quot;prod-trace&quot;)">
<then>
<logger name="org.kar.karusic" level="TRACE" />
<logger name="org.kar.archidata" level="DEBUG" />
</then>
</if>
<if condition="property(&quot;LOG_LEVEL_ENV&quot;).equals(&quot;prod-trace-full&quot;)">
<then>
<logger name="org.kar.karusic" level="TRACE" />
<logger name="org.kar.archidata" level="TRACE" />
</then>
</if>
</configuration>

View File

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

View File

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

View File

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

View File

@@ -1,250 +1,48 @@
package test.kar.karusic;
import java.util.Map;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.MethodOrderer;
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.RESTErrorResponseExeption;
import org.kar.archidata.model.GetToken;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.kar.archidata.tools.JWTWrapper;
import org.kar.archidata.tools.RESTApi;
import org.kar.karusic.api.HealthCheck.HealthResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.nimbusds.jwt.JWTClaimsSet;
@ExtendWith(StepwiseExtension.class)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class TestBase {
private final static Logger LOGGER = LoggerFactory.getLogger(TestBase.class);
public final static String ENDPOINT_NAME = "species/";
static WebLauncherTest webInterface = null;
static RESTApi api = null;
public void login(final String login, final String password) {
try {
final GetToken token = api.post(GetToken.class, "users/get_token", DataGetToken.generate(login, "v1", "202515252", password));
api.setToken(token.jwt());
} catch (final Exception ex) {
Assertions.fail("Can not get Authentication for '" + login + "' ==> " + ex.getMessage());
}
}
public void loginAdmin() {
login("karadmin", "adminA@666");
}
@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 {
public static void stopWebServer() throws Exception {
LOGGER.info("Kill the web server");
webInterface.stop();
webInterface = null;
// TODO: do it better...
ConfigureDb.clear();
}
@Order(1)
@Test
//@RepeatedTest(10)
public void checkHealthCheck() throws Exception {
final HealthResult result = api.get(HealthResult.class, "health_check");
Assertions.assertEquals(result.value(), "alive and kicking");
public static void TestEmpty() throws Exception {
}
@Order(2)
@Test
public void checkHealthCheckWrongAPI() throws Exception {
Assertions.assertThrows(RESTErrorResponseExeption.class, () -> api.get(HealthResult.class, "health_checks"));
}
@Order(3)
@Test
public void firstUserConnect() throws Exception {
final GetToken result = api.post(GetToken.class, "users/get_token", DataGetToken.generate("karadmin", "v1", "202515252", "adminA@666"));
final String[] splitted = result.jwt().split("\\.");
Assertions.assertEquals(3, splitted.length);
final String authorization = result.jwt();
LOGGER.debug(" validate token : " + authorization);
// Note with local access we get the internal key of the system.
final JWTClaimsSet ret = JWTWrapper.validateToken(authorization, "KarAuth", null);
// check the token is valid !!! (signed and coherent issuer...
Assertions.assertNotNull(ret);
// check userID
final String userUID = ret.getSubject();
final long id = Long.parseLong(userUID);
Assertions.assertEquals(1, id);
final String name = (String) ret.getClaim("login");
Assertions.assertEquals("karadmin", name);
final Object rowRight = ret.getClaim("right");
Assertions.assertNotNull(rowRight);
final Map<String, Map<String, Object>> rights = (Map<String, Map<String, Object>>) ret.getClaim("right");
// Check if the element contain the basic keys:
Assertions.assertEquals(rights.size(), 1);
Assertions.assertTrue(rights.containsKey("karusic"));
final Map<String, Object> applRight = rights.get("karusic");
//logger.error("full right: {}", applRight);
Assertions.assertEquals(applRight.size(), 2);
Assertions.assertTrue(applRight.containsKey("ADMIN"));
Assertions.assertEquals(true, applRight.get("ADMIN"));
Assertions.assertTrue(applRight.containsKey("USER"));
Assertions.assertEquals(true, applRight.get("USER"));
//logger.debug("request user: '{}' right: '{}' row='{}'", userUID, applRight, rowRight);
//Assertions.assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9", splitted[0]);
//Assertions.assertEquals("eyJzdWIiOiIwIiwiYXBwbGljYXRpb24iOiJrYXJzbyIsImlzcyI6IkthckF1dGgiLCJyaWdodCI6eyJrYXJzbyI6eyJBRE1JTiI6dHJ1ZSwiVVNFUiI6dHJ1ZX19LCJsb2dpbiI6ImthcmFkbWluIiwiZXhwIjoxNjg0MTk5MTkzLCJpYXQiOjE2ODI3NTU0MjV9", splitted[1]);
// TODO ... Assertions.assertEquals("????", splitted[2]);
}
public void checkFail(final String type, final String urlOffset, final int errorStatus) {
checkFail(type, urlOffset, errorStatus, null);
}
public void checkFail(final String type, final String urlOffset, final int errorStatus, final String data) {
LOGGER.info("Test API: url={} urlOffset={}", type, urlOffset);
try {
if ("GET".equals(type)) {
api.get(String.class, urlOffset);
} else if ("POST".equals(type)) {
api.post(String.class, urlOffset, data);
} else if ("PUT".equals(type)) {
api.put(String.class, urlOffset, data);
} else if ("DELETE".equals(type)) {
api.delete(String.class, urlOffset);
}
Assertions.fail("Request on URL does not fail as expected: '" + type + "' url='" + urlOffset + "'");
} catch (final RESTErrorResponseExeption ex) {
if (errorStatus != ex.status) {
LOGGER.error("Fail in test with the wrong return errors: {}", ex.toString());
}
Assertions.assertEquals(errorStatus, ex.status);
} catch (final Exception ex) {
LOGGER.error("Unexpected throw error: {}", ex);
Assertions.fail("Unexpected throws...");
}
}
public void checkWork(final String type, final String urlOffset) {
checkWork(type, urlOffset, null);
}
public void checkWork(final String type, final String urlOffset, final String data) {
LOGGER.info("Test API: url={} urlOffset={}", type, urlOffset);
try {
if ("GET".equals(type)) {
api.get(String.class, urlOffset);
} else if ("POST".equals(type)) {
api.post(String.class, urlOffset, data);
} else if ("PUT".equals(type)) {
api.put(String.class, urlOffset, data);
} else if ("DELETE".equals(type)) {
api.delete(String.class, urlOffset);
}
//Assertions.fail("Request on URL does not fail as expected: '" + type + "' url='" + urlOffset + "'");
} catch (final RESTErrorResponseExeption ex) {
Assertions.fail("Must not fail ... " + ex.toString());
} catch (final Exception ex) {
LOGGER.error("Unexpected throw error: {}", ex);
Assertions.fail("Unexpected throws...");
}
}
@Order(4)
@Test
public void checkUnAuthorizedAPI() throws Exception {
// /application/
checkFail("GET", "application/", 401);
checkFail("POST", "application/", 401, "{}");
checkFail("PUT", "application/", 405, "{}"); // does not exist
checkFail("DELETE", "application/", 405); // does not exist
// /application/{id}
checkFail("GET", "application/0", 401);
checkFail("PUT", "application/0", 401, "{}");
checkFail("POST", "application/0", 405, "{}");
checkFail("DELETE", "application/0", 401);
// /application/{id}/*
checkFail("GET", "application/0/users", 401);
// /application/*
checkFail("GET", "application/small", 401);
checkFail("GET", "application/get_token", 401);
checkFail("GET", "application/return", 401);
// /application_token/ section:
checkFail("GET", "application_token/0", 401);
checkFail("DELETE", "application_token/0/5", 401);
checkFail("DELETE", "application_token/0/create", 401);
// /front/*
checkFail("GET", "front", 404); // no index in test section
// health check
checkWork("GET", "health_check");
// public_key (only application)
checkFail("GET", "public_key", 401);
checkFail("GET", "public_key/pem", 401);
// /right
checkFail("GET", "right", 401);
checkFail("POST", "right", 401, "{}");
checkFail("GET", "right/0", 401);
checkFail("PUT", "right/0", 401, "{}");
checkFail("DELETE", "right/0", 401);
// /system_config
checkWork("GET", "system_config/is_sign_up_availlable");
checkFail("GET", "system_config/key/skjdfhkjsdhfkjsh", 401);
checkFail("PUT", "system_config/key/skjdfhkjsdhfkjsh", 401, "{}");
// /users
checkFail("GET", "users", 401);
checkFail("GET", "users/0", 401);
checkFail("POST", "users/0/application/0/link", 401, "{}");
checkFail("POST", "users/0/set_admin", 401, "{}");
checkFail("POST", "users/0/set_blocked", 401, "{}");
checkFail("POST", "users/create_new_user", 401, "{}");
checkFail("GET", "users/me", 401, "{}");
checkFail("POST", "users/password", 401, "{}");
checkWork("GET", "users/check_login?login=karadmin");
checkFail("GET", "users/check_login?login=jhkjhkjh", 404);
checkWork("GET", "users/check_email?email=admin@admin.ZZZ");
checkFail("GET", "users/check_email?email=ksjhdkjfhskjdh", 404);
// not testable : get_token
}
@Order(5)
@Test
public void testMeWithToken() throws Exception {
loginAdmin();
final String result = api.get(String.class, "users/me");
Assertions.assertEquals("{\"id\":1,\"login\":\"karadmin\"}", result);
}
}

View File

@@ -1,28 +1,12 @@
package test.kar.karusic;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.kar.karusic.WebLauncher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class WebLauncherTest extends WebLauncher {
final private static Logger LOGGER = LoggerFactory.getLogger(WebLauncherTest.class);
public WebLauncherTest() {
LOGGER.debug("Configure REST system");
// for local test:
ConfigBaseVariable.apiAdress = "http://127.0.0.1:12345/test/api/";
ConfigBaseVariable.dbPort = "3306";
// for the test we a in memory sqlite..
ConfigBaseVariable.dbType = "sqlite";
ConfigBaseVariable.dbHost = "memory";
// for test we need to connect all time the DB
ConfigBaseVariable.dbKeepConnected = "true";
ConfigBaseVariable.dbHost = "localhost";
ConfigBaseVariable.dbUser = "root";
ConfigBaseVariable.dbPassword = "ZERTYSDGFVHSDFGHJYZSDFGSQxfgsqdfgsqdrf4564654";
}
public WebLauncherTest() {}
}

View File

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

View File

@@ -1,12 +0,0 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# You can see what browsers were selected by your queries by running:
# npx browserslist
> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11 # For IE 9-11 support, remove 'not'.

View File

@@ -1,13 +0,0 @@
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

2
front/.env.production Normal file
View File

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

View File

@@ -1,4 +0,0 @@
node_modules/*
build/*
out/*
dist/*

View File

@@ -1,225 +0,0 @@
var OFF = 0, WARN = 1, ERROR = 2;
module.exports = {
'env': {
'browser': true,
'es2021': true,
},
'extends': [
'eslint:recommended',
],
'parser': '@typescript-eslint/parser',
'parserOptions': {
'ecmaVersion': 'latest',
'sourceType': 'module',
},
'plugins': [
'@typescript-eslint',
],
"rules": {
// Possible Errors (overrides from recommended set)
"no-extra-parens": ERROR,
"no-unexpected-multiline": ERROR,
// All JSDoc comments must be valid
"valid-jsdoc": [ OFF, {
"requireReturn": false,
"requireReturnDescription": false,
"requireParamDescription": true,
"prefer": {
"return": "returns"
}
}],
// Best Practices
// Allowed a getter without setter, but all setters require getters
"accessor-pairs": [ OFF, {
"getWithoutSet": false,
"setWithoutGet": true
}],
"block-scoped-var": WARN,
"consistent-return": OFF,
"curly": ERROR,
"default-case": WARN,
// the dot goes with the property when doing multiline
"dot-location": [ WARN, "property" ],
"dot-notation": WARN,
"eqeqeq": [ ERROR, "smart" ],
"guard-for-in": WARN,
"no-alert": ERROR,
"no-caller": ERROR,
"no-case-declarations": WARN,
"no-div-regex": WARN,
"no-else-return": WARN,
"no-empty-pattern": WARN,
"no-eq-null": ERROR,
"no-eval": ERROR,
"no-extend-native": ERROR,
"no-extra-bind": WARN,
"no-floating-decimal": WARN,
"no-implicit-coercion": [ WARN, {
"boolean": true,
"number": true,
"string": true
}],
"no-implied-eval": ERROR,
"no-invalid-this": ERROR,
"no-iterator": ERROR,
"no-labels": WARN,
"no-lone-blocks": WARN,
"no-loop-func": ERROR,
"no-magic-numbers": OFF,
"no-multi-spaces": ERROR,
"no-multi-str": WARN,
"no-native-reassign": ERROR,
"no-new-func": ERROR,
"no-new-wrappers": ERROR,
"no-new": ERROR,
"no-octal-escape": ERROR,
"no-param-reassign": ERROR,
"no-process-env": WARN,
"no-proto": ERROR,
"no-redeclare": ERROR,
"no-return-assign": ERROR,
"no-script-url": ERROR,
"no-self-compare": ERROR,
"no-throw-literal": ERROR,
"no-unused-expressions": ERROR,
"no-useless-call": ERROR,
"no-useless-concat": ERROR,
"no-void": WARN,
// Produce warnings when something is commented as TODO or FIXME
"no-warning-comments": [ WARN, {
"terms": [ "TODO", "FIXME" ],
"location": "start"
}],
"no-with": WARN,
"radix": WARN,
"vars-on-top": ERROR,
// Enforces the style of wrapped functions
"wrap-iife": [ ERROR, "outside" ],
"yoda": ERROR,
// Strict Mode - for ES6, never use strict.
"strict": [ ERROR, "never" ],
// Variables
"init-declarations": [ OFF, "always" ],
"no-catch-shadow": WARN,
"no-delete-var": ERROR,
"no-label-var": ERROR,
"no-shadow-restricted-names": ERROR,
"no-shadow": WARN,
// We require all vars to be initialized (see init-declarations)
// If we NEED a var to be initialized to undefined, it needs to be explicit
"no-undef-init": OFF,
"no-undef": ERROR,
"no-undefined": OFF,
"no-unused-vars": OFF,
// Disallow hoisting - let & const don't allow hoisting anyhow
"no-use-before-define": ERROR,
// Node.js and CommonJS
"callback-return": [ WARN, [ "callback", "next" ]],
"global-require": ERROR,
"handle-callback-err": WARN,
"no-mixed-requires": WARN,
"no-new-require": ERROR,
// Use path.concat instead
"no-path-concat": ERROR,
"no-process-exit": ERROR,
"no-restricted-modules": OFF,
"no-sync": WARN,
// ECMAScript 6 support
"arrow-body-style": [ ERROR, "always" ],
"arrow-parens": [ ERROR, "always" ],
"arrow-spacing": [ ERROR, { "before": true, "after": true }],
"constructor-super": ERROR,
"generator-star-spacing": [ ERROR, "before" ],
"no-confusing-arrow": ERROR,
"no-class-assign": ERROR,
"no-const-assign": ERROR,
"no-dupe-class-members": ERROR,
"no-this-before-super": ERROR,
"no-var": WARN,
"object-shorthand": [ WARN, "never" ],
"prefer-arrow-callback": WARN,
"prefer-spread": WARN,
"prefer-template": WARN,
"require-yield": ERROR,
// Stylistic - everything here is a warning because of style.
"array-bracket-spacing": [ WARN, "always" ],
"block-spacing": [ WARN, "always" ],
"brace-style": [ WARN, "1tbs", { "allowSingleLine": false } ],
"camelcase": WARN,
"comma-spacing": [ WARN, { "before": false, "after": true } ],
"comma-style": [ WARN, "last" ],
"computed-property-spacing": [ WARN, "never" ],
"consistent-this": [ WARN, "self" ],
"eol-last": WARN,
"func-names": WARN,
"func-style": [ WARN, "declaration" ],
"id-length": [ WARN, { "min": 2, "max": 32 } ],
"indent": [ WARN, 'tab' ],
"jsx-quotes": [ WARN, "prefer-double" ],
"linebreak-style": [ WARN, "unix" ],
"lines-around-comment": [ OFF, { "beforeBlockComment": true } ],
"max-depth": [ WARN, 8 ],
"max-len": [ WARN, 182 ],
"max-nested-callbacks": [ WARN, 8 ],
"max-params": [ WARN, 10 ],
"new-cap": OFF,
"new-parens": WARN,
"no-array-constructor": WARN,
"no-bitwise": OFF,
"no-continue": OFF,
"no-inline-comments": OFF,
"no-lonely-if": OFF,
"no-mixed-spaces-and-tabs": OFF,
"no-multiple-empty-lines": WARN,
"no-negated-condition": OFF,
"no-nested-ternary": WARN,
"no-new-object": WARN,
"no-plusplus": OFF,
"no-spaced-func": WARN,
"no-ternary": OFF,
"no-trailing-spaces": WARN,
"no-underscore-dangle": WARN,
"no-unneeded-ternary": WARN,
"object-curly-spacing": [ WARN, "always" ],
"one-var": OFF,
"operator-assignment": [ WARN, "never" ],
"operator-linebreak": [ WARN, "after" ],
"padded-blocks": [ WARN, "never" ],
"quote-props": [ WARN, "consistent-as-needed" ],
"quotes": [ WARN, "single" ],
"require-jsdoc": [ OFF, {
"require": {
"FunctionDeclaration": true,
"MethodDefinition": true,
"ClassDeclaration": false
}
}],
"semi-spacing": [ WARN, { "before": false, "after": true }],
"semi": [ ERROR, "always" ],
"sort-vars": OFF,
"keyword-spacing": [WARN, {
"overrides": {
"if": { "after": false },
"for": { "after": false },
"while": { "after": false },
"static": { "after": false },
"as": { "after": false }
}
}],
"space-before-blocks": [ WARN, "always" ],
"space-before-function-paren": [ WARN, "never" ],
"space-in-parens": [ WARN, "never" ],
"space-infix-ops": [ WARN, { "int32Hint": true } ],
"space-unary-ops": ERROR,
"spaced-comment": [ WARN, "always" ],
"wrap-regex": WARN,
},
};

3
front/.gitignore vendored
View File

@@ -1,3 +0,0 @@
/node_modules/
/.angular/
/.idea/

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

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

View File

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

View File

@@ -0,0 +1,43 @@
import React from 'react';
import { Box } from '@chakra-ui/react';
import { ChakraProvider } from '@chakra-ui/react';
import { MemoryRouter } from 'react-router-dom';
import theme from '../src/theme';
// .storybook/preview.js
export const parameters = {
options: {
storySort: {
order: ['StyleGuide', 'Components', 'Fields', 'App Layout'],
},
},
actions: {},
layout: 'fullscreen',
backgrounds: { disable: true, grid: { disable: true } },
chakra: {
theme,
},
};
const DocumentationWrapper = ({ children }) => {
return (
<Box id="start-ui-storybook-wrapper" p="4" pb="8" flex="1">
{children}
</Box>
);
};
export const decorators = [
(Story, context) => (
<ChakraProvider theme={theme}>
{/* Using MemoryRouter to avoid route clashing with Storybook */}
<MemoryRouter>
<DocumentationWrapper>
<Story {...context} />
</DocumentationWrapper>
</MemoryRouter>
</ChakraProvider>
),
];

View File

@@ -1,35 +0,0 @@
# base image
FROM node:lts as build
# add `/application/node_modules/.bin` to $PATH
ENV PATH /application/node_modules/.bin:$PATH
ADD package-lock.json /application/
ADD package.json /application/
#ADD browserslist /application/
ADD karma.conf.js /application/
ADD protractor.conf.js /application/
WORKDIR /application/
# install and cache app dependencies
RUN npm install
ADD e2e /application/e2e
ADD tsconfig.json /application/
ADD tslint.json /application/
ADD angular.json /application/
ADD src /application/src
# generate build
RUN ng build --output-path=dist --configuration=production --base-href=/karideo/ --deploy-url=/karideo/
############
### prod ###
############
# base image
FROM httpd:latest
# copy artifact build from the 'build environment'
COPY --from=build /application/dist /usr/local/apache2/htdocs/
COPY httpd/httpd.conf /usr/local/apache2/conf/httpd.conf

View File

@@ -1,24 +0,0 @@
# base image
FROM node:latest
ADD src /application/src
ADD e2e /application/e2e
ADD package-lock.json /application/
ADD package.json /application/
ADD angular.json /application/
ADD browserslist /application/
ADD karma.conf.js /application/
ADD protractor.conf.js /application/
ADD tsconfig.json /application/
ADD tslint.json /application/
WORKDIR /application/
# add `/app/node_modules/.bin` to $PATH
ENV PATH /app/node_modules/.bin:$PATH
# install and cache app dependencies
RUN npm install
# start app
CMD ["npx", "ng", "serve", "--host", "0.0.0.0"]

2
front/LICENSE Normal file
View File

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

View File

@@ -1,134 +0,0 @@
{
"$schema" : "./node_modules/@angular/cli/lib/config/schema.json",
"version" : 1,
"newProjectRoot" : "projects",
"defaultProject" : "karusic",
"projects" : {
"karusic" : {
"root" : "",
"sourceRoot" : "src",
"projectType" : "application",
"architect" : {
"build" : {
"builder" : "@angular-devkit/build-angular:browser",
"options" : {
"outputPath" : "dist",
"index" : "src/index.html",
"main" : "src/main.ts",
"tsConfig" : "src/tsconfig.app.json",
"polyfills" : "src/polyfills.ts",
"assets" : [ "src/assets", "src/favicon.ico" ],
"styles" : [ "src/styles.less", "src/generic_page.less", "src/theme.color.blue.less", "src/theme.checkbox.less", "src/theme.modal.less" ],
"scripts" : [ ]
},
"configurations" : {
"production" : {
"optimization" : true,
"outputHashing" : "all",
"sourceMap" : false,
"namedChunks" : false,
"aot" : true,
"extractLicenses" : true,
"vendorChunk" : false,
"buildOptimizer" : true,
"fileReplacements" : [ {
"replace" : "src/environments/environment.ts",
"with" : "src/environments/environment.prod.ts"
} ]
},
"develop" : {
"optimization" : false,
"outputHashing" : "none",
"sourceMap" : true,
"namedChunks" : true,
"aot" : true,
"extractLicenses" : true,
"vendorChunk" : true,
"buildOptimizer" : false
}
}
},
"serve" : {
"builder" : "@angular-devkit/build-angular:dev-server",
"options" : {
"browserTarget" : "karusic:build"
},
"configurations" : {
"production" : {
"browserTarget" : "karusic:build:production"
},
"develop" : {
"browserTarget" : "karusic:build:develop"
}
}
},
"extract-i18n" : {
"builder" : "@angular-devkit/build-angular:extract-i18n",
"options" : {
"browserTarget" : "karusic:build"
}
},
"test" : {
"builder" : "@angular-devkit/build-angular:karma",
"options" : {
"main" : "src/test.ts",
"karmaConfig" : "./karma.conf.js",
"polyfills" : "src/polyfills.ts",
"tsConfig" : "src/tsconfig.spec.json",
"scripts" : [ ],
"styles" : [ "src/styles.less", "src/generic_page.less", "src/theme.color.blue.less", "src/theme.checkbox.less", "src/theme.modal.less" ],
"assets" : [ "src/assets", "src/favicon.ico" ]
}
},
"lint" : {
"builder" : "@angular-eslint/builder:lint",
"options" : {
"fix": true,
"eslintConfig": ".eslintrc.js",
"lintFilePatterns": [
"src/**/*.spec.ts",
"src/**/*.ts"
]
}
},
"TTTTTTlint" : {
"builder" : "@angular-devkit/build-angular:tslint",
"options" : {
"tsConfig" : [ "src/tsconfig.app.json", "src/tsconfig.spec.json" ],
"exclude" : [ "**/node_modules/**" ]
}
}
}
},
"karusic-e2e" : {
"root" : "e2e",
"sourceRoot" : "e2e",
"projectType" : "application",
"architect" : {
"e2e" : {
"builder" : "@angular-devkit/build-angular:protractor",
"options" : {
"protractorConfig" : "./protractor.conf.js",
"devServerTarget" : "karusic:serve"
}
},
"lint" : {
"builder" : "@angular-devkit/build-angular:tslint",
"options" : {
"tsConfig" : [ "e2e/tsconfig.e2e.json" ],
"exclude" : [ "**/node_modules/**" ]
}
}
}
}
},
"schematics" : {
"@schematics/angular:component" : {
"prefix" : "app",
"style" : "less"
},
"@schematics/angular:directive" : {
"prefix" : "app"
}
}
}

6
front/app-build.json Normal file
View File

@@ -0,0 +1,6 @@
{
"display": "2025-01-14",
"version": "0.0.1-dev\n - 2025-01-14T20:19:20+01:00",
"commit": "0.0.1-dev\n",
"date": "2025-01-14T20:19:20+01:00"
}

25
front/build.js Normal file
View File

@@ -0,0 +1,25 @@
const dayjs = require('dayjs');
const fs = require('fs');
const generateAppBuild = () => {
const getVersion = () => fs.readFileSync('version.txt', 'utf8');
const commit = process.env.VERCEL_GIT_COMMIT_SHA
? process.env.VERCEL_GIT_COMMIT_SHA
: getVersion();
const appBuildContent = {
display: `${dayjs().format('YYYY-MM-DD')}`,
version: `${commit} - ${dayjs().format()}`,
commit,
date: dayjs().format(),
};
fs.writeFileSync(
'./app-build.json',
JSON.stringify(appBuildContent, null, 2)
);
};
generateAppBuild();

View File

@@ -1,11 +0,0 @@
version: '3'
services:
karideo_service:
build: .
restart: always
image: yui.heero/karideo
container_name: karideo
ports:
#- 15081:4200
- 15081:80

View File

@@ -1,14 +0,0 @@
import { AppPage } from './app.po';
describe('karusic App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getParagraphText()).toEqual('Welcome to app!');
});
});

View File

@@ -1,11 +0,0 @@
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo() {
return browser.get('/');
}
getParagraphText() {
return element(by.css('app-root h1')).getText();
}
}

View File

@@ -1,14 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"baseUrl": "./",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}

View File

@@ -1,541 +0,0 @@
#
# This is the main Apache HTTP server configuration file. It contains the
# configuration directives that give the server its instructions.
# See <URL:http://httpd.apache.org/docs/2.4/> for detailed information.
# In particular, see
# <URL:http://httpd.apache.org/docs/2.4/mod/directives.html>
# for a discussion of each configuration directive.
#
# Do NOT simply read the instructions in here without understanding
# what they do. They're here only as hints or reminders. If you are unsure
# consult the online docs. You have been warned.
#
# Configuration and logfile names: If the filenames you specify for many
# of the server's control files begin with "/" (or "drive:/" for Win32), the
# server will use that explicit path. If the filenames do *not* begin
# with "/", the value of ServerRoot is prepended -- so "logs/access_log"
# with ServerRoot set to "/usr/local/apache2" will be interpreted by the
# server as "/usr/local/apache2/logs/access_log", whereas "/logs/access_log"
# will be interpreted as '/logs/access_log'.
#
# ServerRoot: The top of the directory tree under which the server's
# configuration, error, and log files are kept.
#
# Do not add a slash at the end of the directory path. If you point
# ServerRoot at a non-local disk, be sure to specify a local disk on the
# Mutex directive, if file-based mutexes are used. If you wish to share the
# same ServerRoot for multiple httpd daemons, you will need to change at
# least PidFile.
#
ServerRoot "/usr/local/apache2"
#
# Mutex: Allows you to set the mutex mechanism and mutex file directory
# for individual mutexes, or change the global defaults
#
# Uncomment and change the directory if mutexes are file-based and the default
# mutex file directory is not on a local disk or is not appropriate for some
# other reason.
#
# Mutex default:logs
#
# Listen: Allows you to bind Apache to specific IP addresses and/or
# ports, instead of the default. See also the <VirtualHost>
# directive.
#
# Change this to Listen on specific IP addresses as shown below to
# prevent Apache from glomming onto all bound IP addresses.
#
Listen 80
Listen 443
#
# Dynamic Shared Object (DSO) Support
#
# To be able to use the functionality of a module which was built as a DSO you
# have to place corresponding `LoadModule' lines at this location so the
# directives contained in it are actually available _before_ they are used.
# Statically compiled modules (those listed by `httpd -l') do not need
# to be loaded here.
#
# Example:
# LoadModule foo_module modules/mod_foo.so
#
LoadModule mpm_event_module modules/mod_mpm_event.so
#LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
#LoadModule mpm_worker_module modules/mod_mpm_worker.so
LoadModule authn_file_module modules/mod_authn_file.so
#LoadModule authn_dbm_module modules/mod_authn_dbm.so
#LoadModule authn_anon_module modules/mod_authn_anon.so
#LoadModule authn_dbd_module modules/mod_authn_dbd.so
#LoadModule authn_socache_module modules/mod_authn_socache.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_user_module modules/mod_authz_user.so
#LoadModule authz_dbm_module modules/mod_authz_dbm.so
#LoadModule authz_owner_module modules/mod_authz_owner.so
#LoadModule authz_dbd_module modules/mod_authz_dbd.so
LoadModule authz_core_module modules/mod_authz_core.so
#LoadModule authnz_ldap_module modules/mod_authnz_ldap.so
#LoadModule authnz_fcgi_module modules/mod_authnz_fcgi.so
LoadModule access_compat_module modules/mod_access_compat.so
LoadModule auth_basic_module modules/mod_auth_basic.so
#LoadModule auth_form_module modules/mod_auth_form.so
#LoadModule auth_digest_module modules/mod_auth_digest.so
#LoadModule allowmethods_module modules/mod_allowmethods.so
#LoadModule isapi_module modules/mod_isapi.so
#LoadModule file_cache_module modules/mod_file_cache.so
#LoadModule cache_module modules/mod_cache.so
#LoadModule cache_disk_module modules/mod_cache_disk.so
#LoadModule cache_socache_module modules/mod_cache_socache.so
#LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
#LoadModule socache_dbm_module modules/mod_socache_dbm.so
#LoadModule socache_memcache_module modules/mod_socache_memcache.so
#LoadModule watchdog_module modules/mod_watchdog.so
#LoadModule macro_module modules/mod_macro.so
#LoadModule dbd_module modules/mod_dbd.so
#LoadModule bucketeer_module modules/mod_bucketeer.so
#LoadModule dumpio_module modules/mod_dumpio.so
#LoadModule echo_module modules/mod_echo.so
#LoadModule example_hooks_module modules/mod_example_hooks.so
#LoadModule case_filter_module modules/mod_case_filter.so
#LoadModule case_filter_in_module modules/mod_case_filter_in.so
#LoadModule example_ipc_module modules/mod_example_ipc.so
#LoadModule buffer_module modules/mod_buffer.so
#LoadModule data_module modules/mod_data.so
#LoadModule ratelimit_module modules/mod_ratelimit.so
LoadModule reqtimeout_module modules/mod_reqtimeout.so
#LoadModule ext_filter_module modules/mod_ext_filter.so
#LoadModule request_module modules/mod_request.so
#LoadModule include_module modules/mod_include.so
LoadModule filter_module modules/mod_filter.so
#LoadModule reflector_module modules/mod_reflector.so
#LoadModule substitute_module modules/mod_substitute.so
#LoadModule sed_module modules/mod_sed.so
#LoadModule charset_lite_module modules/mod_charset_lite.so
#LoadModule deflate_module modules/mod_deflate.so
LoadModule xml2enc_module modules/mod_xml2enc.so
LoadModule proxy_html_module modules/mod_proxy_html.so
LoadModule mime_module modules/mod_mime.so
#LoadModule ldap_module modules/mod_ldap.so
LoadModule log_config_module modules/mod_log_config.so
#LoadModule log_debug_module modules/mod_log_debug.so
#LoadModule log_forensic_module modules/mod_log_forensic.so
#LoadModule logio_module modules/mod_logio.so
#LoadModule lua_module modules/mod_lua.so
LoadModule env_module modules/mod_env.so
#LoadModule mime_magic_module modules/mod_mime_magic.so
#LoadModule cern_meta_module modules/mod_cern_meta.so
#LoadModule expires_module modules/mod_expires.so
LoadModule headers_module modules/mod_headers.so
#LoadModule ident_module modules/mod_ident.so
#LoadModule usertrack_module modules/mod_usertrack.so
#LoadModule unique_id_module modules/mod_unique_id.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule version_module modules/mod_version.so
#LoadModule remoteip_module modules/mod_remoteip.so
LoadModule proxy_module modules/mod_proxy.so
#LoadModule proxy_connect_module modules/mod_proxy_connect.so
#LoadModule proxy_ftp_module modules/mod_proxy_ftp.so
LoadModule proxy_http_module modules/mod_proxy_http.so
#LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
#LoadModule proxy_scgi_module modules/mod_proxy_scgi.so
#LoadModule proxy_uwsgi_module modules/mod_proxy_uwsgi.so
#LoadModule proxy_fdpass_module modules/mod_proxy_fdpass.so
#LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
#LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
#LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
#LoadModule proxy_express_module modules/mod_proxy_express.so
#LoadModule proxy_hcheck_module modules/mod_proxy_hcheck.so
#LoadModule session_module modules/mod_session.so
#LoadModule session_cookie_module modules/mod_session_cookie.so
#LoadModule session_crypto_module modules/mod_session_crypto.so
#LoadModule session_dbd_module modules/mod_session_dbd.so
#LoadModule slotmem_shm_module modules/mod_slotmem_shm.so
#LoadModule slotmem_plain_module modules/mod_slotmem_plain.so
LoadModule ssl_module modules/mod_ssl.so
#LoadModule optional_hook_export_module modules/mod_optional_hook_export.so
#LoadModule optional_hook_import_module modules/mod_optional_hook_import.so
#LoadModule optional_fn_import_module modules/mod_optional_fn_import.so
#LoadModule optional_fn_export_module modules/mod_optional_fn_export.so
#LoadModule dialup_module modules/mod_dialup.so
#LoadModule http2_module modules/mod_http2.so
#LoadModule proxy_http2_module modules/mod_proxy_http2.so
#LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so
#LoadModule lbmethod_bytraffic_module modules/mod_lbmethod_bytraffic.so
#LoadModule lbmethod_bybusyness_module modules/mod_lbmethod_bybusyness.so
#LoadModule lbmethod_heartbeat_module modules/mod_lbmethod_heartbeat.so
LoadModule unixd_module modules/mod_unixd.so
#LoadModule heartbeat_module modules/mod_heartbeat.so
#LoadModule heartmonitor_module modules/mod_heartmonitor.so
#LoadModule dav_module modules/mod_dav.so
LoadModule status_module modules/mod_status.so
LoadModule autoindex_module modules/mod_autoindex.so
#LoadModule asis_module modules/mod_asis.so
#LoadModule info_module modules/mod_info.so
#LoadModule suexec_module modules/mod_suexec.so
<IfModule !mpm_prefork_module>
#LoadModule cgid_module modules/mod_cgid.so
</IfModule>
<IfModule mpm_prefork_module>
#LoadModule cgi_module modules/mod_cgi.so
</IfModule>
#LoadModule dav_fs_module modules/mod_dav_fs.so
#LoadModule dav_lock_module modules/mod_dav_lock.so
#LoadModule vhost_alias_module modules/mod_vhost_alias.so
#LoadModule negotiation_module modules/mod_negotiation.so
LoadModule dir_module modules/mod_dir.so
#LoadModule imagemap_module modules/mod_imagemap.so
#LoadModule actions_module modules/mod_actions.so
#LoadModule speling_module modules/mod_speling.so
#LoadModule userdir_module modules/mod_userdir.so
LoadModule alias_module modules/mod_alias.so
LoadModule rewrite_module modules/mod_rewrite.so
<IfModule unixd_module>
#
# If you wish httpd to run as a different user or group, you must run
# httpd as root initially and it will switch.
#
# User/Group: The name (or #number) of the user/group to run httpd as.
# It is usually good practice to create a dedicated user and group for
# running httpd, as with most system services.
#
User daemon
Group daemon
</IfModule>
# 'Main' server configuration
#
# The directives in this section set up the values used by the 'main'
# server, which responds to any requests that aren't handled by a
# <VirtualHost> definition. These values also provide defaults for
# any <VirtualHost> containers you may define later in the file.
#
# All of these directives may appear inside <VirtualHost> containers,
# in which case these default settings will be overridden for the
# virtual host being defined.
#
#
# ServerAdmin: Your address, where problems with the server should be
# e-mailed. This address appears on some server-generated pages, such
# as error documents. e.g. admin@your-domain.com
#
ServerAdmin yui.heero@gmail.com
#
# ServerName gives the name and port that the server uses to identify itself.
# This can often be determined automatically, but we recommend you specify
# it explicitly to prevent problems during startup.
#
# If your host doesn't have a registered DNS name, enter its IP address here.
#
#ServerName www.example.com:80
#
# Deny access to the entirety of your server's filesystem. You must
# explicitly permit access to web content directories in other
# <Directory> blocks below.
#
<Directory />
AllowOverride none
Require all denied
</Directory>
# intermediate configuration, tweak to your needs
SSLProtocol all -SSLv2 -SSLv3
SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS
SSLHonorCipherOrder on
#
# Note that from this point forward you must specifically allow
# particular features to be enabled - so if something's not working as
# you might expect, make sure that you have specifically enabled it
# below.
#
#
# DocumentRoot: The directory out of which you will serve your
# documents. By default, all requests are taken from this directory, but
# symbolic links and aliases may be used to point to other locations.
#
<VirtualHost *:80>
ServerName my-app
DocumentRoot "/usr/local/apache2/htdocs"
<Directory "/usr/local/apache2/htdocs">
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
RewriteEngine on
# Don't rewrite files or directories
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# Rewrite everything else to index.html to allow HTML5 state links
RewriteRule ^ index.html [L]
</Directory>
</VirtualHost>
#
# DirectoryIndex: sets the file that Apache will serve if a directory
# is requested.
#
<IfModule dir_module>
DirectoryIndex index.html
</IfModule>
#
# The following lines prevent .htaccess and .htpasswd files from being
# viewed by Web clients.
#
<Files ".ht*">
Require all denied
</Files>
#
# ErrorLog: The location of the error log file.
# If you do not specify an ErrorLog directive within a <VirtualHost>
# container, error messages relating to that virtual host will be
# logged here. If you *do* define an error logfile for a <VirtualHost>
# container, that host's errors will be logged there and not here.
#
ErrorLog /proc/self/fd/2
#
# LogLevel: Control the number of messages logged to the error_log.
# Possible values include: debug, info, notice, warn, error, crit,
# alert, emerg.
#
LogLevel warn
<IfModule log_config_module>
#
# The following directives define some format nicknames for use with
# a CustomLog directive (see below).
#
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %b" common
<IfModule logio_module>
# You need to enable mod_logio.c to use %I and %O
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
</IfModule>
#
# The location and format of the access logfile (Common Logfile Format).
# If you do not define any access logfiles within a <VirtualHost>
# container, they will be logged here. Contrariwise, if you *do*
# define per-<VirtualHost> access logfiles, transactions will be
# logged therein and *not* in this file.
#
CustomLog /proc/self/fd/1 common
#
# If you prefer a logfile with access, agent, and referer information
# (Combined Logfile Format) you can use the following directive.
#
#CustomLog "logs/access_log" combined
</IfModule>
<IfModule alias_module>
#
# Redirect: Allows you to tell clients about documents that used to
# exist in your server's namespace, but do not anymore. The client
# will make a new request for the document at its new location.
# Example:
# Redirect permanent /foo http://www.example.com/bar
#
# Alias: Maps web paths into filesystem paths and is used to
# access content that does not live under the DocumentRoot.
# Example:
# Alias /webpath /full/filesystem/path
#
# If you include a trailing / on /webpath then the server will
# require it to be present in the URL. You will also likely
# need to provide a <Directory> section to allow access to
# the filesystem path.
#
# ScriptAlias: This controls which directories contain server scripts.
# ScriptAliases are essentially the same as Aliases, except that
# documents in the target directory are treated as applications and
# run by the server when requested rather than as documents sent to the
# client. The same rules about trailing "/" apply to ScriptAlias
# directives as to Alias.
#
ScriptAlias /cgi-bin/ "/usr/local/apache2/cgi-bin/"
</IfModule>
<IfModule cgid_module>
#
# ScriptSock: On threaded servers, designate the path to the UNIX
# socket used to communicate with the CGI daemon of mod_cgid.
#
#Scriptsock cgisock
</IfModule>
#
# "/usr/local/apache2/cgi-bin" should be changed to whatever your ScriptAliased
# CGI directory exists, if you have that configured.
#
<Directory "/usr/local/apache2/cgi-bin">
AllowOverride None
Options None
Require all granted
</Directory>
<IfModule headers_module>
#
# Avoid passing HTTP_PROXY environment to CGI's on this or any proxied
# backend servers which have lingering "httpoxy" defects.
# 'Proxy' request header is undefined by the IETF, not listed by IANA
#
RequestHeader unset Proxy early
</IfModule>
<IfModule mime_module>
#
# TypesConfig points to the file containing the list of mappings from
# filename extension to MIME-type.
#
TypesConfig conf/mime.types
#
# AddType allows you to add to or override the MIME configuration
# file specified in TypesConfig for specific file types.
#
#AddType application/x-gzip .tgz
#
# AddEncoding allows you to have certain browsers uncompress
# information on the fly. Note: Not all browsers support this.
#
#AddEncoding x-compress .Z
#AddEncoding x-gzip .gz .tgz
#
# If the AddEncoding directives above are commented-out, then you
# probably should define those extensions to indicate media types:
#
AddType application/x-compress .Z
AddType application/x-gzip .gz .tgz
#
# AddHandler allows you to map certain file extensions to "handlers":
# actions unrelated to filetype. These can be either built into the server
# or added with the Action directive (see below)
#
# To use CGI scripts outside of ScriptAliased directories:
# (You will also need to add "ExecCGI" to the "Options" directive.)
#
#AddHandler cgi-script .cgi
# For type maps (negotiated resources):
#AddHandler type-map var
#
# Filters allow you to process content before it is sent to the client.
#
# To parse .shtml files for server-side includes (SSI):
# (You will also need to add "Includes" to the "Options" directive.)
#
#AddType text/html .shtml
#AddOutputFilter INCLUDES .shtml
</IfModule>
#
# The mod_mime_magic module allows the server to use various hints from the
# contents of the file itself to determine its type. The MIMEMagicFile
# directive tells the module where the hint definitions are located.
#
#MIMEMagicFile conf/magic
#
# Customizable error responses come in three flavors:
# 1) plain text 2) local redirects 3) external redirects
#
# Some examples:
#ErrorDocument 500 "The server made a boo boo."
#ErrorDocument 404 /missing.html
#ErrorDocument 404 "/cgi-bin/missing_handler.pl"
#ErrorDocument 402 http://www.example.com/subscription_info.html
#
#
# MaxRanges: Maximum number of Ranges in a request before
# returning the entire resource, or one of the special
# values 'default', 'none' or 'unlimited'.
# Default setting is to accept 200 Ranges.
#MaxRanges unlimited
#
# EnableMMAP and EnableSendfile: On systems that support it,
# memory-mapping or the sendfile syscall may be used to deliver
# files. This usually improves server performance, but must
# be turned off when serving from networked-mounted
# filesystems or if support for these functions is otherwise
# broken on your system.
# Defaults: EnableMMAP On, EnableSendfile Off
#
#EnableMMAP off
#EnableSendfile on
# Supplemental configuration
#
# The configuration files in the conf/extra/ directory can be
# included to add extra features or to modify the default configuration of
# the server, or you may simply copy their contents here and change as
# necessary.
# Server-pool management (MPM specific)
#Include conf/extra/httpd-mpm.conf
# Multi-language error messages
#Include conf/extra/httpd-multilang-errordoc.conf
# Fancy directory listings
#Include conf/extra/httpd-autoindex.conf
# Language settings
#Include conf/extra/httpd-languages.conf
# User home directories
#Include conf/extra/httpd-userdir.conf
# Real-time info on requests and configuration
#Include conf/extra/httpd-info.conf
# Virtual hosts
#Include conf/extra/httpd-vhosts.conf
# Local access to the Apache HTTP Server Manual
#Include conf/extra/httpd-manual.conf
# Distributed authoring and versioning (WebDAV)
#Include conf/extra/httpd-dav.conf
# Various default settings
#Include conf/extra/httpd-default.conf
# Configure mod_proxy_html to understand HTML4/XHTML1
<IfModule proxy_html_module>
Include conf/extra/proxy-html.conf
</IfModule>
# Secure (SSL/TLS) connections
#Include conf/extra/httpd-ssl.conf
#
# Note: The following must must be present to support
# starting without SSL on platforms with no /dev/random equivalent
# but a statically compiled-in mod_ssl.
#
<IfModule ssl_module>
SSLRandomSeed startup builtin
SSLRandomSeed connect builtin
</IfModule>

13
front/index.html Normal file
View File

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

View File

@@ -1,31 +0,0 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client:{
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};

18
front/knip.ts Normal file
View File

@@ -0,0 +1,18 @@
import type { KnipConfig } from 'knip';
const config: KnipConfig = {
// Ignoring mostly shell binaries
ignoreBinaries: ['export', 'sleep'],
ignore: [
// Related to tests
'tests/**',
'**.conf.js',
'steps.d.ts',
'steps_file.js',
'env_ci/codecept.conf.js',
// Generic components are useful.
'src/components/**',
],
};
export default config;

21733
front/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,40 +1,82 @@
{
"name": "karusic",
"version": "0.0.0",
"license": "MIT",
"scripts": {
"all": "npm run build && npm run test",
"ng": "ng",
"start": "ng serve --configuration=develop --watch --port 4203",
"build": "ng build --prod",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"version": "0.0.1",
"description": "KAR web music application",
"author": {
"name": "Edouard DUPIN",
"email": "yui.heero@gmail.farm"
},
"license": "PROPRIETARY",
"engines": {
"node": ">=20"
},
"scripts": {
"update_packages": "ncu --target minor",
"upgrade_packages": "ncu --upgrade ",
"install_dependency": "pnpm install",
"test": "vitest run",
"test:watch": "vitest watch",
"build": "tsc && vite build",
"static:build": "node build.js && pnpm build",
"dev": "vite",
"pretty": "prettier -w .",
"lint": "pnpm tsc --noEmit",
"storybook": "storybook dev -p 3001",
"storybook:build": "storybook build && mv ./storybook-static ./public/storybook"
},
"lint-staged": {
"*.{ts,tsx,js,jsx,json}": "prettier --write"
},
"dependencies": {
"@angular/animations": "^14.2.10",
"@angular/cdk": "^14.2.7",
"@angular/common": "^14.2.10",
"@angular/compiler": "^14.2.10",
"@angular/core": "^14.2.10",
"@angular/forms": "^14.2.10",
"@angular/material": "^14.2.7",
"@angular/platform-browser": "^14.2.10",
"@angular/platform-browser-dynamic": "^14.2.10",
"@angular/router": "^14.2.10",
"rxjs": "^7.5.7",
"zone.js": "^0.12.0"
"history": "5.3.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-error-boundary": "5.0.0",
"react-icons": "5.4.0",
"react-router-dom": "7.1.1",
"react-select": "5.9.0",
"react-use": "17.6.0",
"zod": "3.24.1",
"zustand": "5.0.3"
},
"devDependencies": {
"@angular-devkit/build-angular": "^14.2.9",
"@angular-eslint/builder": "14.2.0",
"@angular-eslint/eslint-plugin": "14.2.0",
"@angular-eslint/eslint-plugin-template": "14.2.0",
"@angular-eslint/schematics": "14.2.0",
"@angular-eslint/template-parser": "14.2.0",
"@angular/cli": "^14.2.9",
"@angular/compiler-cli": "^14.2.10",
"@angular/language-service": "^14.2.10"
"@playwright/test": "1.49.1",
"@storybook/addon-actions": "8.4.7",
"@storybook/addon-essentials": "8.4.7",
"@storybook/addon-links": "8.4.7",
"@storybook/addon-mdx-gfm": "8.4.7",
"@storybook/react": "8.4.7",
"@storybook/react-vite": "8.4.7",
"@storybook/theming": "8.4.7",
"@testing-library/jest-dom": "6.6.3",
"@testing-library/react": "16.1.0",
"@testing-library/user-event": "14.5.2",
"@trivago/prettier-plugin-sort-imports": "5.2.1",
"@types/jest": "29.5.14",
"@types/node": "22.10.6",
"@types/react": "18.3.8",
"@types/react-dom": "18.3.0",
"@typescript-eslint/eslint-plugin": "8.20.0",
"@typescript-eslint/parser": "8.20.0",
"@vitejs/plugin-react": "4.3.4",
"eslint": "9.18.0",
"eslint-plugin-codeceptjs": "1.3.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-react": "7.37.4",
"eslint-plugin-react-hooks": "5.1.0",
"eslint-plugin-storybook": "0.11.2",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"knip": "5.42.0",
"lint-staged": "15.3.0",
"npm-check-updates": "^17.1.13",
"prettier": "3.4.2",
"react-is": "19.0.0",
"storybook": "8.4.7",
"ts-node": "10.9.2",
"typescript": "5.7.3",
"vite": "6.0.7",
"vitest": "2.1.8"
}
}
}

9194
front/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

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

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

View File

@@ -1,28 +0,0 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./e2e/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: 'e2e/tsconfig.e2e.json'
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1,43 +0,0 @@
Start the application:
```
npm install
```
upgrade package
```
npm audit fix
```
## npm install -g angular-cli
start the application:
```
npx ng serve --watch
```
plus facilement:
npm install @angular-devkit/build-angular@0.901.9
npm start
Apply linter:
==============
```
npx ng lint
```
build the local image:
docker build -t gitea.atria-soft.org/kangaroo-and-rabbit/karusic:latest .
docker login gitea.atria-soft.org
docker push gitea.atria-soft.org/kangaroo-and-rabbit/karusic:latest

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

@@ -0,0 +1,116 @@
import { App as SpaApp } from '@/scene/App';
import { Div, FullPage } from './ui';
import { useDisclosure } from './utils/disclosure';
import { useState } from 'react';
import { environment } from './environment';
import { hashLocalData } from './utils/sso';
import { USERS } from './service/session';
const AppEnvHint = () => {
const dialog = useDisclosure();
const [selectUserTest, setSelectUserTest] = useState<string>('NO_USER');
//const setUser = useRightsStore((store) => store.setUser);
const buildEnv =
process.env.NODE_ENV === 'development'
? 'Development'
: import.meta.env.VITE_DEV_ENV_NAME;
const envName: Array<string> = [];
!!buildEnv && envName.push(buildEnv);
if (!envName.length) {
return null;
}
const handleChange = (selectedOption) => {
console.log(`SELECT: [${selectedOption.target.value}]`);
setSelectUserTest(selectedOption.target.value);
};
const onClose = () => {
dialog.onClose();
if (selectUserTest == 'NO_USER') {
window.location.href = `/${environment.applName}/sso/${hashLocalData()}/false/__LOGOUT__`;
} else {
window.location.href = `/${environment.applName}/sso/${hashLocalData()}/true/${USERS[selectUserTest]}`;
}
};
return (
<>
<Div style={{
zIndex: "100000",
position: "fixed",
top: "0",
height: "2px",
width: "100%",
background: "warning.400",
cursor: "pointer"
}}
data-test-id="devtools"
onClick={dialog.onOpen}
>
<Div style={{
position: "fixed",
top: "0",
background: "warning.400",
color: "warning.900",
fontSize: "0.6rem",
fontWeight: "bold",
paddingLeft: "10px",
paddingRight: "10px",
marginLeft: "25%",
borderRadius: "0 0 10px 10px",
textTransform: "uppercase",
}}
>
{envName.join(' : ')}
</Div>
</Div>
{/* <Dialog.Root open={dialog.open} onOpenChange={dialog.onClose}>
<Dialog.Trigger asChild>
<Button>
{dialog.open ? "Close" : "Open"} Dialog
</Button>
</Dialog.Trigger>
<Portal>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content>
<Dialog.Title>Outils développeurs</Dialog.Title>
<Dialog.Description>
<HStack>
<Text>User</Text>
<Select.Root onChange={handleChange} collection={USERS_COLLECTION}>
<Select.Trigger>
<SelectValueText placeholder="Select test user" />
</Select.Trigger>
<Select.Content>
{USERS_COLLECTION.items.map((value) => (
<Select.Item item={value} key={value.value}>
{value.label}
</Select.Item>
))}
</Select.Content>
</Select.Root>
</HStack>
</Dialog.Description>
<Dialog.CloseTrigger>
<Button onClick={onClose}>Apply</Button>
</Dialog.CloseTrigger>
</Dialog.Content>
</Dialog.Positioner>
</Portal>
</Dialog.Root> */}
</>
);
};
const App = () => {
return (
<FullPage data-test-id="Full-root-page">
<AppEnvHint />
<SpaApp data-test-id="app" />
{/* <Toaster /> */}
</FullPage>
);
};
export default App;

View File

@@ -1,185 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2018, Edouard DUPIN, all right reserved
* @license PROPRIETARY (see license file)
*/
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router'; // CLI imports router
import { ForbiddenScene, HomeOutScene, NotFound404Scene, SsoScene } from 'common/scene';
import { OnlyAdminGuard, OnlyUnregisteredGuardHome, OnlyUsersGuard, OnlyUsersGuardHome } from 'common/service';
import { HelpScene, HomeScene, AlbumEditScene, AlbumsScene, ArtistEditScene, ArtistScene, SettingsScene,
GenderScene, PlaylistScene, TrackEditScene, TrackScene, UploadScene, ArtistsScene, ArtistAlbumScene, AlbumScene } from './scene';
// import { HelpComponent } from './help/help.component';
// see https://angular.io/guide/router
const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'forbidden', component: ForbiddenScene },
// ------------------------------------
// -- home global interface
// ------------------------------------
{
path: 'home',
component: HomeScene,
canActivate: [OnlyUsersGuardHome], // this route to unregistered path when not logged ==> permit to simplify display
},
{
path: 'unregistered',
component: HomeOutScene,
canActivate: [OnlyUnregisteredGuardHome], // jump to the home when registered
},
// ------------------------------------
// -- SSO Generic interface
// ------------------------------------
{ path: 'sso/:data/:keepConnected/:token', component: SsoScene },
{ path: 'sso/:keepConnected/:token', component: SsoScene },
{ path: 'sso', component: SsoScene },
// ------------------------------------
// -- Generic pages
// ------------------------------------
{ path: 'help/:page', component: HelpScene },
{ path: 'help', component: HelpScene },
// ------------------------------------
// -- upload new data:
// ------------------------------------
{
path: 'upload',
component: UploadScene,
canActivate: [OnlyAdminGuard],
},
// ------------------------------------
// -- gender:
// ------------------------------------
// display all gender
{
path: 'gender',
component: GenderScene,
canActivate: [OnlyUsersGuard],
},
// display all (artist | album | traks)
{
path: 'gender/:genderId',
component: GenderScene,
canActivate: [OnlyUsersGuard],
},
//{ path: 'gender-edit/:genderId', component: GenderEditScene },
//{ path: 'gender/:genderId', component: GenderScene },
//{ path: 'gender/:genderId/:artistId/:albumId/:trackId', component: GenderScene },
// ------------------------------------
// -- playlist:
// ------------------------------------
{
path: 'playlist',
component: PlaylistScene,
canActivate: [OnlyUsersGuard],
},
{
path: 'playlist/:playlistId',
component: PlaylistScene,
canActivate: [OnlyUsersGuard],
},
//{ path: 'playlist-edit/:playlistId', component: PlaylistEditScene },
// ------------------------------------
// -- Artist:
// ------------------------------------
// display list of all artist
{
path: 'artist',
component: ArtistsScene,
canActivate: [OnlyUsersGuard],
},
// display list af all artist with a specific gender
{
path: 'artist/:artistId',
component: ArtistScene,
canActivate: [OnlyUsersGuard],
},
{
path: 'artist/:artistId/:albumId',
component: ArtistAlbumScene,
canActivate: [OnlyUsersGuard],
},
{
path: 'artist-edit/:artistId',
component: ArtistEditScene,
canActivate: [OnlyAdminGuard],
},
// ------------------------------------
// -- Album:
// ------------------------------------
// display all Album
{
path: 'album',
component: AlbumsScene,
canActivate: [OnlyUsersGuard],
},
{
path: 'album/:albumId',
component: AlbumScene,
canActivate: [OnlyUsersGuard],
},
{
path: 'album-edit/:albumId',
component: AlbumEditScene,
canActivate: [OnlyAdminGuard],
},
// ------------------------------------
// -- Tracks:
// ------------------------------------
{
path: 'track/:genderId/:artistId/:albumId/:trackId',
component: TrackScene,
canActivate: [OnlyUsersGuard],
},
{
path: 'track-edit/:trackId',
component: TrackEditScene,
canActivate: [OnlyAdminGuard],
},
// ------------------------------------
// -- setting:
// ------------------------------------
{
path: 'settings',
component: SettingsScene,
canActivate: [OnlyUsersGuard],
},
{
path: '**',
component: NotFound404Scene,
},
];
@NgModule({
imports: [
RouterModule.forRoot(
routes,
{
//enableTracing: true, // <-- debugging purposes only
},
),
],
exports: [
RouterModule,
]
})
export class AppRoutingModule { }
// export const routing: ModuleWithProviders = RouterModule.forRoot(routes);

View File

@@ -1,15 +0,0 @@
<!-- Generig global menu -->
<app-top-menu [menu]="currentMenu" (callback)="eventOnMenu($event)"></app-top-menu>
<!-- all interfaced pages -->
<div class="main-content" *ngIf="autoConnectedDone">
<router-outlet ></router-outlet>
</div>
<div class="main-content" *ngIf="!autoConnectedDone">
<div class="generic-page">
<div class="fill-all colomn_mutiple">
<b style="color:red;">Auto-connection in progress</b>
<div class="clear"></div>
</div>
</div>
</div>
<app-element-player-audio></app-element-player-audio>

View File

@@ -1,46 +0,0 @@
#create-exercice-button {
position: fixed;
display: block;
right: 0;
bottom: 0;
margin-right: 40px;
margin-bottom: 40px;
z-index: 900;
}
#save-exercice-button {
position: fixed;
display: block;
right: 0;
bottom: 0;
margin-right: 110px;
margin-bottom: 40px;
z-index: 900;
}
.main-content {
position: absolute;
//width: ~"calc(calc(100% / 5 ) * 5)";
width: 100%;
height: ~"calc(100% - 56px)";
top: 56px;
left: 0;
margin: 0;
padding: 0;
display: block;
position: fixed;
overflow-y: auto;
//background-color:#FF0;
/*
.main-reduce {
width: 40%;
height: 100%;
margin: 0;
padding: 0px 10% 0px 10%;
display: block;
overflow-y:scroll;
}
*/
}

View File

@@ -1,295 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2018, Edouard DUPIN, all right reserved
* @license PROPRIETARY (see license file)
*/
import { Component, OnInit } from '@angular/core';
import { EventOnMenu } from 'common/component/top-menu/top-menu';
import { MenuItem, MenuPosition } from 'common/model';
import { UserService, SessionService, SSOService } from 'common/service';
import { isNullOrUndefined } from 'common/utils';
import { ArianeService } from './service';
import { UserRoles222 } from 'common/service/session';
enum MenuEventType {
SSO_LOGIN = "SSO_CALL_LOGIN",
SSO_LOGOUT = "SSO_CALL_LOGOUT",
SSO_SIGNUP = "SSO_CALL_SIGNUP",
SEGMENT = "SEGMENT",
TYPE = "TYPE",
ARTIST = "ARTIST",
ALBUM = "ALBUM",
TRACK = "TRACK",
PLAYLIST = "PLAYLIST",
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: [
'./app.component.less',
]
})
export class AppComponent implements OnInit {
title: string = 'Karideo';
autoConnectedDone: boolean = false;
isConnected: boolean = false;
signUpEnable: boolean = true;
currentMenu: MenuItem[] = [];
location: string = "home";
constructor(
private userService: UserService,
private sessionService: SessionService,
private ssoService: SSOService,
private arianeService: ArianeService) {
}
ngOnInit() {
this.autoConnectedDone = false;
this.isConnected = false;
this.updateMainMenu();
let self = this;
this.sessionService.change.subscribe((isConnected) => {
console.log(`receive event from session ...${ isConnected}`);
self.isConnected = isConnected;
self.autoConnectedDone = true;
self.updateMainMenu();
});
this.ssoService.checkSignUpEnable()
.then((value: boolean) => {
console.log(`Get value signUp = ${value}`);
self.signUpEnable = value;
self.updateMainMenu();
}).catch((error: any) => {
console.log(`Can not call the sso to check the sign-up_interface: ${error}`);
});
this.userService.checkAutoConnect().then(() => {
console.log(` ==>>>>> Autoconnect THEN !!!`);
self.autoConnectedDone = true;
}).catch(() => {
console.log(` ==>>>>> Autoconnect CATCH !!!`);
self.autoConnectedDone = true;
}).finally(() => {
console.log(` ==>>>>> Autoconnect FINALLY !!!`);
self.autoConnectedDone = true;
});
this.arianeService.segmentChange.subscribe((_segmentName: string) => {
//console.log(`>>> change typeId=${typeId}`);
self.updateMainMenu();
});
this.arianeService.typeChange.subscribe((_typeId: number) => {
//console.log(`>>> change typeId=${typeId}`);
self.updateMainMenu();
});
this.arianeService.playlistChange.subscribe((_universId: number) => {
//console.log(`>>> change universId=${universId}`);
self.updateMainMenu();
});
this.arianeService.artistChange.subscribe((_artistId: number) => {
//console.log(`>>> change artistId=${artistId}`);
self.updateMainMenu();
});
this.arianeService.albumChange.subscribe((_albumId: number) => {
//console.log(`>>> change albumId=${albumId}`);
self.updateMainMenu();
});
this.arianeService.trackChange.subscribe((_trackId: number) => {
//console.log(`>>> change trackId=${trackId}`);
self.updateMainMenu();
});
}
eventOnMenu(data: EventOnMenu): void {
//console.log(`plopppppppppp ${JSON.stringify(this.route.snapshot.url)}`);
//console.log(`Get event on menu: ${JSON.stringify(data, null, 4)}`);
switch(data.menu.otherData) {
case MenuEventType.SSO_LOGIN:
this.ssoService.requestSignIn();
break;
case MenuEventType.SSO_LOGOUT:
this.ssoService.requestSignOut();
break;
case MenuEventType.SSO_SIGNUP:
this.ssoService.requestSignUp();
break;
case MenuEventType.SEGMENT:
if(this.arianeService.getCurrrentSegment() === "artist") {
this.arianeService.navigateArtist({});
} else if(this.arianeService.getCurrrentSegment() === "gender") {
this.arianeService.navigateGender({});
} else if(this.arianeService.getCurrrentSegment() === "playlist") {
this.arianeService.navigatePlaylist({});
} else if(this.arianeService.getCurrrentSegment() === "track") {
this.arianeService.navigateTrack({});
} else if(this.arianeService.getCurrrentSegment() === "album") {
this.arianeService.navigateAlbum({});
}
break;
case MenuEventType.TYPE:
break;
case MenuEventType.ARTIST:
if(this.arianeService.getCurrrentSegment() === "artist") {
this.arianeService.navigateArtist({artistId: this.arianeService.getArtistId()});
}
break;
case MenuEventType.ALBUM:
break;
case MenuEventType.TRACK:
break;
case MenuEventType.PLAYLIST:
break;
}
}
updateMainMenu(): void {
console.log("update main menu :");
if (this.isConnected) {
this.currentMenu = [
{
position: MenuPosition.LEFT,
hover: `You are logged as: ${this.sessionService.getLogin()}`,
icon: "menu",
title: "Menu",
subMenu: [
{
position: MenuPosition.LEFT,
hover: "Go to Home page",
icon: "home",
title: "Home",
navigateTo: "home",
}, {
position: MenuPosition.LEFT,
icon: "group_work",
title: this.getSegmentDisplayable(),
otherData: MenuEventType.SEGMENT,
callback: true,
enable: this.getSegmentDisplayable() !== "",
}, {
position: MenuPosition.LEFT,
icon: "piano",
title: this.getSegmentDisplayable(),
otherData: MenuEventType.TYPE,
callback: true,
enable: !isNullOrUndefined(this.arianeService.getTypeId()),
}, {
position: MenuPosition.LEFT,
icon: "person",
title: this.arianeService.getArtistName(),
otherData: MenuEventType.ARTIST,
callback: true,
enable: !isNullOrUndefined(this.arianeService.getArtistId()),
}, {
position: MenuPosition.LEFT,
icon: "album",
title: this.arianeService.getAlbumName(),
otherData: MenuEventType.ALBUM,
callback: true,
enable: !isNullOrUndefined(this.arianeService.getAlbumId()),
}, {
position: MenuPosition.LEFT,
icon: "music_note",
title: this.arianeService.getTrackName(),
otherData: MenuEventType.TRACK,
callback: true,
enable: !isNullOrUndefined(this.arianeService.getTrackId()),
}, {
position: MenuPosition.LEFT,
icon: "queue_music",
title: this.arianeService.getPlaylistName(),
otherData: MenuEventType.PLAYLIST,
callback: true,
enable: !isNullOrUndefined(this.arianeService.getPlaylistId()),
}
],
},{
position: MenuPosition.RIGHT,
image: "assets/images/avatar_generic.svg",
title: "",
subMenu: [
{
position: MenuPosition.LEFT,
hover: `You are logged as: <b>${this.sessionService.getLogin()}</b>`,
title: `Sign in as ${this.sessionService.getLogin()}`,
}, {
position: MenuPosition.LEFT,
icon: "add_circle",
title: "Add media",
navigateTo: "upload",
enable: this.sessionService.hasRight(UserRoles222.admin),
}, {
position: MenuPosition.LEFT,
icon: "settings",
title: "Settings",
navigateTo: "settings",
}, {
position: MenuPosition.LEFT,
icon: "help",
title: "Help",
navigateTo: "help",
}, {
position: MenuPosition.LEFT,
hover: "Exit connection",
icon: "exit_to_app",
title: "Sign out",
callback: true,
otherData: MenuEventType.SSO_LOGOUT,
},
],
},
];
} else {
this.currentMenu = [
{
position: MenuPosition.LEFT,
hover: "Go to Home page",
icon: "home",
title: "Home",
navigateTo: "home",
}, {
position: MenuPosition.RIGHT,
hover: "Create a new account",
icon: "add_circle_outline",
title: "Sign-up",
callback: true,
model: this.signUpEnable?undefined:"disable",
otherData: MenuEventType.SSO_SIGNUP,
}, {
position: MenuPosition.RIGHT,
hover: "Login page",
icon: "account_circle",
title: "Sign-in",
callback: true,
otherData: MenuEventType.SSO_LOGIN,
},
];
}
console.log(" ==> DONE");
}
getSegmentDisplayable(): string {
let segment = this.arianeService.getCurrrentSegment();
if (segment === "artist") {
return "Artists"
}
if (segment === "gender") {
return "Genders"
}
if (segment === "album") {
return "Albums"
}
if (segment === "track") {
return "Tracks"
}
if (segment === "playlist") {
return "Playlistq"
}
return "";
}
}

View File

@@ -1,94 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2018, Edouard DUPIN, all right reserved
* @license PROPRIETARY (see license file)
*/
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
import { RouterModule } from '@angular/router';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { ElementDataImageComponent } from './component/data-image/data-image';
import { ElementTypeComponent } from './component/element-type/element-type';
import { PopInCreateType } from './popin/create-type/create-type';
import { AppComponent } from './app.component';
import {
HomeScene, HelpScene, GenderScene, PlaylistScene, ArtistScene, AlbumScene, AlbumsScene, TrackScene, SettingsScene,
TrackEditScene, AlbumEditScene, ArtistEditScene, ArtistsScene, ArtistAlbumScene
} from './scene';
import { GenderService, DataService, PlaylistService, ArtistService, AlbumService, TrackService, ArianeService, PlayerService } from './service';
import { UploadScene } from './scene/upload/upload';
import { ElementSeriesComponent, ElementTrackComponent, ElementSeasonComponent, ElementVideoComponent, ElementPlayerAudioComponent } from './component';
import { common_module_declarations, common_module_imports, common_module_providers, common_module_exports } from 'common/module';
@NgModule({
declarations: [
AppComponent,
ElementDataImageComponent,
ElementTypeComponent,
ElementSeriesComponent,
ElementTrackComponent,
ElementSeasonComponent,
ElementVideoComponent,
ElementPlayerAudioComponent,
PopInCreateType,
HomeScene,
HelpScene,
GenderScene,
PlaylistScene,
ArtistAlbumScene,
ArtistsScene,
ArtistScene,
AlbumScene,
AlbumsScene,
TrackScene,
SettingsScene,
TrackEditScene,
AlbumEditScene,
ArtistEditScene,
UploadScene,
...common_module_declarations,
],
imports: [
BrowserModule,
RouterModule,
AppRoutingModule,
HttpClientModule,
...common_module_imports,
],
providers: [
PlayerService,
GenderService,
DataService,
PlaylistService,
ArtistService,
AlbumService,
TrackService,
ArianeService,
...common_module_providers,
],
exports: [
AppComponent,
ElementTypeComponent,
ElementSeriesComponent,
ElementSeasonComponent,
ElementVideoComponent,
PopInCreateType,
...common_module_exports,
],
bootstrap: [
AppComponent
],
/*
schemas: [
CUSTOM_ELEMENTS_SCHEMA,
NO_ERRORS_SCHEMA
]
*/
})
export class AppModule { }

View File

@@ -1,2 +0,0 @@
<!--<canvas width="200" height="250" style="border:1px solid #d3d3d3;" id="imageCanvas" #imageCanvas></canvas>-->
<img src="{{cover}}"/>

View File

@@ -1,5 +0,0 @@
img {
max-height: 250px;
max-width: 200px;
}

View File

@@ -1,58 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2018, Edouard DUPIN, all right reserved
* @license PROPRIETARY (see license file)
*/
import { Component, OnInit, Input } from '@angular/core';
//import { ModelResponseHttp } from '@app/service/http-wrapper';
import { DataService } from 'app/service/data';
@Component({
selector: 'data-image',
templateUrl: './data-image.html',
styleUrls: [ './data-image.less' ]
})
export class ElementDataImageComponent implements OnInit {
// input parameters
@Input() id:number = -1;
cover: string = '';
/*
imageCanvas:any;
@ViewChild('imageCanvas')
set mainDivEl(el: ElementRef) {
if(el !== null && el !== undefined) {
this.imageCanvas = el.nativeElement;
}
}
*/
constructor(private dataService: DataService) {
}
ngOnInit() {
this.cover = this.dataService.getCoverThumbnailUrl(this.id);
/*
let canvas = this.imageCanvas.nativeElement;
let ctx = canvas.getContext("2d");
console.log(`Request thumnail for ---> ${this.id}`);
this.dataService.getImageThumbnail(this.id)
.then((result:ModelResponseHttp) => {
console.log(`plop ---> ${result.status}`);
const response = result.data as Response;
response.blob().then((value:Blob) => {
let img = new Image();
img.onload = function() {
//ctx.drawImage(img, 0, 0)
}
let imageUrl = URL.createObjectURL(value);
console.log(`get new image url blob: ${imageUrl}`);
//img.src = imageUrl;
})
}).catch(()=>{
console.log("plop ---> ");
});
//img.src = "../../assets/aCRF-PRV111_CLN-001 v1.4-images/aCRF-PRV111_CLN-001 v1.4-blank_0.jpg";
//ctx.drawImage(img, 10, 10, 250, 250);
*/
}
}

View File

@@ -1,46 +0,0 @@
<div class="bottom" *ngIf="mediaSource">
<div class="media" >
<div class="media-elem">
<audio width="1" height="1" src="{{mediaSource}}"
#mediaPlayer
preload
(play)="changeStateToPlay()"
(pause)="changeStateToPause()"
(timeupdate)="changeTimeupdate($event.currentTime)"
(durationchange)="changeDurationchange($event.duration)"
(loadedmetadata)="changeMetadata()"
autoplay
(ended)="onAudioEnded()"
><!-- controls > --> <!--preload="none"-->
<!--<p>Your browser does not support HTML5 media player. download media: <a href="{{mediaSource}}>link here</a>.</p>-->
</audio>
</div>
<div class="controls">
<div class="timer">
<div class="timer-title">
<label class="unselectable label">{{nameData}}</label>
</div>
<div class="timer-lines">
<input type="range" min="0" class="slider"
[value]="currentTime"
[max]="duration"
(input)="seek($event.target)">
</div>
<div class="timer-text">
<label class="unselectable label">{{currentTimeDisplay}} / {{durationDisplay}}</label>
</div>
<div class="timer-control">
<button (click)="onPlay()" *ngIf="!isPlaying" ><i class="material-icons">play_arrow</i></button>
<button (click)="onPause()" *ngIf="isPlaying" ><i class="material-icons">pause</i></button>
<button (click)="onStop()" ><i class="material-icons">stop</i></button>
<button [disabled]="havePrevious" (click)="onBefore()"><i class="material-icons">navigate_before</i></button>
<button (click)="onRewind()"><i class="material-icons">fast_rewind</i></button>
<button (click)="onForward()"><i class="material-icons">fast_forward</i></button>
<button [disabled]="haveNext" (click)="onNext()"><i class="material-icons">navigate_next</i></button>
<button (click)="onPlayMode()"><i class="material-icons">{{playMode}}</i></button>
<!--<button (click)="onMore()" ><i class="material-icons">more_vert</i></button>-->
<!--<button (click)="onTakeScreenShoot()"><i class="material-icons">add_a_photo</i></button>-->
</div>
</div>
</div>
</div>

View File

@@ -1,159 +0,0 @@
.bottom {
.controls {
//visibility: hidden;
opacity: 0.85;
width: 96%;
height: 150px;
border-radius: 10px;
position: absolute;
bottom: 20px;
left: 2%;
//margin-left: -200px;
background-color: black;
box-shadow: 3px 3px 5px black;
transition: 1s all;
display: flex;
font-size: 40px;
.material-icons {
color: #FFF;
font-size: 40px;
}
button {
border: none;
background: none;
}
button:disabled,
button[disabled] {
.material-icons {
color: rgb(46, 46, 46);
}
}
.slidecontainer {
line-height: 38px;
font-size: 10px;
font-family: monospace;
text-shadow: 1px 1px 0px black;
color: white;
flex: 5;
position: relative;
}
.slider {
position: relative;
-webkit-appearance: none;
//width: calc(100% - 60px);
width: 95%;
height: 10px;
top: 5px;
border-radius: 5px;
background: #d3d3d3;
outline: none;
opacity: 0.7;
-webkit-transition: .2s;
transition: opacity .2s;
flex: 5;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 25px;
height: 25px;
border-radius: 50%;
background: #4CAF50;
cursor: pointer;
}
.slider::-moz-range-thumb {
width: 25px;
height: 25px;
border-radius: 50%;
background: #4CAF50;
cursor: pointer;
}
.timer-title {
position: absolute;
left: 30px;
width: 100%;
font-size: 20px;
font-weight:bold;
bottom: 104px;
}
.timer-lines {
position: absolute;
left: 30px;
//width: calc(100%-60px);
font-size: 20px;
font-weight:bold;
bottom: 82px;
}
.timer-text {
position: absolute;
left: 30px;
width: 100%;
font-size: 20px;
font-weight:bold;
bottom: 40px;
}
.timer-control {
position: absolute;
//top: 25px;
left: 0px;
width: 100%;
//line-height: 38px;
font-size: 30px;
font-weight:bold;
bottom: 0px;
}
.label {
//transform: translate(-12px,-12px);
vertical-align: text-top;
line-height: 18px;
min-width: 50%;
//display: block,
}
}
/*
:hover .controls, :focus .controls {
opacity: 1;
}
*/
button:before {
font-family: HeydingsControlsRegular;
font-size: 30px;
position: relative;
color: #FFF;
//text-shadow: 1px 1px 0px black;
}
.timer {
line-height: 38px;
font-size: 30px;
font-family: monospace;
text-shadow: 1px 1px 0px black;
color: white;
flex: 5;
position: relative;
}
.timer div {
width:100%;
line-height: 38px;
font-size: 10px;
font-family: monospace;
text-shadow: 1px 1px 0px black;
color: white;
flex: 5;
position: relative;
}
.timer span {
position: absolute;
z-index: 3;
left: 19px;
}
}

View File

@@ -1,501 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2018, Edouard DUPIN, all right reserved
* @license PROPRIETARY (see license file)
*/
import { Component, OnInit, Input, ViewChild, ElementRef } from '@angular/core';
import { isArray, isArrayOf, isNullOrUndefined, isNumberFinite } from 'common/utils';
import { NodeData } from 'common/model';
import { GenderService, DataService, PlayerService, TrackService, AlbumService, ArtistService } from 'app/service';
import { PlaylistCurrent } from 'app/service/player';
import { Media } from 'app/model';
import { HttpWrapperService } from 'common/service';
import { Title } from '@angular/platform-browser';
import { environment } from 'environments/environment';
export enum PlayMode {
PLAY_ONE = "check",
PLAY_ALL = "trending_flat",
PLAY_ONE_LOOP = "repeat_one",
PLAY_ALL_LOOP = "repeat",
};
// note: all the input is managed with the player service ...
@Component({
selector: 'app-element-player-audio',
templateUrl: './element-player-audio.html',
styleUrls: [ './element-player-audio.less' ]
})
export class ElementPlayerAudioComponent implements OnInit {
mediaPlayer: HTMLAudioElement;
duration: number;
durationDisplay: string;
@ViewChild('mediaPlayer')
set mainVideoEl(el: ElementRef) {
if(el !== null && el !== undefined) {
this.mediaPlayer = el.nativeElement;
}
}
public mediaSource: string = undefined;
public name: string = 'rr';
public description: string;
public counttrack: number;
public covers: string[];
public volume_value: number = 100;
public volume_displayMenu: boolean = false;
nameData: string;
playStream: boolean;
isPlaying: boolean;
currentTime: any;
currentTimeDisplay: string;
havePrevious: boolean = false;
haveNext: boolean = false;
playMode: PlayMode = PlayMode.PLAY_ALL;
onPlayMode() {
if (this.playMode === PlayMode.PLAY_ONE) {
this.playMode = PlayMode.PLAY_ALL;
} else if (this.playMode === PlayMode.PLAY_ALL) {
this.playMode = PlayMode.PLAY_ONE_LOOP;
} else if (this.playMode === PlayMode.PLAY_ONE_LOOP) {
this.playMode = PlayMode.PLAY_ALL_LOOP;
} else {
this.playMode = PlayMode.PLAY_ONE;
}
}
constructor(
private playerService: PlayerService,
private trackService: TrackService,
private albumService: AlbumService,
private artistService: ArtistService,
private httpService: HttpWrapperService,
private dataService: DataService,
private titleService: Title) {
// nothing to do...
}
private currentLMedia: Media;
private localIdStreaming: number;
private localListStreaming: number[] = [];
ngOnInit() {
/*
export interface PlaylistCurrent {
playTrackList: number[];
trackLocalId?: number;
}
*/
let self = this;
this.playerService.playChange.subscribe((newProperty: PlaylistCurrent) => {
self.localIdStreaming = newProperty.trackLocalId;
self.localListStreaming = newProperty.playTrackList;
self.updateMediaStreamed();
});
}
updateMediaStreamed() {
if (isNullOrUndefined(this.localIdStreaming)) {
this.mediaSource = undefined;
return;
}
let self = this;
this.havePrevious = this.localIdStreaming <= 0;
this.haveNext = this.localIdStreaming >= this.localListStreaming.length-1;
this.trackService.get(this.localListStreaming[this.localIdStreaming])
.then((response: Media) => {
//console.log(`get response of video : ${ JSON.stringify(response, null, 2)}`);
self.currentLMedia = response;
self.nameData = response.name;
if (!isNullOrUndefined(response.albumId)) {
this.albumService.get(response.albumId)
.then((response2: NodeData) => {
self.nameData = response2.name + " - " + self.nameData;
this.titleService.setTitle(`${environment.applName} > ${self.nameData}`)
})
.catch(() => {});
}
if (!isNullOrUndefined(response.artists) && isArray(response.artists) && response.artists.length > 0) {
this.artistService.get(response.artists[0])
.then((response2: NodeData) => {
self.nameData = `${self.nameData} (${response2.name})`;
this.titleService.setTitle(`${environment.applName} > ${self.nameData}`)
})
.catch(() => {});
}
if (isNullOrUndefined(self.currentLMedia)) {
console.log("Can not retreive the media ...")
return;
}
if (isNullOrUndefined(self.currentLMedia.dataId)) {
console.log("Can not retreive the media ... null or undefined data ID")
return;
}
self.mediaSource = self.httpService.createRESTCall2({
api: `data/${self.currentLMedia.dataId}/unknowMediaName.webm`, //${self.generatedName}`,
addURLToken: true,
});
}).catch(() => {
console.error("error not ùmanaged in audio player ... 111");
})
}
generateName() {
/*
this.generatedName = '';
if(this.seriesName !== undefined) {
this.generatedName = `${this.generatedName }${this.seriesName }-`;
}
if(this.seasonName !== undefined) {
if(this.seasonName.length < 2) {
this.generatedName = `${this.generatedName }s0${ this.seasonName }-`;
} else {
this.generatedName = `${this.generatedName }s${ this.seasonName }-`;
}
}
if(this.episode !== undefined) {
if(this.episode < 10) {
this.generatedName = `${this.generatedName }e0${ this.episode }-`;
} else {
this.generatedName = `${this.generatedName }e${ this.episode }-`;
}
}
this.generatedName = this.generatedName + this.name;
this.generatedName = this.generatedName.replace(new RegExp('&', 'g'), '_');
this.generatedName = this.generatedName.replace(new RegExp('/', 'g'), '_');
// update the path of the uri request
this.mediaSource = this.httpService.createRESTCall2({
api: `data/${ this.dataId}/${this.generatedName}`,
addURLToken: true,
});
*/
}
myPeriodicCheckFunction() {
console.log('check ... ');
}
changeMetadata() {
console.log('list of the stream:');
/*
let captureStream = this.audioPlayer.audioTracks;
for (let iii=0; iii < captureStream.length; iii++) {
console.log(" - " + captureStream[iii].language);
if (captureStream[iii].language.substring(0,2) === "fr") {
captureStream[iii].enabled = true;
} else {
captureStream[iii].enabled = false;
}
}
*/
}
/*
ngOnInit() {
let self = this;
this.arianeService.updateManual(this.route.snapshot.paramMap);
this.idVideo = this.arianeService.getVideoId();
this.arianeService.mediaChange.subscribe((mediaId) => {
console.log(`Detect mediaId change...${ mediaId}`);
self.idVideo = mediaId;
self.updateDisplay();
});
self.updateDisplay();
}
*/
updateDisplay():void {
let self = this;
/*
self.haveNext = null;
self.havePrevious = null;
this.mediaService.get(this.idVideo)
.then((response) => {
console.log(`get response of media : ${ JSON.stringify(response, null, 2)}`);
self.error = '';
self.name = response.name;
self.description = response.description;
self.episode = response.episode;
self.seriesId = response.seriesId;
self.seasonId = response.seasonId;
self.dataId = response.dataId;
self.time = response.time;
self.generatedName = "????????? TODO: ???????" //response.generatedName;
if(self.dataId !== -1) {
self.mediaSource = self.httpService.createRESTCall2({
api: `data/${ self.dataId}/${self.generatedName}`,
addURLToken: true,
});
} else {
self.mediaSource = '';
}
self.covers = self.dataService.getCoverListUrl(response.covers);
self.generateName();
if(self.seriesId !== undefined && self.seriesId !== null) {
self.seriesService.get(self.seriesId)
.then((response2) => {
self.seriesName = response2.name;
self.generateName();
}).catch((response3) => {
// nothing to do ...
});
}
if(self.seasonId !== undefined && self.seasonId !== null) {
self.seasonService.get(self.seasonId)
.then((response4) => {
self.seasonName = response4.name;
self.generateName();
}).catch((response5) => {
// nothing to do ...
});
self.seasonService.getVideo(self.seasonId)
.then((response6:any) => {
// console.log("saison property: " + JSON.stringify(response, null, 2));
self.haveNext = null;
self.havePrevious = null;
for(let iii = 0; iii < response6.length; iii++) {
if(isNullOrUndefined(response6[iii].episode)) {
continue;
}
if(response6[iii].episode < self.episode) {
if(self.havePrevious === null) {
self.havePrevious = response6[iii];
} else if(self.havePrevious.episode < response6[iii].episode) {
self.havePrevious = response6[iii];
}
} else if(response6[iii].episode > self.episode) {
if(self.haveNext === null) {
self.haveNext = response6[iii];
} else if(self.haveNext.episode > response6[iii].episode) {
self.haveNext = response6[iii];
}
}
self.covers.push(self.dataService.getCoverUrl(response6[iii]));
}
}).catch((response7:any) => {
});
}
self.mediaIsLoading = false;
// console.log("display source " + self.mediaSource);
// console.log("set transformed : " + JSON.stringify(self, null, 2));
}).catch((response8) => {
self.error = 'Can not get the data';
self.name = '';
self.description = '';
self.episode = undefined;
self.seriesId = undefined;
self.seasonId = undefined;
self.dataId = -1;
self.time = undefined;
self.generatedName = '';
self.mediaSource = '';
self.covers = undefined;
self.seriesName = undefined;
self.seasonName = undefined;
self.mediaIsNotFound = true;
self.mediaIsLoading = false;
});
*/
}
onRequirePlay() {
this.playStream = true;
}
onRequireStop() {
this.playStream = false;
}
onAudioEnded() {
if (this.playMode === PlayMode.PLAY_ONE) {
this.playStream = false;
} else if (this.playMode === PlayMode.PLAY_ALL) {
this.onNext();
} else if (this.playMode === PlayMode.PLAY_ONE_LOOP) {
this.mediaPlayer.currentTime = 0;
this.onPlay();
} else {
if (this.localIdStreaming == this.localListStreaming.length-1) {
this.localIdStreaming = 0;
this.updateMediaStreamed();
if (this.localListStreaming.length == 1) {
this.mediaPlayer.currentTime = 0;
this.onPlay();
}
} else {
this.onNext();
}
}
}
changeStateToPlay() {
this.isPlaying = true;
}
changeStateToPause() {
this.isPlaying = false;
}
convertIndisplayTime(time:number) : string {
let tmpp = parseInt(`${ time}`, 10);
let heures = parseInt(`${ tmpp / 3600}`, 10);
tmpp = tmpp - heures * 3600;
let minutes = parseInt(`${ tmpp / 60}`, 10);
let seconds = tmpp - minutes * 60;
let out = '';
if(heures !== 0) {
out = `${out }${heures }:`;
}
if(minutes >= 10) {
out = `${out }${minutes }:`;
} else {
out = `${out }0${ minutes }:`;
}
if(seconds >= 10) {
out = out + seconds;
} else {
out = `${out }0${ seconds}`;
}
return out;
}
changeTimeupdate(currentTime: any) {
// console.log("time change ");
// console.log(" ==> " + this.audioPlayer.currentTime);
this.currentTime = this.mediaPlayer.currentTime;
this.currentTimeDisplay = this.convertIndisplayTime(this.currentTime);
// console.log(" ==> " + this.currentTimeDisplay);
}
changeDurationchange(duration: any) {
console.log('duration change ');
console.log(` ==> ${ this.mediaPlayer.duration}`);
this.duration = this.mediaPlayer.duration;
this.durationDisplay = this.convertIndisplayTime(this.duration);
}
onPlay() {
console.log('play');
if(isNullOrUndefined(this.mediaPlayer)) {
console.log(`error element: ${ this.mediaPlayer}`);
return;
}
this.lockPlayerEnable();
this.mediaPlayer.play();
}
onPause() {
console.log('pause');
if(isNullOrUndefined(this.mediaPlayer)) {
console.log(`error element: ${ this.mediaPlayer}`);
return;
}
this.unlockPlayerEnable();
this.mediaPlayer.pause();
}
onPauseToggle() {
console.log('pause toggle ...');
if(this.isPlaying === true) {
this.onPause();
} else {
this.onPlay();
}
}
onStop() {
console.log('stop');
if(isNullOrUndefined(this.mediaPlayer)) {
console.log(`error element: ${ this.mediaPlayer}`);
return;
}
this.unlockPlayerEnable();
this.mediaPlayer.pause();
this.mediaPlayer.currentTime = 0;
}
onBefore() {
console.log('before');
if (this.localIdStreaming <= 0) {
console.error("have no previous !!!");
return;
}
this.localIdStreaming--;
this.updateMediaStreamed();
this.playerService.previous();
}
onNext() {
console.log('next');
if (this.localIdStreaming >= this.localListStreaming.length-1) {
console.error("have no next !!!");
return;
}
this.localIdStreaming++;
this.updateMediaStreamed();
this.playerService.next();
}
seek(newValue:any) {
console.log(`seek ${ newValue.value}`);
if(isNullOrUndefined(this.mediaPlayer)) {
console.log(`error element: ${ this.mediaPlayer}`);
return;
}
this.mediaPlayer.currentTime = newValue.value;
}
onRewind() {
console.log('rewind');
if(isNullOrUndefined(this.mediaPlayer)) {
console.log(`error element: ${ this.mediaPlayer}`);
return;
}
this.mediaPlayer.currentTime = this.currentTime - 10;
}
onForward() {
console.log('forward');
if(isNullOrUndefined(this.mediaPlayer)) {
console.log(`error element: ${ this.mediaPlayer}`);
return;
}
this.mediaPlayer.currentTime = this.currentTime + 10;
}
onMore() {
console.log('more');
if(isNullOrUndefined(this.mediaPlayer)) {
console.log(`error element: ${ this.mediaPlayer}`);
return;
}
}
// these element is to permit to not stop music when sceen is off !!
wakeLock = null;
unlockPlayerEnable() {
let tmppp = this.wakeLock;
this.wakeLock = null;
if (!isNullOrUndefined(tmppp)) {
tmppp.release();
}
try {
let tmp = navigator as any;
this.wakeLock = tmp.wakeLock.request('screen');
} catch (err) {
// The Wake Lock request has failed - usually system related, such as battery.
console.log(`Can not lock screen element: ${err.name}, ${err.message}`);
}
}
lockPlayerEnable() {
if ('wakeLock' in navigator) {
try {
let tmp = navigator as any;
this.wakeLock = tmp.wakeLock.request('screen');
} catch (err) {
// The Wake Lock request has failed - usually system related, such as battery.
console.log(`Can not lock screen element: ${err.name}, ${err.message}`);
}
}
}
}

View File

@@ -1,41 +0,0 @@
<div class="imgContainer-small">
<div *ngIf="covers">
<!--<data-image id="{{cover}}"></data-image>-->
<img src="{{covers[0]}}"/>
</div>
<div *ngIf="!covers" class="noImage">
</div>
</div>
<div class="season-small">
{{prefixName}} {{numberAlbum}}
</div>
<div class="description-small" *ngIf="countSubType && (!count || count == 0)">
No {{countSubType}}
</div>
<div class="description-small" *ngIf="countSubType && count > 1">
{{count}} {{countSubType}}s
</div>
<div class="description-small" *ngIf="countSubType && count == 1">
{{count}} {{countSubType}}
</div>
<div class="description-small" *ngIf="countSubType2 && (!count2 || count2 == 0)">
No {{countSubType2}}
</div>
<div class="description-small" *ngIf="countSubType2 && count2 > 1">
{{count2}} {{countSubType2}}s
</div>
<div class="description-small" *ngIf="countSubType2 && count2 == 1">
{{count2}} {{countSubType2}}
</div>
<div class="description-small" *ngIf="subValueData">
{{subValues}}: {{subValueData}}
</div>
<div class="description-small" *ngIf="!description">
{{description}}
</div>

View File

@@ -1,35 +0,0 @@
.imgContainer-small {
text-align: center;
width: 100px;
margin: 4px 15px 4px 10px;
height: 100px;
img {
max-height: 100px;
max-width: 100px;
}
.noImage {
height: 95px;
width: 95px;
//box-shadow: 0px 2px 4px 0 rgba(0, 0, 0, 0.7);
border: solid 2px;
border-color: rgba(0, 0, 0, 0.7);
margin: auto;
}
float: left;
}
.title-small {
height: 50px;
//width: 100%;
font-size: 35px;
display:inline-block;
font-weight: bold;
}
.description-small {
height: 30px;
font-size: 16px;
overflow:hidden;
vertical-align: middle;
}

View File

@@ -1,90 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2018, Edouard DUPIN, all right reserved
* @license PROPRIETARY (see license file)
*/
import {Component, OnInit, Input } from '@angular/core';
import { AlbumService, DataService } from 'app/service';
import { NodeData } from 'common/model';
import { isNullOrUndefined } from 'common/utils';
@Component({
selector: 'app-element-season',
templateUrl: './element-season.html',
styleUrls: [ './element-season.less' ]
})
export class ElementSeasonComponent implements OnInit {
// input parameters
@Input() element:NodeData;
@Input() prefix:String;
@Input() countSubTypeCallBack: (arg0: number) => Promise<number>;
@Input() countSubType: String;
@Input() countSubType2CallBack: (arg0: number) => Promise<number>;
@Input() countSubType2: String;
@Input() subValuesCallBack: (arg0: number) => Promise<string[]>;
@Input() subValues: String;
prefixName: string = "";
numberAlbum: string;
count: number;
count2: number;
subValueData: String;
covers: string[];
description: string;
constructor(
private albumService: AlbumService,
private dataService: DataService) {
}
ngOnInit() {
this.prefix = this.prefixName??"";
this.count = undefined;
this.count2 = undefined;
if (isNullOrUndefined(this.element)) {
this.numberAlbum = undefined;
this.covers = undefined;
this.description = undefined;
}
this.numberAlbum = this.element.name;
this.description = this.element.description;
this.covers = this.dataService.getCoverListThumbnailUrl(this.element.covers);
let self = this;
if (!isNullOrUndefined(this.countSubTypeCallBack)) {
this.countSubTypeCallBack(this.element.id)
.then((response) => {
self.count = response;
//console.log(`get values: ${this.element.id} ==> ${self.count}`)
}).catch((response) => {
self.count = undefined;
});
}
if (!isNullOrUndefined(this.countSubType2CallBack)) {
this.countSubType2CallBack(this.element.id)
.then((response) => {
self.count2 = response;
//console.log(`get values: ${this.element.id} ==> ${self.count}`)
}).catch((response) => {
self.count2 = undefined;
});
}
if (!isNullOrUndefined(this.subValuesCallBack)) {
//console.log(`Value call back define ==> call values`)
this.subValuesCallBack(this.element.id)
.then((response: string[]) => {
this.subValueData = "";
for (let kkk=0; kkk<response.length; kkk++) {
if (kkk != 0) {
this.subValueData += ", ";
}
this.subValueData += response[kkk];
}
//console.log(`get values: ${this.element.id} ==> ${self.subValueData}`)
}).catch((response) => {
self.count2 = undefined;
});
}
}
}

View File

@@ -1,24 +0,0 @@
<div>
<div class="count-base">
<div class="count" *ngIf="counttrack">
{{counttrack}}
</div>
</div>
<div class="imgContainer-small">
<div *ngIf="covers">
<!--<data-image id="{{cover}}"></data-image>-->
<img src="{{covers[0]}}"/>
</div>
<div *ngIf="!covers" class="noImage">
</div>
</div>
<div class="title-small">
{{name}}
</div>
<!--
<div class="description-small" *ngIf="description">
{{description}}
</div>
-->
</div>

View File

@@ -1,63 +0,0 @@
.count-base {
height: 0px;
width: 0px;
.count {
height: 30px;
width: 30px;
font-size: 17px;
line-height: 30px;
overflow:hidden;
position:relative;
z-index: 12;
left: 165px;
top: 2px;
text-align: center;
//padding: 5px 10px;
color: rgba(0, 0, 0, 1.0);
background: rgba(256, 256, 256, 0.3);
border: 1px solid;
border-color: rgba(256, 256, 256, 0.8);
border-radius: 15px;
}
}
.imgContainer-small {
right: 2px;
top: 2px;
text-align: center;
width: 200px;
margin: 0 auto;
height: 250px;
img {
//width: 100%;
max-height: 250px;
max-width: 200px;
}
.noImage {
height: 243px;
width: 193px;
border: solid 3px;
border-color: rgba(0, 0, 0, 0.7);
margin: auto;
}
}
.title-small {
height: 40px;
width: 200px;
font-size: 17px;
overflow:hidden;
display:inline-block;
text-align: center;
padding:auto;
background: rgba(256, 256, 256, 0.3);
border-radius: 7px;
}
.description-small {
height: 30px;
font-size: 12px;
overflow:hidden;
vertical-align: middle;
}

View File

@@ -1,50 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2018, Edouard DUPIN, all right reserved
* @license PROPRIETARY (see license file)
*/
import { Component, OnInit, Input } from '@angular/core';
import { NodeData } from 'common/model';
import { isNullOrUndefined } from 'common/utils';
import { ArtistService, DataService } from 'app/service';
@Component({
selector: 'app-element-series',
templateUrl: './element-series.html',
styleUrls: [ './element-series.less' ]
})
export class ElementSeriesComponent implements OnInit {
// input parameters
@Input() element:NodeData;
name:string = 'plouf';
description:string = '';
counttrack:number = null;
covers:string[];
constructor(
private artistService: ArtistService,
private dataService: DataService) {
}
ngOnInit() {
if (isNullOrUndefined(this.element)) {
this.name = '!!ERROR!!';
this.description = undefined;
return;
}
let self = this;
self.name = this.element.name;
self.description = this.element.description;
self.covers = self.dataService.getCoverListThumbnailUrl(this.element.covers);
this.artistService.countTrack(this.element.id)
.then((response) => {
self.counttrack = response;
}).catch((response) => {
self.counttrack = 0;
});
}
}

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