62 Commits

Author SHA1 Message Date
Edouard DUPIN
ba0ab6d57b [FEAT] update gui 2025-10-20 21:15:22 +02:00
Edouard DUPIN
ef17ad203d [FIX] randoùm 2025-10-18 21:48:41 +02:00
Edouard DUPIN
19f08d36cf [FIX] the cover display 2025-10-18 21:34:40 +02:00
Edouard DUPIN
141c8f1659 [FEAT] review view of media to have episode number on the left 2025-10-18 21:34:16 +02:00
Edouard DUPIN
dd677afff0 [FEAT] st rt cover asynchronopusly 2025-10-18 21:33:31 +02:00
Edouard DUPIN
cc0d502229 [FEAT] add zebra when display media from other season or types (and add comment) 2025-10-18 19:46:09 +02:00
Edouard DUPIN
0d32740ec7 [FEAT] in type list view of series permit top add many cover from files 2025-10-18 19:45:14 +02:00
Edouard DUPIN
839236ecd1 [FEAT] in group edit add capacity to manage empty season 2025-10-18 19:44:38 +02:00
Edouard DUPIN
4c7a9e27e3 [FEAT] filter series with select a type in insertion page 2025-10-18 19:43:49 +02:00
Edouard DUPIN
08879a6860 [FEAT] filter option when edit media ==> permit to prevent errors 2025-10-18 19:43:20 +02:00
Edouard DUPIN
f72645e05c [FEAT] add description in media display 2025-10-18 19:42:47 +02:00
Edouard DUPIN
e834e0f096 [FEAT] add batch generation of covers 2025-10-18 16:07:40 +02:00
Edouard DUPIN
4eca38ad24 [FIX] naming of the variables 2025-10-18 16:07:23 +02:00
Edouard DUPIN
7543f07562 [FEAT] clean cover generation 2025-10-18 16:06:41 +02:00
Edouard DUPIN
a90be58013 [FIX] name of the return button in pop-up 2025-10-18 14:33:01 +02:00
Edouard DUPIN
93a643d10d [FIX] prevent exit pop-up when editing 2025-10-18 12:37:55 +02:00
Edouard DUPIN
85c52fac2a [FEAT] add capability to extract 10 screenshoot from video 2025-10-18 12:37:31 +02:00
Edouard DUPIN
8239e387fb [FIX] some display 2025-10-18 12:36:58 +02:00
Edouard DUPIN
b4e17d6853 [FEAT] add a composnent that permit to add many covers extract from the video 2025-10-18 12:36:09 +02:00
Edouard DUPIN
ca16e3c43a [FEAT] update wideo player with some feature:
- take a picture
  - speed change
  - volume change
  - full page size (when fullscreen is not possible)
  - unmute as start
2025-10-18 12:35:34 +02:00
Edouard DUPIN
5f9247391a [FEAT] review cover to have a corect dispaly 2025-10-18 12:33:55 +02:00
Edouard DUPIN
ee12030cf6 [FEAT] add audio level and review hide of elements 2025-10-16 23:49:27 +02:00
Edouard DUPIN
b062fb0b97 [FEAT] init from backup 2025-10-15 23:22:44 +02:00
Edouard DUPIN
01c72af17a [FEAT] update Archidata 2025-10-15 21:55:09 +02:00
Edouard DUPIN
b28eb6d3b7 [FEAT] update dockerfile 2025-10-15 21:54:36 +02:00
Edouard DUPIN
5d2392ad9e [FEAT] add help to deploy on Mongo 2025-10-12 12:20:05 +02:00
Edouard DUPIN
efb334f8de [FIX] upload files 2025-10-05 15:27:49 +02:00
Edouard DUPIN
d4b1c6a9e1 [Fix interfaces 2025-10-05 14:55:32 +02:00
Edouard DUPIN
f7a62560c7 [FIX] set back normal ionitalisation 2025-10-05 13:14:07 +02:00
Edouard DUPIN
e7393c0192 [DEV] migrate to mongo in init 2025-10-05 13:12:53 +02:00
Edouard DUPIN
bc647ce9be [FEAT] moigrate to mongo 2025-09-29 23:27:58 +02:00
Edouard DUPIN
af6cb7d1ec [FEAT] work on mongo migration 2025-09-28 23:56:24 +02:00
Edouard DUPIN
a88e7d2ce5 qsdqsdqsdef 2025-07-04 18:14:55 +02:00
3d78b90ff1 [FEAT] ready to deploy 2025-04-16 19:53:24 +02:00
3d7ee42ca8 [FEAT] update to the new archidata 0.28.0, and update namespace kar-> atriasoft 2025-04-15 00:18:49 +02:00
77acd2060b [FEAT] set edition pop-up (not full fisished but operational 2025-02-26 15:45:51 +01:00
88f65f0806 [FEAT] upgrade upload of data... 2025-02-26 15:00:14 +01:00
6df71e3341 [FIX] correct fullsceen hide of menu 2025-02-26 00:20:21 +01:00
263a4ae4c0 [FEAT] update upload of data 2025-02-26 00:09:58 +01:00
57a48de52f [FEAT] update fullscreen management 2025-02-25 00:12:30 +01:00
ae9f00a60a update back dependency 2025-02-24 12:38:02 +01:00
93b95becb7 [DEV] add save of file move 2025-02-24 10:32:20 +01:00
bd66703a08 [FEAT] add the stack needed 2025-02-24 09:34:00 +01:00
86fec254aa [DEV] initiate migration 2025-02-18 22:00:21 +01:00
6f16bffe31 [DEV] move angular folder 2025-02-14 23:25:42 +01:00
1fc1b5d35d [DEV] update new archidata 2025-02-14 23:23:25 +01:00
7d6e3bb8c8 [VERSION] update dev tag version 2024-05-12 12:03:48 +02:00
11b8b575e4 [RELEASE] Release v0.3.0 2024-05-12 12:03:48 +02:00
dfa245e842 [DEV] update with new model 2024-05-12 11:58:36 +02:00
ed94562372 [FEAT] udpaet release for island 2024-05-12 11:58:04 +02:00
5fee3a1390 [DEV] update the API generation and video button 2024-04-22 23:08:29 +02:00
96bc7ec052 [FIX] correct release 2024-04-18 00:01:26 +02:00
94d2fd0fc8 [VERSION] update version v0.2.1 2024-04-17 23:39:22 +02:00
d8ceaef3f9 [DEV] add drag & drop 2024-04-17 23:38:00 +02:00
334d68ac1f [DEV] continue integration 2024-04-15 00:58:42 +02:00
e4831e1a17 [DEV] (back) update the progress and the migration 2024-04-15 00:58:02 +02:00
e0b81d2122 [DEV] update kar-cw and new generate back-end API 2024-04-13 15:52:00 +02:00
95c3c0f968 [DEV] update new model 2024-03-19 08:37:07 +01:00
154c825499 [DEV] continue integration to support new model of data with uuid 2024-03-04 00:13:14 +01:00
5e8b244ebd [DEV] work on uuid conversion 2024-02-27 08:01:42 +01:00
48ad545da1 [DEV] migration angular17 (not tested) 2024-02-26 00:16:19 +01:00
4b002aaee4 [DEV] update dev tag version 2024-01-19 22:53:03 +01:00
459 changed files with 57586 additions and 26551 deletions

4
.gitignore vendored
View File

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

View File

@@ -1,7 +1,32 @@
#!/bin/bash #!/bin/bash
version_file="../version.txt"
# update new release dependency
cd back 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 -
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

@@ -1,25 +1,33 @@
###################################################################################### ######################################################################################
## ##
## buyilding-end install applications: ## building-end install applications:
## ##
###################################################################################### ######################################################################################
FROM archlinux:base-devel AS builder FROM archlinux:base-devel AS common
# update system # update system
RUN pacman -Syu --noconfirm && pacman-db-upgrade \ RUN pacman -Syu --noconfirm && pacman-db-upgrade \
&& pacman -S --noconfirm jdk-openjdk maven npm \ && pacman -S --noconfirm jdk-openjdk wget\
&& pacman -Scc --noconfirm && pacman -Scc --noconfirm
ENV PATH /tmp/node_modules/.bin:$PATH
WORKDIR /tmp 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
###################################################################################### ######################################################################################
## ##
## Build back: ## Build back:
## ##
###################################################################################### ######################################################################################
FROM builder AS buildBack FROM builder AS build_back
COPY back/pom.xml /tmp COPY back/pom.xml ./
COPY back/src /tmp/src/ COPY back/Formatter.xml ./
COPY back/src ./src/
RUN mvn clean compile assembly:single RUN mvn clean compile assembly:single
###################################################################################### ######################################################################################
@@ -27,26 +35,44 @@ RUN mvn clean compile assembly:single
## Build front: ## Build front:
## ##
###################################################################################### ######################################################################################
FROM builder AS buildFront FROM builder AS dependency_front
ADD front/package-lock.json \ RUN echo "@kangaroo-and-rabbit:registry=https://gitea.atria-soft.org/api/packages/kangaroo-and-rabbit/npm/" > /root/.npmrc
front/package.json \
front/karma.conf.js \ COPY front/package.json \
front/protractor.conf.js \ front/pnpm-lock.yaml \
/tmp/ ./
COPY front/src/theme ./src/theme
# install and cache app dependencies # install and cache app dependencies
RUN npm install RUN pnpm install --prod=false
ADD front/e2e \ ###############################################################
## Install sources
###############################################################
FROM dependency_front AS load_sources_front
# JUST to get the version of the application and his sha...
COPY \
front/tsconfig.json \ front/tsconfig.json \
front/tslint.json \ front/tsconfig.node.json \
front/angular.json \ front/vite.config.mts \
/tmp/ front/index.html \
ADD front/src /tmp/src ./
# generate build COPY front/public ./public
RUN ng build --output-path=dist --configuration=production --base-href=/karideo/ --deploy-url=/karideo/ 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,26 +80,24 @@ RUN ng build --output-path=dist --configuration=production --base-href=/karideo/
## ##
###################################################################################### ######################################################################################
FROM bellsoft/liberica-openjdk-alpine:latest FROM bellsoft/liberica-openjdk-alpine-musl:latest
# add wget to manage the health check...
RUN apk add --no-cache wget
#FROM archlinux:base RUN apk add --no-cache wget \
#RUN pacman -Syu --noconfirm && pacman-db-upgrade && addgroup -g 1000 user \
## install package && adduser --system -u 1000 -G user user
#RUN pacman -S --noconfirm jdk-openjdk wget
## intall npm
#RUN pacman -S --noconfirm npm
## clean all the caches Need only on the release environment
#RUN pacman -Scc --noconfirm
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/
WORKDIR /application/
EXPOSE 80 EXPOSE 80
WORKDIR /application/
RUN chown user:user -R /application
CMD ["java", "-Xms64M", "-Xmx1G", "-cp", "/application/application.jar", "org.kar.karideo.WebLauncher"] # To verify health-check: docker inspect --format "{{json .State.Health }}" YOUR_SERVICE_NAME | jq
HEALTHCHECK --start-period=10s --start-interval=2s --interval=30s --timeout=5s --retries=10 \
CMD wget --no-verbose --tries=1 --spider http://localhost:80/api/health_check || exit 1
CMD ["java", "-Xms128M", "-Xmx1G", "-cp", "/application/application.jar", "org.atriasoft.karideo.WebLauncher"]
COPY --chown=user:user --from=build_back /tmp/out/maven/*.jar /application/application.jar
COPY --chown=user:user --from=build_front /tmp/dist /application/front/
USER user

145
README.md Normal file
View File

@@ -0,0 +1,145 @@
Karideo
=======
**K**angaroo **A**nd **R**abbit (v)ideo is a simple framework to propose video streaming for personal network
Run in local:
=============
Start tools
-----------
Start the server basic interfaces: (DB(mySQL), Adminer)
```{.bash}
# start the Bdd interface (no big data > 50Mo)
docker compose -f env_dev/docker-compose.yaml up -d
```
Start the Back-end:
-------------------
backend is developed in JAVA
The first step is configuring your JAVA version (or select the JVM with the OS)
```bash
export PATH=$(ls -d --color=never /usr/lib/jvm/java-2*-openjdk)/bin:$PATH
```
Install the dependency:
```bash
mvn install
```
Run the test
```bash
mvn test
```
Install it for external use
```bash
mvn install
```
Execute the local server:
```bash
mvn exec:java@dev-mode
```
Start the Front-end:
--------------------
backend is developed in JAVA
```bash
cd front
pnpm install
pnpm dev
```
Display the result:
-------------------
[show the webpage: http://localhost:4203](http://localhost:4203)
Some other dev tools:
=====================
Format code:
------------
```bash
export PATH=$(ls -d --color=never /usr/lib/jvm/java-2*-openjdk)/bin:$PATH
mvn formatter:format
mvn test
```
Tools in production mode
========================
Changing the Log Level
----------------------
In a production environment, you can adjust the log level to help diagnose bugs more effectively.
The available log levels are:
| **Log Level Tag** | **org.kar.karideo** | **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
```
Deploy on mongodb
-----------------
connect on the mongosh interface:
```bash
docker compose exec kar_mongodb_service mongosh -u root
```
Create the base and the user
```
use karideo
db.createUser({
user: "karideo",
pwd: "your_password",
roles: [
{ role: "readWrite", db: "karideo" }
]
})
```
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/karideo:${TAG_DOCKER} .
docker push ${REGISTRY_ADDRESS}/kangaroo-and-rabbit/karideo:${TAG_DOCKER}
```

View File

@@ -53,6 +53,9 @@ Checkstyle configuration that checks the sun coding conventions.
<module name="LambdaParameterName"/> <module name="LambdaParameterName"/>
<module name="Regexp"/> <module name="Regexp"/>
<module name="RegexpSinglelineJava"/> <module name="RegexpSinglelineJava"/>
<module name="UnusedPrivateField">
<property name="ignorePattern" value="LOGGER"/>
</module>
</module> </module>
<module name="BeforeExecutionExclusionFileFilter"> <module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="module\-info\.java$"/> <property name="fileNamePattern" value="module\-info\.java$"/>

View File

@@ -1,21 +0,0 @@
FROM maven:3.6.3-openjdk-16 AS build
COPY pom.xml /tmp/
COPY src /tmp/src/
WORKDIR /tmp/
RUN mvn clean compile assembly:single
FROM bellsoft/liberica-openjdk-alpine:latest
ENV LANG=C.UTF-8
# add wget to manage the health check...
RUN apk add --no-cache wget
RUN mkdir /application/
COPY --from=build /tmp/out/maven/*.jar /application/application.jar
WORKDIR /application/
EXPOSE 18080
CMD ["java", "-Xms64M", "-Xmx1G", "-cp", "/application/application.jar", "org.kar.karideo.WebLauncher"]

View File

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

View File

@@ -1,31 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <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> <modelVersion>4.0.0</modelVersion>
<groupId>org.kar</groupId> <groupId>org.atriasoft</groupId>
<artifactId>karideo</artifactId> <artifactId>karideo</artifactId>
<version>0.2.0</version> <version>0.3.0</version>
<properties> <properties>
<maven.compiler.version>3.1</maven.compiler.version> <maven.compiler.version>3.13.0</maven.compiler.version>
<maven.compiler.source>21</maven.compiler.source> <maven.compiler.source>23</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target> <maven.compiler.target>23</maven.compiler.target>
<maven.dependency.version>3.1.1</maven.dependency.version> <maven.dependency.version>3.1.1</maven.dependency.version>
</properties> </properties>
<repositories>
<repository>
<id>gitea</id>
<url>https://gitea.atria-soft.org/api/packages/kangaroo-and-rabbit/maven</url>
</repository>
</repositories>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>kangaroo-and-rabbit</groupId> <groupId>org.atria-soft</groupId>
<artifactId>archidata</artifactId> <artifactId>archidata</artifactId>
<version>0.6.1</version> <version>0.37.2</version>
</dependency> </dependency>
<!-- Loopback of logger JDK logging API to SLF4J -->
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId> <artifactId>jul-to-slf4j</artifactId>
<version>2.0.9</version> <version>2.1.0-alpha1</version>
</dependency>
<!-- generic logger of SLF4J to console (in color) -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.19</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.12</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.20.0</version>
</dependency> </dependency>
<!-- <!--
************************************************************ ************************************************************
@@ -35,15 +51,25 @@
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId> <artifactId>junit-jupiter-api</artifactId>
<version>5.10.1</version> <version>6.0.0</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId> <artifactId>junit-jupiter-engine</artifactId>
<version>5.10.1</version> <version>6.0.0</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<version>2.29.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.6.0</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
<sourceDirectory>src</sourceDirectory> <sourceDirectory>src</sourceDirectory>
@@ -72,16 +98,45 @@
<plugin> <plugin>
<groupId>org.codehaus.mojo</groupId> <groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId> <artifactId>exec-maven-plugin</artifactId>
<version>1.4.0</version> <version>3.2.0</version>
<executions>
<execution>
<id>prod-mode</id>
<goals>
<goal>java</goal>
</goals>
<configuration> <configuration>
<mainClass>org.kar.karideo.WebLauncher</mainClass> <mainClass>org.atriasoft.karideo.WebLauncher</mainClass>
</configuration>
</execution>
<execution>
<id>dev-mode</id>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>org.atriasoft.karideo.WebLauncherLocal</mainClass>
</configuration>
</execution>
<execution>
<id>generate-api</id>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>org.atriasoft.karideo.GenerateApi</mainClass>
</configuration>
</execution>
</executions>
<configuration>
<mainClass/>
</configuration> </configuration>
</plugin> </plugin>
<!-- Create the source bundle --> <!-- Create the source bundle -->
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId> <artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version> <version>4.0.0-beta-1</version>
<executions> <executions>
<execution> <execution>
<id>attach-sources</id> <id>attach-sources</id>
@@ -95,10 +150,12 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version> <version>3.2.5</version>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId> <artifactId>maven-assembly-plugin</artifactId>
<version>3.7.1</version>
<configuration> <configuration>
<archive> <archive>
<manifest> <manifest>
@@ -110,94 +167,21 @@
</descriptorRefs> </descriptorRefs>
</configuration> </configuration>
</plugin> </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 --> <!-- Java-doc generation for stand-alone site -->
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version> <version>3.3.0</version>
<configuration> <configuration>
<show>private</show> <show>private</show>
<nohelp>true</nohelp> <nohelp>true</nohelp>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>exec-application</id>
<phase>package</phase>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>org.kar.karideo.WebLauncher</mainClass>
</configuration>
</plugin>
<!-- Check the style of the code --> <!-- 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> <plugin>
<groupId>net.revelc.code.formatter</groupId> <groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId> <artifactId>formatter-maven-plugin</artifactId>
<version>2.12.2</version> <version>2.24.1</version>
<configuration> <configuration>
<encoding>UTF-8</encoding> <encoding>UTF-8</encoding>
<lineEnding>LF</lineEnding> <lineEnding>LF</lineEnding>
@@ -221,7 +205,15 @@
</execution> </execution>
</executions> </executions>
</plugin> </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> </plugins>
</build> </build>
<!-- Generate Java-docs As Part Of Project Reports --> <!-- Generate Java-docs As Part Of Project Reports -->
@@ -230,7 +222,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version> <version>3.3.0</version>
<configuration> <configuration>
<show>public</show> <show>public</show>
</configuration> </configuration>

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

@@ -0,0 +1,245 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.kar</groupId>
<artifactId>karideo</artifactId>
<version>0.3.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.11.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.9</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.16.1</version>
</dependency>
<!--
************************************************************
** TEST dependency **
************************************************************
-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.10.1</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>
</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.karideo.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.karideo.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>
<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>
-->
</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

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

View File

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

View File

@@ -1,50 +1,53 @@
package org.kar.karideo; package org.atriasoft.karideo;
import java.net.URI; import java.net.URI;
import java.util.TimeZone;
import java.util.logging.LogManager;
import org.atriasoft.archidata.UpdateJwtPublicKey;
import org.atriasoft.archidata.api.DataResource;
import org.atriasoft.archidata.catcher.GenericCatcher;
import org.atriasoft.archidata.cron.CronScheduler;
import org.atriasoft.archidata.db.DbConfig;
import org.atriasoft.archidata.filter.CORSFilter;
import org.atriasoft.archidata.filter.OptionFilter;
import org.atriasoft.archidata.migration.MigrationEngine;
import org.atriasoft.archidata.tools.ConfigBaseVariable;
import org.atriasoft.archidata.tools.ContextGenericTools;
import org.atriasoft.karideo.api.Front;
import org.atriasoft.karideo.api.HealthCheck;
import org.atriasoft.karideo.api.MediaResource;
import org.atriasoft.karideo.api.SeasonResource;
import org.atriasoft.karideo.api.SeriesResource;
import org.atriasoft.karideo.api.TypeResource;
import org.atriasoft.karideo.api.UserMediaAdvancementResource;
import org.atriasoft.karideo.api.UserResource;
import org.atriasoft.karideo.filter.KarideoAuthenticationFilter;
import org.atriasoft.karideo.job.BackupJob;
import org.atriasoft.karideo.migration.Initialization;
import org.glassfish.grizzly.http.server.HttpServer; import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory; import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.jackson.JacksonFeature; import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.media.multipart.MultiPartFeature; import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.server.ResourceConfig;
import org.kar.archidata.GlobalConfiguration; import org.glassfish.jersey.server.validation.ValidationFeature;
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.db.DBConfig;
import org.kar.archidata.filter.CORSFilter;
import org.kar.archidata.filter.OptionFilter;
import org.kar.archidata.migration.MigrationEngine;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.kar.karideo.api.Front;
import org.kar.karideo.api.HealthCheck;
import org.kar.karideo.api.SeasonResource;
import org.kar.karideo.api.SeriesResource;
import org.kar.karideo.api.TypeResource;
import org.kar.karideo.api.UserMediaAdvancementResource;
import org.kar.karideo.api.UserResource;
import org.kar.karideo.api.VideoResource;
import org.kar.karideo.filter.KarideoAuthenticationFilter;
import org.kar.karideo.migration.Initialization;
import org.kar.karideo.migration.Migration20230810;
import org.kar.karideo.migration.Migration20231015;
import org.kar.karideo.migration.Migration20231126;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;
import jakarta.ws.rs.core.UriBuilder; import jakarta.ws.rs.core.UriBuilder;
public class WebLauncher { public class WebLauncher {
final static Logger LOGGER = LoggerFactory.getLogger(WebLauncher.class); final static Logger LOGGER = LoggerFactory.getLogger(WebLauncher.class);
public static DBConfig dbConfig;
protected UpdateJwtPublicKey keyUpdater = null; protected UpdateJwtPublicKey keyUpdater = null;
protected HttpServer server = null; protected HttpServer server = null;
protected CronScheduler scheduler = null;
public WebLauncher() { public WebLauncher() {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
ConfigBaseVariable.bdDatabase = "karideo"; ConfigBaseVariable.bdDatabase = "karideo";
this.scheduler = new CronScheduler();
this.scheduler.setGracePeriodMinutes(1);
} }
private static URI getBaseURI() { private static URI getBaseURI() {
@@ -57,16 +60,17 @@ public class WebLauncher {
WebLauncher.LOGGER.info("Add initialization"); WebLauncher.LOGGER.info("Add initialization");
migrationEngine.setInit(new Initialization()); migrationEngine.setInit(new Initialization());
WebLauncher.LOGGER.info("Add migration since last version"); WebLauncher.LOGGER.info("Add migration since last version");
migrationEngine.add(new Migration20230810()); // migrationEngine.add(new Migration20250928());
migrationEngine.add(new Migration20231015());
migrationEngine.add(new Migration20231126());
//migrationEngine.add(new Migration20231126());
WebLauncher.LOGGER.info("Migrate the DB [START]"); WebLauncher.LOGGER.info("Migrate the DB [START]");
migrationEngine.migrateWaitAdmin(GlobalConfiguration.dbConfig); migrationEngine.migrateWaitAdmin(new DbConfig());
WebLauncher.LOGGER.info("Migrate the DB [STOP]"); WebLauncher.LOGGER.info("Migrate the DB [STOP]");
} }
public static void main(final String[] args) throws Exception { 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"); WebLauncher.LOGGER.info("[START] application wake UP");
final WebLauncher launcher = new WebLauncher(); final WebLauncher launcher = new WebLauncher();
launcher.migrateDB(); launcher.migrateDB();
@@ -78,7 +82,7 @@ public class WebLauncher {
WebLauncher.LOGGER.info("STOP the REST server"); WebLauncher.LOGGER.info("STOP the REST server");
} }
public void process() throws InterruptedException { public void process() throws Exception {
// =================================================================== // ===================================================================
// Configure resources // Configure resources
@@ -94,29 +98,32 @@ public class WebLauncher {
// global authentication system // global authentication system
rc.register(KarideoAuthenticationFilter.class); rc.register(KarideoAuthenticationFilter.class);
// register exception catcher // register exception catcher
rc.register(InputExceptionCatcher.class); GenericCatcher.addAll(rc);
rc.register(SystemExceptionCatcher.class);
rc.register(FailExceptionCatcher.class);
rc.register(ExceptionCatcher.class);
// add default resource: // add default resource:
rc.register(UserResource.class); rc.register(UserResource.class);
rc.register(SeriesResource.class); rc.register(SeriesResource.class);
rc.register(DataResource.class); rc.register(DataResource.class);
rc.register(SeasonResource.class); rc.register(SeasonResource.class);
rc.register(TypeResource.class); rc.register(TypeResource.class);
rc.register(VideoResource.class); rc.register(MediaResource.class);
rc.register(UserMediaAdvancementResource.class); rc.register(UserMediaAdvancementResource.class);
rc.register(HealthCheck.class); rc.register(HealthCheck.class);
rc.register(Front.class); rc.register(Front.class);
ContextGenericTools.addJsr310(rc);
// add jackson to be discover when we are ins stand-alone server // add jackson to be discover when we are ins stand-alone server
rc.register(JacksonFeature.class); rc.register(JacksonFeature.class);
// enable jersey specific validations (@Valid)
rc.register(ValidationFeature.class);
// enable this to show low level request // enable this to show low level request
// rc.property(LoggingFeature.LOGGING_FEATURE_LOGGER_LEVEL_SERVER, Level.WARNING.getName()); // rc.property(LoggingFeature.LOGGING_FEATURE_LOGGER_LEVEL_SERVER, Level.WARNING.getName());
this.server = GrizzlyHttpServerFactory.createHttpServer(getBaseURI(), rc); this.server = GrizzlyHttpServerFactory.createHttpServer(getBaseURI(), rc);
final HttpServer serverLink = this.server; final HttpServer serverLink = this.server;
// for (final NetworkListener listener : serverLink.getListeners()) {
// listener.getKeepAlive().setIdleTimeoutInSeconds(30); // Set idle timeout
// listener.getKeepAlive().setMaxRequestsCount(80); // Set request timeout
// }
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -131,6 +138,14 @@ public class WebLauncher {
this.keyUpdater = new UpdateJwtPublicKey(); this.keyUpdater = new UpdateJwtPublicKey();
this.keyUpdater.start(); this.keyUpdater.start();
// ===================================================================
// start generic scheduler ...
// ===================================================================
this.scheduler.addTask("backup", "0 0 * * *", new BackupJob());
this.scheduler.start();
// execute a backup before start...
new BackupJob().run();
// =================================================================== // ===================================================================
// run JERSEY // run JERSEY
// =================================================================== // ===================================================================
@@ -148,6 +163,10 @@ public class WebLauncher {
this.server.shutdownNow(); this.server.shutdownNow();
this.server = null; this.server = null;
} }
if (this.scheduler != null) {
this.scheduler.stop();
this.scheduler = null;
}
} }
public void stopOther() { public void stopOther() {

View File

@@ -1,16 +1,26 @@
package org.kar.karideo; package org.atriasoft.karideo;
import org.kar.archidata.tools.ConfigBaseVariable; import java.util.logging.LogManager;
import org.atriasoft.archidata.tools.ConfigBaseVariable;
import org.atriasoft.karideo.migration.Initialization;
import org.atriasoft.karideo.util.ConfigVariable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;
public class WebLauncherLocal extends WebLauncher { public class WebLauncherLocal extends WebLauncher {
private final static Logger LOGGER = LoggerFactory.getLogger(WebLauncherLocal.class); private static final Logger LOGGER = LoggerFactory.getLogger(WebLauncherLocal.class);
private WebLauncherLocal() {} private WebLauncherLocal() {}
public static void main(final String[] args) throws InterruptedException { 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
Initialization.generateObjects();
final WebLauncherLocal launcher = new WebLauncherLocal(); final WebLauncherLocal launcher = new WebLauncherLocal();
launcher.process(); launcher.process();
LOGGER.info("end-configure the server & wait finish process:"); LOGGER.info("end-configure the server & wait finish process:");
@@ -19,11 +29,15 @@ public class WebLauncherLocal extends WebLauncher {
} }
@Override @Override
public void process() throws InterruptedException { public void process() throws Exception {
if (true) { if (true) {
// for local test: // for local test:
ConfigBaseVariable.apiAdress = "http://0.0.0.0:18080/karideo/api/"; ConfigBaseVariable.apiAdress = "http://0.0.0.0:18080/karideo/api/";
ConfigBaseVariable.dbPort = "3906"; ConfigBaseVariable.testMode = "true";
ConfigBaseVariable.dbType = "mongo";
}
if (ConfigVariable.isInitWithBackup()) {
Initialization.initializeWithBackup();
} }
try { try {
super.migrateDB(); super.migrateDB();

View File

@@ -0,0 +1,502 @@
package org.atriasoft.karideo.api;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.UUID;
import javax.imageio.ImageIO;
import org.atriasoft.archidata.annotation.apiGenerator.ApiInputOptional;
import org.atriasoft.archidata.annotation.security.PermitTokenInURI;
import org.atriasoft.archidata.api.MediaStreamer;
import org.atriasoft.archidata.dataAccess.DataAccess;
import org.atriasoft.archidata.dataAccess.QueryCondition;
import org.atriasoft.archidata.dataAccess.options.Condition;
import org.atriasoft.archidata.exception.FailException;
import org.atriasoft.archidata.filter.GenericContext;
import org.atriasoft.archidata.model.Data;
import org.atriasoft.archidata.tools.ConfigBaseVariable;
import org.bson.types.ObjectId;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
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.GET;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.InternalServerErrorException;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.CacheControl;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.SecurityContext;
import jakarta.ws.rs.core.StreamingOutput;
// https://stackoverflow.com/questions/35367113/jersey-webservice-scalable-approach-to-download-file-and-reply-to-client
// https://gist.github.com/aitoroses/4f7a2b197b732a6a691d
@Path("/data")
@Produces(MediaType.APPLICATION_JSON)
public class DataResource {
private static final Logger LOGGER = LoggerFactory.getLogger(DataResource.class);
private final static int CHUNK_SIZE = 1024 * 1024; // 1MB chunks
private final static int CHUNK_SIZE_IN = 50 * 1024 * 1024; // 1MB chunks
/** Upload some datas */
private static long tmpFolderId = 1;
private static void createFolder(final String path) throws IOException {
if (!Files.exists(Paths.get(path))) {
Files.createDirectories(Paths.get(path));
}
}
public static long getTmpDataId() {
return tmpFolderId++;
}
public static String getTmpFileInData(final long tmpFolderId) {
final String filePath = ConfigBaseVariable.getTmpDataFolder() + File.separator + tmpFolderId;
try {
createFolder(ConfigBaseVariable.getTmpDataFolder() + File.separator);
} catch (final IOException e) {
e.printStackTrace();
}
return filePath;
}
public static String getFileDataOld(final UUID uuid) {
final String stringUUID = uuid.toString();
final String part1 = stringUUID.substring(0, 2);
final String part2 = stringUUID.substring(2, 4);
final String part3 = stringUUID.substring(4);
final String finalPath = part1 + File.separator + part2;
String filePath = ConfigBaseVariable.getMediaDataFolder() + "_uuid" + File.separator + finalPath + File.separator;
try {
createFolder(filePath);
} catch (final IOException e) {
e.printStackTrace();
}
filePath += part3;
return filePath;
}
public static String getFileData(final ObjectId oid) {
final String stringOid = oid.toHexString();
String dir1 = stringOid.substring(0, 2);
String dir2 = stringOid.substring(2, 4);
String dir3 = stringOid.substring(4, 6);
try {
final MessageDigest digest = MessageDigest.getInstance("SHA-256");
final byte[] hashBytes = digest.digest(oid.toByteArray());
dir1 = String.format("%02x", hashBytes[0]);
dir2 = String.format("%02x", hashBytes[1]);
dir3 = String.format("%02x", hashBytes[2]);
} catch (final NoSuchAlgorithmException ex) {
LOGGER.error("Fail to generate the hash of the objectId ==> ise direct value ... {}", ex.getMessage());
}
final String finalPath = dir1 + File.separator + dir2 + File.separator + dir3;
String filePath = ConfigBaseVariable.getMediaDataFolder() + "_oid" + File.separator + finalPath + File.separator;
try {
createFolder(filePath);
} catch (final IOException e) {
e.printStackTrace();
}
filePath += stringOid;
return filePath;
}
public static String getFileMetaData(final ObjectId oid) {
return getFileData(oid) + ".json";
}
public Data getWithSha512(final String sha512) {
LOGGER.info("find sha512 = {}", sha512);
try {
return DataAccess.getWhere(Data.class, new Condition(new QueryCondition("sha512", "=", sha512)));
} catch (final Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public Data getWithId(final long id) {
LOGGER.info("find id = {}", id);
try {
return DataAccess.get(Data.class, id);
} catch (final Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
protected String getMimeType(final String extension) throws IOException {
return switch (extension.toLowerCase()) {
case "jpg", "jpeg" -> "image/jpeg";
case "png" -> "image/png";
case "webp" -> "image/webp";
case "mka" -> "audio/x-matroska";
case "mkv" -> "video/x-matroska";
case "webm" -> "video/webm";
default -> throw new IOException("Can not find the mime type of data input: '" + extension + "'");
};
}
public Data createNewData(final long tmpUID, final String originalFileName, final String sha512) throws IOException {
// determine mime type:
Data injectedData = new Data();
String mimeType = "";
final String extension = originalFileName.substring(originalFileName.lastIndexOf('.') + 1);
mimeType = getMimeType(extension);
injectedData.mimeType = mimeType;
injectedData.sha512 = sha512;
final String tmpPath = getTmpFileInData(tmpUID);
injectedData.size = Files.size(Paths.get(tmpPath));
try {
injectedData = DataAccess.insert(injectedData);
} catch (final Exception e) {
e.printStackTrace();
return null;
}
final String mediaPath = getFileData(injectedData.oid);
LOGGER.info("src = {}", tmpPath);
LOGGER.info("dst = {}", mediaPath);
Files.move(Paths.get(tmpPath), Paths.get(mediaPath), StandardCopyOption.ATOMIC_MOVE);
LOGGER.info("Move done");
return injectedData;
}
public static void modeFileOldModelToNewModel(final UUID uuid, final ObjectId oid) throws IOException {
String mediaCurentPath = getFileDataOld(uuid);
String mediaDestPath = getFileData(oid);
LOGGER.info("src = {}", mediaCurentPath);
LOGGER.info("dst = {}", mediaDestPath);
if (Files.exists(Paths.get(mediaCurentPath))) {
LOGGER.info("move: {} ==> {}", mediaCurentPath, mediaDestPath);
Files.move(Paths.get(mediaCurentPath), Paths.get(mediaDestPath), StandardCopyOption.ATOMIC_MOVE);
}
// Move old meta-data...
mediaCurentPath = mediaCurentPath.substring(mediaCurentPath.length() - 4) + "meta.json";
mediaDestPath = mediaCurentPath.substring(mediaDestPath.length() - 4) + "meta.json";
if (Files.exists(Paths.get(mediaCurentPath))) {
LOGGER.info("moveM: {} ==> {}", mediaCurentPath, mediaDestPath);
Files.move(Paths.get(mediaCurentPath), Paths.get(mediaDestPath), StandardCopyOption.ATOMIC_MOVE);
}
LOGGER.info("Move done");
}
public static String saveTemporaryFile(final InputStream uploadedInputStream, final long idData) throws FailException {
return saveFile(uploadedInputStream, DataResource.getTmpFileInData(idData));
}
public static void removeTemporaryFile(final long idData) {
final String filepath = DataResource.getTmpFileInData(idData);
if (Files.exists(Paths.get(filepath))) {
try {
Files.delete(Paths.get(filepath));
} catch (final IOException e) {
LOGGER.info("can not delete temporary file : {}", Paths.get(filepath));
e.printStackTrace();
}
}
}
// save uploaded file to a defined location on the server
static String saveFile(final InputStream uploadedInputStream, final String serverLocation) throws FailException {
String out = "";
MessageDigest md = null;
try (OutputStream outpuStream = new FileOutputStream(new File(serverLocation))) {
md = MessageDigest.getInstance("SHA-512");
outpuStream.flush();
} catch (final IOException ex) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Can not write in temporary file", ex);
} catch (final NoSuchAlgorithmException ex) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Can not find sha512 algorithms", ex);
}
if (md != null) {
try (OutputStream outpuStream = new FileOutputStream(new File(serverLocation))) {
int read = 0;
final byte[] bytes = new byte[CHUNK_SIZE_IN];
while ((read = uploadedInputStream.read(bytes)) != -1) {
// logger.info("write {}", read);
md.update(bytes, 0, read);
outpuStream.write(bytes, 0, read);
}
LOGGER.info("Flush input stream ... {}", serverLocation);
outpuStream.flush();
// create the end of sha512
final byte[] sha512Digest = md.digest();
// convert in hexadecimal
out = bytesToHex(sha512Digest);
uploadedInputStream.close();
} catch (final IOException ex) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Can not write in temporary file", ex);
}
}
return out;
}
public static String bytesToHex(final byte[] bytes) {
final StringBuilder sb = new StringBuilder();
for (final byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
public Data getSmall(final ObjectId oid) {
try {
return DataAccess.get(Data.class, oid);
} catch (final Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
@POST
@Path("/upload/")
@Consumes({ MediaType.MULTIPART_FORM_DATA })
@RolesAllowed("ADMIN")
@Operation(description = "Insert a new data in the data environment", tags = "SYSTEM")
public void uploadFile(@Context final SecurityContext sc, @FormDataParam("file") final InputStream fileInputStream, @FormDataParam("file") final FormDataContentDisposition fileMetaData)
throws FailException {
final GenericContext gc = (GenericContext) sc.getUserPrincipal();
LOGGER.info("===================================================");
LOGGER.info("== DATA uploadFile {}", (gc == null ? "null" : gc.userByToken));
LOGGER.info("===================================================");
// public NodeSmall uploadFile(final FormDataMultiPart form) {
LOGGER.info("Upload file: ");
final String filePath = ConfigBaseVariable.getTmpDataFolder() + File.separator + tmpFolderId++;
try {
createFolder(ConfigBaseVariable.getTmpDataFolder() + File.separator);
} catch (final IOException ex) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Impossible to create the folder in the server", ex);
}
saveFile(fileInputStream, filePath);
}
@GET
@Path("{oid}")
@PermitTokenInURI
@RolesAllowed("USER")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@Operation(description = "Get back some data from the data environment", tags = "SYSTEM")
public Response retrieveDataId(@Context final SecurityContext sc, @QueryParam(HttpHeaders.AUTHORIZATION) final String token, @HeaderParam("Range") final String range,
@PathParam("oid") final ObjectId oid) throws FailException {
final GenericContext gc = (GenericContext) sc.getUserPrincipal();
// logger.info("===================================================");
LOGGER.info("== DATA retrieveDataId ? oid={} user={}", oid, (gc == null ? "null" : gc.userByToken));
// logger.info("===================================================");
final Data value = getSmall(oid);
if (value == null) {
return Response.status(404).entity("media NOT FOUND: " + oid).type("text/plain").build();
}
try {
return buildStream(getFileData(oid), range, value.mimeType == null ? "application/octet-stream" : value.mimeType);
} catch (final Exception ex) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Fail to build output stream", ex);
}
}
@GET
@Path("thumbnail/{oid}")
@RolesAllowed("USER")
@PermitTokenInURI
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@Operation(description = "Get a thumbnail of from the data environment (if resize is possible)", tags = "SYSTEM")
// @CacheMaxAge(time = 10, unit = TimeUnit.DAYS)
public Response retrieveDataThumbnailId(@Context final SecurityContext sc, @QueryParam(HttpHeaders.AUTHORIZATION) final String token, @HeaderParam("Range") final String range,
@PathParam("oid") final ObjectId oid) throws FailException {
final GenericContext gc = (GenericContext) sc.getUserPrincipal();
LOGGER.info("===================================================");
LOGGER.info("== DATA retrieveDataThumbnailId ? {}", (gc == null ? "null" : gc.userByToken));
LOGGER.info("===================================================");
final Data value = getSmall(oid);
if (value == null) {
return Response.status(404).entity("media NOT FOUND: " + oid).type("text/plain").build();
}
final String filePathName = getFileData(oid);
final File inputFile = new File(filePathName);
if (!inputFile.exists()) {
return Response.status(404).entity("{\"error\":\"media Does not exist: " + oid + "\"}").type("application/json").build();
}
if (value.mimeType.contentEquals("image/jpeg") || value.mimeType.contentEquals("image/png")
// || value.mimeType.contentEquals("image/webp")
) {
// reads input image
BufferedImage inputImage;
try {
inputImage = ImageIO.read(inputFile);
} catch (final IOException ex) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Fail to READ the image", ex);
}
LOGGER.info("input size image: {}x{} type={}", inputImage.getWidth(), inputImage.getHeight(), inputImage.getType());
final int scaledWidth = ConfigBaseVariable.getThumbnailWidth();
final int scaledHeight = (int) ((float) inputImage.getHeight() / (float) inputImage.getWidth() * scaledWidth);
// creates output image
final BufferedImage outputImage = new BufferedImage(scaledWidth, scaledHeight, inputImage.getType());
// scales the input image to the output image
final Graphics2D g2d = outputImage.createGraphics();
LOGGER.info("output size image: {}x{}", scaledWidth, scaledHeight);
g2d.drawImage(inputImage, 0, 0, scaledWidth, scaledHeight, null);
g2d.dispose();
// create the output stream:
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
ImageIO.write(outputImage, ConfigBaseVariable.getThumbnailFormat(), baos);
} catch (final IOException e) {
e.printStackTrace();
return Response.status(500).entity("Internal Error: resize fail: " + e.getMessage()).type("text/plain").build();
}
final byte[] imageData = baos.toByteArray();
LOGGER.info("output length {}", imageData.length);
if (imageData.length == 0) {
LOGGER.error("Fail to convert image... Availlable format:");
for (final String data : ImageIO.getWriterFormatNames()) {
LOGGER.error(" - {}", data);
}
}
final Response.ResponseBuilder out = Response.ok(imageData).header(HttpHeaders.CONTENT_LENGTH, imageData.length);
try {
out.type(getMimeType(ConfigBaseVariable.getThumbnailFormat()));
} catch (final IOException ex) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Fail to convert mime type of " + ConfigBaseVariable.getThumbnailFormat(), ex);
}
// TODO: move this in a decorator !!!
final CacheControl cc = new CacheControl();
cc.setMaxAge(3600);
cc.setNoCache(false);
out.cacheControl(cc);
return out.build();
}
try {
return buildStream(filePathName, range, value.mimeType);
} catch (final Exception ex) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Fail to build output stream", ex);
}
}
@GET
@Path("{oid}/{name}")
@PermitTokenInURI
@RolesAllowed("USER")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@Operation(description = "Get back some data from the data environment (with a beautiful name (permit download with basic name)", tags = "SYSTEM")
public Response retrieveDataFull(@Context final SecurityContext sc, @QueryParam(HttpHeaders.AUTHORIZATION) final String token, @ApiInputOptional @HeaderParam("Range") final String range,
@PathParam("oid") final ObjectId oid, @PathParam("name") final String name) throws Exception {
final GenericContext gc = (GenericContext) sc.getUserPrincipal();
// logger.info("===================================================");
LOGGER.info("== DATA retrieveDataFull ? id={} user={}", oid, (gc == null ? "null" : gc.userByToken));
// logger.info("===================================================");
final Data value = getSmall(oid);
if (value == null) {
return Response.status(404).entity("media NOT FOUND: " + oid).type("text/plain").build();
}
return buildStream(getFileData(oid), range, value.mimeType == null ? "application/octet-stream" : value.mimeType);
}
/** Adapted from http://stackoverflow.com/questions/12768812/video-streaming-to-ipad-does-not-work-with-tapestry5/12829541#12829541
*
* @param range range header
* @return Streaming output
* @throws FileNotFoundException
* @throws Exception IOException if an error occurs in streaming. */
private Response buildStream(final String filename, final String range, final String mimeType) throws FailException {
final File file = new File(filename);
// logger.info("request range : {}", range);
// range not requested : Firefox does not send range headers
if (range == null) {
final StreamingOutput output = new StreamingOutput() {
@Override
public void write(final OutputStream out) {
try (FileInputStream in = new FileInputStream(file)) {
final byte[] buf = new byte[1024 * 1024];
int len;
while ((len = in.read(buf)) != -1) {
try {
out.write(buf, 0, len);
out.flush();
// logger.info("---- wrote {} bytes file ----", len);
} catch (final IOException ex) {
LOGGER.info("remote close connection");
break;
}
}
} catch (final IOException ex) {
throw new InternalServerErrorException(ex);
}
}
};
final Response.ResponseBuilder out = Response.ok(output).header(HttpHeaders.CONTENT_LENGTH, file.length());
if (mimeType != null) {
out.type(mimeType);
}
return out.build();
}
final String[] ranges = range.split("=")[1].split("-");
final long from = Long.parseLong(ranges[0]);
// logger.info("request range : {}", ranges.length);
// Chunk media if the range upper bound is unspecified. Chrome, Opera sends "bytes=0-"
long to = CHUNK_SIZE + from;
if (ranges.length == 1) {
to = file.length() - 1;
} else if (to >= file.length()) {
to = file.length() - 1;
}
final String responseRange = String.format("bytes %d-%d/%d", from, to, file.length());
// LOGGER.info("responseRange: {}", responseRange);
try {
final RandomAccessFile raf = new RandomAccessFile(file, "r");
raf.seek(from);
final long len = to - from + 1;
final MediaStreamer streamer = new MediaStreamer(len, raf);
final Response.ResponseBuilder out = Response.ok(streamer).status(Response.Status.PARTIAL_CONTENT).header("Accept-Ranges", "bytes").header("Content-Range", responseRange)
.header(HttpHeaders.CONTENT_LENGTH, streamer.getLenth()).header(HttpHeaders.LAST_MODIFIED, new Date(file.lastModified()));
if (mimeType != null) {
out.type(mimeType);
}
return out.build();
} catch (final FileNotFoundException ex) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Fail to find the required file.", ex);
} catch (final IOException ex) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Fail to access to the required file.", ex);
}
}
public void undelete(final Long id) throws Exception {
DataAccess.unsetDelete(Data.class, id);
}
}

View File

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

View File

@@ -1,11 +1,12 @@
package org.kar.karideo.api; package org.atriasoft.karideo.api;
import org.kar.archidata.exception.FailException; import org.atriasoft.archidata.exception.FailException;
import org.kar.archidata.tools.ConfigBaseVariable; import org.atriasoft.archidata.tools.ConfigBaseVariable;
import org.kar.archidata.tools.JWTWrapper; import org.atriasoft.archidata.tools.JWTWrapper;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.PermitAll;
import jakarta.ws.rs.GET; import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path; import jakarta.ws.rs.Path;
@@ -18,11 +19,13 @@ import jakarta.ws.rs.core.Response;
public class HealthCheck { public class HealthCheck {
static final Logger LOGGER = LoggerFactory.getLogger(HealthCheck.class); static final Logger LOGGER = LoggerFactory.getLogger(HealthCheck.class);
public record HealthResult( public record HealthResult(String value) {
String value) {};
};
@GET @GET
@PermitAll @PermitAll
@Operation(description = "Get the server state (health)", tags = "SYSTEM")
public HealthResult getHealth() throws FailException { public HealthResult getHealth() throws FailException {
if (JWTWrapper.getPublicKeyJson() == null && !ConfigBaseVariable.getTestMode()) { if (JWTWrapper.getPublicKeyJson() == null && !ConfigBaseVariable.getTestMode()) {
throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Missing Jwt public token"); throw new FailException(Response.Status.INTERNAL_SERVER_ERROR, "Missing Jwt public token");

View File

@@ -0,0 +1,241 @@
package org.atriasoft.karideo.api;
import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.List;
import org.atriasoft.archidata.annotation.apiGenerator.ApiTypeScriptProgress;
import org.atriasoft.archidata.annotation.checker.GroupUpdate;
import org.atriasoft.archidata.annotation.checker.ValidGroup;
import org.atriasoft.archidata.api.DataResource;
import org.atriasoft.archidata.dataAccess.DBAccess;
import org.atriasoft.archidata.dataAccess.DataAccess;
import org.atriasoft.archidata.exception.FailException;
import org.atriasoft.archidata.exception.InputException;
import org.atriasoft.archidata.model.Data;
import org.atriasoft.archidata.tools.DataTools;
import org.atriasoft.karideo.model.Media;
import org.atriasoft.karideo.model.Season;
import org.atriasoft.karideo.model.Series;
import org.atriasoft.karideo.model.Type;
import org.bson.types.ObjectId;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.security.RolesAllowed;
import jakarta.validation.Valid;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/media")
@Produces(MediaType.APPLICATION_JSON)
public class MediaResource {
static final Logger LOGGER = LoggerFactory.getLogger(MediaResource.class);
@GET
@RolesAllowed("USER")
@Operation(description = "Get all Media", tags = "GLOBAL")
public List<Media> gets() throws Exception {
return DataAccess.gets(Media.class);
}
@GET
@Path("{id}")
@RolesAllowed("USER")
@Operation(description = "Get a specific Media with his ID", tags = "GLOBAL")
public Media get(@PathParam("id") final Long id) throws Exception {
return DataAccess.get(Media.class, id);
}
@PUT
@Path("{oid}")
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(description = "Modify a specific Media", tags = "GLOBAL")
public Media put(@PathParam("oid") final ObjectId oid, @Valid @ValidGroup(GroupUpdate.class) final Media media) throws Exception {
LOGGER.info("update video {} ==> '{}'", oid, media);
media.oid = oid;
DataAccess.update(media, oid);
return DataAccess.get(Media.class, oid);
}
private String multipartCorrection(final String data) {
if (data == null) {
return null;
}
if (data.isEmpty()) {
return null;
}
if (data.contentEquals("null")) {
return null;
}
if (data.contentEquals("undefined")) {
return null;
}
return data;
}
@POST
@RolesAllowed("ADMIN")
@Consumes({ MediaType.MULTIPART_FORM_DATA })
@Operation(description = "Create a new Media", tags = "GLOBAL")
@ApiTypeScriptProgress
public Media uploadMedia( //
// @AsyncType(Long.class) @FormDataParam("universeId") String universeId, //
// @AsyncType(Long.class) @FormDataParam("typeId") String typeId, //
// @AsyncType(Long.class) @FormDataParam("seriesId") String seriesId, //
// @AsyncType(Long.class) @FormDataParam("season") String season, // value of the season ==> local add if needed
// @AsyncType(Long.class) @FormDataParam("episode") String episode, // value of the season ==> local add if needed
@FormDataParam("universeId") String universeId, //
@FormDataParam("typeId") String typeId, //
@FormDataParam("seriesId") String seriesId, //
@FormDataParam("season") String season, // value of the season ==> local add if needed
@FormDataParam("episode") String episode, // value of the season ==> local add if needed
@FormDataParam("title") String title, //
@FormDataParam("file") final InputStream fileInputStream, //
@FormDataParam("file") final FormDataContentDisposition fileMetaData //
) throws FailException {
try (DBAccess db = DBAccess.createInterface()) {
// correct input string stream :
final String fileName = multipartCorrection(fileMetaData.getFileName());
universeId = multipartCorrection(universeId);
final ObjectId universeOid = universeId != null ? new ObjectId(universeId) : null;
typeId = multipartCorrection(typeId);
final ObjectId typeOid = typeId != null ? new ObjectId(typeId) : null;
seriesId = multipartCorrection(seriesId);
final ObjectId seriesOid = seriesId != null ? new ObjectId(seriesId) : null;
season = multipartCorrection(season);
final Long seasonLong = season != null ? Long.parseLong(season) : null;
episode = multipartCorrection(episode);
title = multipartCorrection(title);
// todo: check if all remotes Id exist ...
// public NodeSmall uploadFile(final FormDataMultiPart form) {
LOGGER.info("Upload media file: {}", fileMetaData);
LOGGER.info(" - fileName: {}", fileName);
LOGGER.info(" - universe: {}", universeOid);
LOGGER.info(" - series: {}", seriesOid);
LOGGER.info(" - season: {}", seasonLong);
LOGGER.info(" - episode: {}", episode);
LOGGER.info(" - title: {}", title);
LOGGER.info(" - type: {}", typeId);
LOGGER.info(" - fileInputStream: {}", fileInputStream);
LOGGER.info(" - fileMetaData: {}", fileMetaData);
System.out.flush();
if (typeId == null) {
throw new InputException("typeId", "TypiId is not specified");
}
final long tmpUID = DataResource.getTmpDataId();
final String sha512 = DataResource.saveTemporaryFile(fileInputStream, tmpUID);
Data data = DataTools.getWithSha512(db, sha512);
if (data == null) {
LOGGER.info("Need to add the data in the BDD ... ");
System.out.flush();
try {
data = DataTools.createNewData(db, tmpUID, fileName, sha512);
} catch (final IOException ex) {
DataResource.removeTemporaryFile(tmpUID);
ex.printStackTrace();
throw new FailException("can not create input media (the data model has an internal error");
}
} else if (data != null && data.deleted != null && data.deleted) {
LOGGER.info("Data already exist but deleted");
System.out.flush();
DataTools.undelete(db, data.oid);
data.deleted = false;
} else {
LOGGER.info("Data already exist ... all good");
System.out.flush();
}
// Fist step: retieve all the Id of each parents:...
LOGGER.info("Find typeNode");
// check if id of type exist:
final Type typeNode = TypeResource.getId(typeOid);
if (typeNode == null) {
DataResource.removeTemporaryFile(tmpUID);
throw new InputException("typeId", "TypeId does not exist ...");
}
// check if id of type exist:
Series seriesNode = null;
if (seriesOid != null) {
seriesNode = SeriesResource.getId(seriesOid);
if (seriesNode == null) {
DataResource.removeTemporaryFile(tmpUID);
throw new InputException("seriesId", "seriesId does not exist ...");
}
if (!seriesNode.typeId.equals(typeNode.oid)) {
DataResource.removeTemporaryFile(tmpUID);
throw new InputException("seriesId", "seriesId object have not the correct parent... '" + seriesNode.typeId + "' != '" + typeNode.oid + "'");
}
}
LOGGER.info(" ==> {}", seriesNode);
LOGGER.info("Find seasonNode");
// get uid of season:
Season seasonNode = null;
if (seriesNode == null && seasonLong != null) {
DataResource.removeTemporaryFile(tmpUID);
throw new InputException("season", "Season is set but no series is set !!");
}
if (season != null) {
seasonNode = SeasonResource.getOrCreate(season, seriesNode.oid);
}
LOGGER.info(" ==> {}", seasonNode);
LOGGER.info("add media");
try {
final Media media = new Media();
media.name = title;
media.dataId = data.oid;
media.typeId = typeNode.oid;
media.seriesId = null;
if (seriesNode != null) {
media.seriesId = seriesNode.oid;
}
media.seasonId = null;
if (seasonNode != null) {
media.seasonId = seasonNode.oid;
}
media.episode = null;
if (episode != null && !episode.contentEquals("")) {
media.episode = Integer.parseInt(episode);
}
final Media out = db.insert(media);
LOGGER.info("Generate new media {}", out);
return out;
} catch (final SQLException ex) {
ex.printStackTrace();
LOGGER.error("Catch error: {}", ex.getMessage());
throw new FailException("Catch SQLerror ==> check server logs");
} finally {
DataResource.removeTemporaryFile(tmpUID);
}
} catch (final Exception ex) {
LOGGER.error("Catch an unexpected error ... {} ", ex.getMessage());
ex.printStackTrace();
throw new FailException("Catch Exception ==> check server logs");
}
}
@DELETE
@Path("{oid}")
@RolesAllowed("ADMIN")
@Operation(description = "Remove a specific Media", tags = "GLOBAL")
public void remove(@PathParam("oid") final ObjectId id) throws Exception {
DataAccess.delete(Media.class, id);
}
}

View File

@@ -0,0 +1,97 @@
package org.atriasoft.karideo.api;
import java.util.List;
import org.atriasoft.archidata.annotation.checker.GroupCreate;
import org.atriasoft.archidata.annotation.checker.GroupUpdate;
import org.atriasoft.archidata.annotation.checker.ValidGroup;
import org.atriasoft.archidata.dataAccess.DataAccess;
import org.atriasoft.archidata.dataAccess.QueryAnd;
import org.atriasoft.archidata.dataAccess.QueryCondition;
import org.atriasoft.archidata.dataAccess.options.Condition;
import org.atriasoft.karideo.model.Season;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.security.RolesAllowed;
import jakarta.validation.Valid;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/season")
@Produces(MediaType.APPLICATION_JSON)
public class SeasonResource {
static final Logger LOGGER = LoggerFactory.getLogger(SeasonResource.class);
@GET
@RolesAllowed("USER")
@Operation(description = "Get a specific Season with his ID", tags = "GLOBAL")
public List<Season> gets() throws Exception {
return DataAccess.gets(Season.class);
}
@GET
@Path("{oid}")
@RolesAllowed("USER")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(description = "Get all season", tags = "GLOBAL")
public Season get(@PathParam("oid") final ObjectId oid) throws Exception {
return DataAccess.get(Season.class, oid);
}
/* ============================================================================= ADMIN SECTION: ============================================================================= */
@POST
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(description = "Create a new season", tags = "GLOBAL")
public Season post(@Valid @ValidGroup(GroupCreate.class) final Season season) throws Exception {
return DataAccess.insert(season);
}
@PUT
@Path("{oid}")
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(description = "Modify a specific season", tags = "GLOBAL")
public Season put(@PathParam("oid") final ObjectId oid, @Valid @ValidGroup(GroupUpdate.class) final Season season) throws Exception {
season.oid = oid;
DataAccess.update(season, oid);
return DataAccess.get(Season.class, oid);
}
@DELETE
@Path("{oid}")
@RolesAllowed("ADMIN")
@Operation(description = "Remove a specific season", tags = "GLOBAL")
public void remove(@PathParam("oid") final ObjectId oid) throws Exception {
DataAccess.delete(Season.class, oid);
}
public static Season getOrCreate(final String name, final ObjectId seriesId) {
try {
Season out = DataAccess.getWhere(Season.class, new Condition(new QueryAnd(new QueryCondition("name", "=", name), new QueryCondition("seriesId", "=", seriesId))));
if (out == null) {
out = new Season();
out.name = name;
out.seriesId = seriesId;
out = DataAccess.insert(out);
}
return out;
} catch (final Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}

View File

@@ -0,0 +1,100 @@
package org.atriasoft.karideo.api;
import java.util.List;
import org.atriasoft.archidata.annotation.checker.GroupCreate;
import org.atriasoft.archidata.annotation.checker.GroupUpdate;
import org.atriasoft.archidata.annotation.checker.ValidGroup;
import org.atriasoft.archidata.dataAccess.DataAccess;
import org.atriasoft.archidata.dataAccess.QueryAnd;
import org.atriasoft.archidata.dataAccess.QueryCondition;
import org.atriasoft.archidata.dataAccess.options.Condition;
import org.atriasoft.karideo.model.Series;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.security.RolesAllowed;
import jakarta.validation.Valid;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/series")
@Produces(MediaType.APPLICATION_JSON)
public class SeriesResource {
static final Logger LOGGER = LoggerFactory.getLogger(SeriesResource.class);
@GET
@RolesAllowed("USER")
@Operation(description = "Get all Series", tags = "GLOBAL")
public List<Series> gets() throws Exception {
return DataAccess.gets(Series.class);
}
@GET
@Path("{oid}")
@RolesAllowed("USER")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(description = "Get a specific Series with his ID", tags = "GLOBAL")
public Series get(@PathParam("oid") final ObjectId oid) throws Exception {
return DataAccess.get(Series.class, oid);
}
public static Series getId(final ObjectId oid) throws Exception {
return DataAccess.get(Series.class, oid);
}
/* ============================================================================= ADMIN SECTION: ============================================================================= */
@POST
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(description = "Create a new Series", tags = "GLOBAL")
public Series post(@Valid @ValidGroup(GroupCreate.class) final Series jsonRequest) throws Exception {
return DataAccess.insert(jsonRequest);
}
@PUT
@Path("{oid}")
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(description = "Modify a specific Series", tags = "GLOBAL")
public Series put(@PathParam("oid") final ObjectId oid, @Valid @ValidGroup(GroupUpdate.class) final Series series) throws Exception {
series.oid = oid;
DataAccess.update(series, oid);
return DataAccess.get(Series.class, oid);
}
@DELETE
@Path("{oid}")
@RolesAllowed("ADMIN")
@Operation(description = "Remove a specific Series", tags = "GLOBAL")
public void remove(@PathParam("oid") final ObjectId oid) throws Exception {
DataAccess.delete(Series.class, oid);
}
public static Series getOrCreate(final String name, final ObjectId typeId) {
try {
Series out = DataAccess.getWhere(Series.class, new Condition(new QueryAnd(new QueryCondition("name", "=", name), new QueryCondition("typeId", "=", typeId))));
if (out == null) {
out = new Series();
out.name = name;
out.typeId = typeId;
out = DataAccess.insert(out);
}
return out;
} catch (final Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}

View File

@@ -0,0 +1,99 @@
package org.atriasoft.karideo.api;
import java.util.List;
import org.atriasoft.archidata.annotation.checker.GroupCreate;
import org.atriasoft.archidata.annotation.checker.GroupUpdate;
import org.atriasoft.archidata.annotation.checker.ValidGroup;
import org.atriasoft.archidata.dataAccess.DataAccess;
import org.atriasoft.archidata.dataAccess.QueryCondition;
import org.atriasoft.archidata.dataAccess.options.Condition;
import org.atriasoft.karideo.model.Type;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.security.RolesAllowed;
import jakarta.validation.Valid;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/type")
@Produces(MediaType.APPLICATION_JSON)
public class TypeResource {
static final Logger LOGGER = LoggerFactory.getLogger(TypeResource.class);
@GET
@RolesAllowed("USER")
@Operation(description = "Get all Type", tags = "GLOBAL")
public List<Type> gets() throws Exception {
return DataAccess.gets(Type.class);
}
@GET
@Path("{oid}")
@RolesAllowed("USER")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(description = "Get a specific Type with his ID", tags = "GLOBAL")
public Type get(@PathParam("oid") final ObjectId oid) throws Exception {
return DataAccess.get(Type.class, oid);
}
public static Type getId(final ObjectId oid) throws Exception {
return DataAccess.get(Type.class, oid);
}
/* ============================================================================= ADMIN SECTION: ============================================================================= */
@POST
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(description = "Create a new Type", tags = "GLOBAL")
public Type post(@Valid @ValidGroup(GroupCreate.class) final Type jsonRequest) throws Exception {
return DataAccess.insert(jsonRequest);
}
@PUT
@Path("{oid}")
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(description = "Modify a specific Type", tags = "GLOBAL")
public Type patch(@PathParam("oid") final ObjectId oid, @Valid @ValidGroup(GroupUpdate.class) final Type type) throws Exception {
type.oid = oid;
DataAccess.update(type, oid);
return DataAccess.get(Type.class, oid);
}
@DELETE
@Path("{oid}")
@RolesAllowed("ADMIN")
@Operation(description = "Remove a specific Type", tags = "GLOBAL")
public void remove(@PathParam("oid") final ObjectId oid) throws Exception {
DataAccess.delete(Type.class, oid);
}
public static Type getOrCreate(final String name) {
try {
Type out = DataAccess.getWhere(Type.class, new Condition(new QueryCondition("name", "=", name)));
if (out == null) {
out = new Type();
out.name = name;
out = DataAccess.insert(out);
}
return out;
} catch (final Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}

View File

@@ -0,0 +1,111 @@
package org.atriasoft.karideo.api;
import java.util.List;
import org.atriasoft.archidata.annotation.checker.GroupUpdate;
import org.atriasoft.archidata.annotation.checker.ValidGroup;
import org.atriasoft.archidata.dataAccess.DataAccess;
import org.atriasoft.archidata.dataAccess.QueryAnd;
import org.atriasoft.archidata.dataAccess.QueryCondition;
import org.atriasoft.archidata.dataAccess.options.Condition;
import org.atriasoft.archidata.dataAccess.options.FilterValue;
import org.atriasoft.archidata.filter.GenericContext;
import org.atriasoft.karideo.model.UserMediaAdvancement;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.security.RolesAllowed;
import jakarta.validation.Valid;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.SecurityContext;
@Path("/advancement")
@Produces(MediaType.APPLICATION_JSON)
public class UserMediaAdvancementResource {
static final Logger LOGGER = LoggerFactory.getLogger(UserMediaAdvancementResource.class);
@GET
@Path("{oid}")
@RolesAllowed("USER")
@Operation(description = "Get a specific user advancement with his ID", tags = "GLOBAL")
public UserMediaAdvancement get(@Context final SecurityContext sc, @PathParam("oid") final ObjectId oid) throws Exception {
final GenericContext gc = (GenericContext) sc.getUserPrincipal();
return DataAccess.getWhere(UserMediaAdvancement.class, new Condition(new QueryAnd(new QueryCondition("mediaId", "=", oid), new QueryCondition("userId", "=", gc.userByToken.oid))));
}
@GET
@RolesAllowed("USER")
@Operation(description = "Get all user advancement", tags = "GLOBAL")
public List<UserMediaAdvancement> gets(@Context final SecurityContext sc) throws Exception {
final GenericContext gc = (GenericContext) sc.getUserPrincipal();
return DataAccess.getsWhere(UserMediaAdvancement.class, new Condition(new QueryCondition("userId", "=", gc.userByToken.oid)));
}
/* ============================================================================= Modification SECTION: ============================================================================= */
public record MediaInformations(int time, float percent, int count) {
}
// @POST
// @Path("{id}")
// @RolesAllowed("USER")
// @Consumes(MediaType.APPLICATION_JSON)
public UserMediaAdvancement post(@Context final SecurityContext sc, @PathParam("oid") final ObjectId oid, final MediaInformations data) throws Exception {
final GenericContext gc = (GenericContext) sc.getUserPrincipal();
final UserMediaAdvancement elem = new UserMediaAdvancement();
elem.userId = gc.userByToken.oid;
elem.mediaId = oid;
elem.time = data.time;
elem.percent = data.percent;
elem.count = data.count;
return DataAccess.insert(elem);
}
public record MediaInformationsDelta(int time, float percent, boolean addCount) {
}
@PUT
@Path("{oid}")
@RolesAllowed("USER")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(description = "Modify a user advancement", tags = "GLOBAL")
public UserMediaAdvancement put(@Context final SecurityContext sc, @PathParam("oid") final ObjectId oid, @Valid @ValidGroup(GroupUpdate.class) final MediaInformationsDelta data) throws Exception {
final UserMediaAdvancement elem = get(sc, oid);
if (elem == null) {
// insert element
if (data.addCount) {
return post(sc, oid, new MediaInformations(data.time(), data.percent(), 1));
} else {
return post(sc, oid, new MediaInformations(data.time(), data.percent(), 0));
}
}
elem.time = data.time;
elem.percent = data.percent;
if (data.addCount) {
elem.count++;
}
LOGGER.info("{},{},{}", elem.time, elem.percent, elem.count);
DataAccess.update(elem, elem.oid, new FilterValue("time", "percent", "count"));
return DataAccess.get(UserMediaAdvancement.class, elem.oid);
}
@DELETE
@Path("{oid}")
@RolesAllowed("USER")
@Operation(description = "Remove a specific user advancement", tags = "GLOBAL")
public void remove(@Context final SecurityContext sc, @PathParam("oid") final ObjectId oid) throws Exception {
final UserMediaAdvancement elem = get(sc, oid);
DataAccess.delete(UserMediaAdvancement.class, elem.oid);
}
}

View File

@@ -1,15 +1,17 @@
package org.kar.karideo.api; package org.atriasoft.karideo.api;
import java.util.List; import java.util.List;
import org.kar.archidata.dataAccess.DataAccess; import org.atriasoft.archidata.dataAccess.DataAccess;
import org.kar.archidata.filter.GenericContext; import org.atriasoft.archidata.filter.GenericContext;
import org.kar.karideo.model.UserKarideo; import org.atriasoft.karideo.model.UserKarideo;
import org.bson.types.ObjectId;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.security.RolesAllowed; import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.GET; import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path; import jakarta.ws.rs.Path;
@@ -20,17 +22,17 @@ import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.SecurityContext; import jakarta.ws.rs.core.SecurityContext;
@Path("/users") @Path("/users")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces(MediaType.APPLICATION_JSON)
public class UserResource { public class UserResource {
static final Logger LOGGER = LoggerFactory.getLogger(UserResource.class); static final Logger LOGGER = LoggerFactory.getLogger(UserResource.class);
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public class UserOut { public class UserOut {
public long id; public ObjectId oid;
public String login; public String login;
public UserOut(final long id, final String login) { public UserOut(final ObjectId oid, final String login) {
this.id = id; this.oid = oid;
this.login = login; this.login = login;
} }
@@ -41,7 +43,8 @@ public class UserResource {
// curl http://localhost:9993/api/users // curl http://localhost:9993/api/users
@GET @GET
@RolesAllowed("ADMIN") @RolesAllowed("ADMIN")
public List<UserKarideo> getUsers() { @Operation(description = "Get all the users", tags = "SYSTEM")
public List<UserKarideo> gets() {
System.out.println("getUsers"); System.out.println("getUsers");
try { try {
return DataAccess.gets(UserKarideo.class); return DataAccess.gets(UserKarideo.class);
@@ -54,9 +57,10 @@ public class UserResource {
// curl http://localhost:9993/api/users/3 // curl http://localhost:9993/api/users/3
@GET @GET
@Path("{id}") @Path("{oid}")
@RolesAllowed("ADMIN") @RolesAllowed("ADMIN")
public UserKarideo getUsers(@Context final SecurityContext sc, @PathParam("id") final long userId) { @Operation(description = "Get a specific user data", tags = "SYSTEM")
public UserKarideo get(@Context final SecurityContext sc, @PathParam("oid") final ObjectId userId) {
System.out.println("getUser " + userId); System.out.println("getUser " + userId);
final GenericContext gc = (GenericContext) sc.getUserPrincipal(); final GenericContext gc = (GenericContext) sc.getUserPrincipal();
System.out.println("==================================================="); System.out.println("===================================================");
@@ -74,11 +78,12 @@ public class UserResource {
@GET @GET
@Path("me") @Path("me")
@RolesAllowed("USER") @RolesAllowed("USER")
@Operation(description = "Get the user personal data", tags = "SYSTEM")
public UserOut getMe(@Context final SecurityContext sc) { public UserOut getMe(@Context final SecurityContext sc) {
LOGGER.debug("getMe()"); LOGGER.debug("getMe()");
final GenericContext gc = (GenericContext) sc.getUserPrincipal(); final GenericContext gc = (GenericContext) sc.getUserPrincipal();
LOGGER.debug("== USER ? {}", gc.userByToken); LOGGER.debug("== USER ? {}", gc.userByToken);
return new UserOut(gc.userByToken.id, gc.userByToken.name); return new UserOut(gc.userByToken.oid, gc.userByToken.name);
} }
} }

View File

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

View File

@@ -0,0 +1,60 @@
package org.atriasoft.karideo.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

@@ -0,0 +1,47 @@
package org.atriasoft.karideo.job;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import org.atriasoft.archidata.backup.BackupEngine;
import org.atriasoft.archidata.backup.BackupEngine.EngineBackupType;
import org.atriasoft.archidata.exception.DataAccessException;
import org.atriasoft.archidata.tools.ConfigBaseVariable;
import org.atriasoft.karideo.migration.Initialization;
import org.atriasoft.karideo.util.ConfigVariable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BackupJob implements Runnable {
final static Logger LOGGER = LoggerFactory.getLogger(BackupJob.class);
BackupEngine engine;
public BackupJob() throws DataAccessException, IOException {
final Path path = Paths.get(ConfigVariable.getBackupFolder());
Files.createDirectories(path);
this.engine = new BackupEngine(path, ConfigBaseVariable.getDBName(), EngineBackupType.JSON_EXTENDED);
this.engine.setEnableStoreOrRestoreData(false);
for (final Class<?> clazz : Initialization.CLASSES_BASE) {
this.engine.addClass(clazz);
}
this.engine.addCollection("counters");
this.engine.addCollection("KAR_migration");
}
@Override
public void run() {
LOGGER.warn("Backup request");
try {
final String timestampUtc = ZonedDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH:mm:ss.SSS"));
this.engine.store(timestampUtc + "_full");
} catch (final Exception ex) {
LOGGER.error("Fail in Backup: {}", ex.getMessage());
ex.printStackTrace();
}
}
}

View File

@@ -0,0 +1,131 @@
package org.atriasoft.karideo.migration;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import org.atriasoft.archidata.api.DataResource;
import org.atriasoft.archidata.backup.BackupEngine;
import org.atriasoft.archidata.backup.BackupEngine.EngineBackupType;
import org.atriasoft.archidata.dataAccess.DBAccess;
import org.atriasoft.archidata.db.DbConfig;
import org.atriasoft.archidata.exception.DataAccessException;
import org.atriasoft.archidata.exception.FailException;
import org.atriasoft.archidata.externalRestApi.AnalyzeApi;
import org.atriasoft.archidata.externalRestApi.TsGenerateApi;
import org.atriasoft.archidata.filter.PartRight;
import org.atriasoft.archidata.migration.MigrationSqlStep;
import org.atriasoft.archidata.model.Data;
import org.atriasoft.archidata.model.User;
import org.atriasoft.archidata.model.token.JwtToken;
import org.atriasoft.archidata.tools.ConfigBaseVariable;
import org.atriasoft.karideo.api.Front;
import org.atriasoft.karideo.api.HealthCheck;
import org.atriasoft.karideo.api.MediaResource;
import org.atriasoft.karideo.api.SeasonResource;
import org.atriasoft.karideo.api.SeriesResource;
import org.atriasoft.karideo.api.TypeResource;
import org.atriasoft.karideo.api.UserMediaAdvancementResource;
import org.atriasoft.karideo.api.UserResource;
import org.atriasoft.karideo.model.Media;
import org.atriasoft.karideo.model.Season;
import org.atriasoft.karideo.model.Series;
import org.atriasoft.karideo.model.Type;
import org.atriasoft.karideo.model.UserMediaAdvancement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.ws.rs.InternalServerErrorException;
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(Data.class, Media.class, Type.class, Series.class, Season.class, User.class, UserMediaAdvancement.class);
@Override
public String getName() {
return "Initialization";
}
public static void removeDB() throws DataAccessException, InternalServerErrorException, IOException {
final DbConfig config = new DbConfig();
LOGGER.info("Remove DB '{}'", config.getDbName());
try (DBAccess dba = DBAccess.createInterface(config)) {
dba.deleteDB(ConfigBaseVariable.bdDatabase);
}
}
/** Restore backup file (./init/backup.tar.gz). */
public static void initializeWithBackup() throws IOException, DataAccessException, FailException {
final Path path = Paths.get("./init/backup.tar.gz");
if (!Files.exists(path)) {
throw new FailException("file: ./init/backup.tar.gz does not exist");
}
removeDB();
final BackupEngine engine = new BackupEngine(Paths.get("."), ConfigBaseVariable.bdDatabase, EngineBackupType.JSON_EXTENDED);
if (!engine.restoreFile(path, null)) {
throw new FailException("Can not retrieve db from backup");
}
}
public static void generateObjects() throws Exception {
LOGGER.info("Generate APIs");
final List<Class<?>> listOfResources = List.of(Front.class, HealthCheck.class, SeasonResource.class, SeriesResource.class, TypeResource.class, UserMediaAdvancementResource.class,
UserResource.class, MediaResource.class, DataResource.class);
final AnalyzeApi api = new AnalyzeApi();
api.addAllApi(listOfResources);
api.addModel(JwtToken.class);
api.addModel(PartRight.class);
TsGenerateApi.generateApi(api, Paths.get("../front/src/back-api/"));
LOGGER.info("Generate APIs (DONE)");
}
@Override
public void generateStep() throws Exception {
for (final Class<?> clazz : CLASSES_BASE) {
addClass(clazz);
}
addAction((final DBAccess da) -> {
final List<Type> data = List.of(//
new Type("Documentary", "Documentary (animals, space, earth...)"), //
new Type("Movie", "Movie with real humans (film)"), //
new Type("Animation", "Animation movies (film)"), //
new Type("Short movie", "Small movies (less 2 minutes)"), //
new Type("TV show", "TV show for old peoples"), //
new Type("Animation TV show", "TV show for young peoples"), //
new Type("Theater", "Theater play"), //
new Type("One man show", "Recorded stand up"), //
new Type("Concert", "Recorded concert"), //
new Type("Opera", "Recorded opera") //
);
da.insertMultiple(data);
});
}
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

@@ -0,0 +1,41 @@
package org.atriasoft.karideo.migration;
import java.util.List;
import org.atriasoft.archidata.dataAccess.DBAccess;
import org.atriasoft.archidata.migration.MigrationSqlStep;
import org.atriasoft.karideo.migration.model.TypeOld;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Migration20250928 extends MigrationSqlStep {
private static final Logger LOGGER = LoggerFactory.getLogger(Migration20250928.class);
public static final int KARSO_INITIALISATION_ID = 1;
@Override
public String getName() {
return "migration-2025-10-12: Fix UserId in advencement";
}
public static ObjectId getElementType(final List<TypeOld> datas, final Long id) throws Exception {
if (id == null) {
return null;
}
for (final var elem : datas) {
if (elem.id.equals(id)) {
return elem.getOid();
}
}
throw new Exception("Does not exist");
}
@Override
public void generateStep() throws Exception {
addAction((final DBAccess daMongo) -> {
});
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,57 @@
package org.atriasoft.karideo.migration.model;
import org.atriasoft.archidata.annotation.DataIfNotExists;
import org.atriasoft.archidata.annotation.DataNotRead;
import org.atriasoft.archidata.annotation.apiGenerator.ApiAccessLimitation;
import org.atriasoft.archidata.annotation.apiGenerator.ApiGenerationMode;
import org.atriasoft.archidata.annotation.checker.CheckForeignKey;
import org.atriasoft.archidata.model.GenericDataSoftDelete;
import org.atriasoft.karideo.model.UserKarideo;
import org.bson.types.ObjectId;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable;
import jakarta.persistence.Column;
import jakarta.persistence.FetchType;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.PositiveOrZero;
@Table(name = "userMediaAdvancement")
@DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL)
@ApiGenerationMode(create = true, update = true)
public class UserMediaAdvancementOld extends GenericDataSoftDelete {
@DataNotRead
@Column(nullable = false)
@Schema(description = "Foreign Key Id of the user")
@ManyToOne(fetch = FetchType.LAZY, targetEntity = UserKarideo.class)
@Nullable
@ApiAccessLimitation(updatable = false, creatable = false)
public Long userId;
@Column(nullable = false)
@Schema(description = "Id of the media")
@ManyToOne(fetch = FetchType.LAZY, targetEntity = MediaOld.class)
@CheckForeignKey(target = MediaOld.class)
public Long mediaId;
@Column(nullable = false)
@Schema(description = "Percent of advancement in the media")
@PositiveOrZero
public Float percent;
@Column(nullable = false)
@Schema(description = "Number of second of advancement in the media")
@PositiveOrZero
public Integer time;
@Column(nullable = false)
@Schema(description = "Number of time this media has been read")
@PositiveOrZero
public Integer count;
@Column(nullable = true)
private final ObjectId oid = new ObjectId();
public ObjectId getOid() {
return this.oid;
}
}

View File

@@ -0,0 +1,68 @@
package org.atriasoft.karideo.model;
import java.util.Date;
import java.util.List;
import org.atriasoft.archidata.annotation.apiGenerator.ApiGenerationMode;
import org.atriasoft.archidata.annotation.checker.CheckForeignKey;
import org.atriasoft.archidata.annotation.checker.CollectionNotEmpty;
import org.atriasoft.archidata.model.Data;
import org.atriasoft.archidata.model.OIDGenericDataSoftDelete;
import org.bson.types.ObjectId;
import org.hibernate.validator.constraints.UniqueElements;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.PositiveOrZero;
import jakarta.validation.constraints.Size;
@Entity()
@JsonInclude(JsonInclude.Include.NON_NULL)
@ApiGenerationMode(create = true, update = true)
public class Media extends OIDGenericDataSoftDelete {
@Schema(description = "Name of the media (this represent the title)")
@Size(min = 0, max = 256)
@NotNull
public String name;
@Schema(description = "Description of the media")
@Size(min = 0, max = 8192)
public String description;
@Schema(description = "Foreign Key Id of the data")
@Column(nullable = false)
@CheckForeignKey(target = Data.class)
public ObjectId dataId;
@Schema(description = "Type of the media")
@CheckForeignKey(target = Type.class)
public ObjectId typeId;
@Schema(description = "Series reference of the media")
@CheckForeignKey(target = Series.class)
public ObjectId seriesId;
@Schema(description = "Season reference of the media")
@CheckForeignKey(target = Season.class)
public ObjectId seasonId;
@Schema(description = "Episode Id")
@PositiveOrZero
public Integer episode;
@Schema(description = "Creation years of the media")
public Date datePublication;
@Schema(description = "Limitation Age of the media")
@PositiveOrZero
public Integer ageLimit;
@Schema(description = "List of Id of the specific covers")
@Nullable
@CollectionNotEmpty
@UniqueElements
public List<@CheckForeignKey(target = Data.class) ObjectId> covers = null;
@Override
public String toString() {
return "Media [name=" + this.name + ", description=" + this.description + ", dataId=" + this.dataId + ", typeId=" + this.typeId + ", seriesId=" + this.seriesId + ", seasonId=" + this.seasonId
+ ", episode=" + this.episode + ", date=" + this.datePublication + ", ageLimit=" + this.ageLimit + ", covers=" + this.covers + "]";
}
}

View File

@@ -0,0 +1,42 @@
package org.atriasoft.karideo.model;
import java.util.List;
import org.atriasoft.archidata.annotation.DataIfNotExists;
import org.atriasoft.archidata.annotation.apiGenerator.ApiGenerationMode;
import org.atriasoft.archidata.annotation.checker.CheckForeignKey;
import org.atriasoft.archidata.annotation.checker.CollectionNotEmpty;
import org.atriasoft.archidata.model.Data;
import org.atriasoft.archidata.model.OIDGenericDataSoftDelete;
import org.bson.types.ObjectId;
import org.hibernate.validator.constraints.UniqueElements;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable;
import jakarta.persistence.Entity;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
@Entity()
@DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL)
@ApiGenerationMode(create = true, update = true)
public class Season extends OIDGenericDataSoftDelete {
@Schema(description = "Name of the media (this represent the title)")
@Size(min = 0, max = 256)
public String name;
@Schema(description = "Description of the media")
@Size(min = 0, max = 8192)
public String description;
@Schema(description = "series parent ID")
// @ManyToOneDoc(targetEntity = Series.class, remoteField = "")
@CheckForeignKey(target = Series.class)
public ObjectId seriesId;
@Schema(description = "List of Id of the specific covers")
@Nullable
@CollectionNotEmpty
@UniqueElements
public List<@CheckForeignKey(target = Data.class) @NotNull ObjectId> covers = null;
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,43 @@
package org.atriasoft.karideo.model;
import org.atriasoft.archidata.annotation.DataIfNotExists;
import org.atriasoft.archidata.annotation.DataNotRead;
import org.atriasoft.archidata.annotation.apiGenerator.ApiGenerationMode;
import org.atriasoft.archidata.annotation.checker.CheckForeignKey;
import org.atriasoft.archidata.model.OIDGenericDataSoftDelete;
import org.bson.types.ObjectId;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Entity;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.PositiveOrZero;
@Entity()
@DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL)
@ApiGenerationMode(create = true, update = true)
public class UserMediaAdvancement extends OIDGenericDataSoftDelete {
@DataNotRead
@NotNull
@Schema(description = "Foreign Key Id of the user")
@CheckForeignKey(target = UserKarideo.class)
public ObjectId userId;
@NotNull
@Schema(description = "Id of the media")
@CheckForeignKey(target = Media.class)
public ObjectId mediaId;
@NotNull
@Schema(description = "Percent of advancement in the media")
@PositiveOrZero
public Float percent;
@NotNull
@Schema(description = "Number of second of advancement in the media")
@PositiveOrZero
public Integer time;
@NotNull
@Schema(description = "Number of time this media has been read")
@PositiveOrZero
public Integer count;
}

View File

@@ -0,0 +1,29 @@
package org.atriasoft.karideo.util;
public class ConfigVariable {
public static final String BASE_NAME = "ORG_KARIDEO_";
public static String getFrontFolder() {
final String out = System.getenv(BASE_NAME + "FRONT_FOLDER");
if (out == null) {
return "./front";
}
return out;
}
public static String getBackupFolder() {
final String out = System.getenv(BASE_NAME + "BACKUP_FOLDER");
if (out == null) {
return "./backup";
}
return out;
}
public static boolean isInitWithBackup() {
final String out = System.getenv(BASE_NAME + "INIT_WITH_BACKUP");
if (out == null) {
return false;
}
return "true".equals(out);
}
}

View File

@@ -1,118 +0,0 @@
package org.kar.karideo.api;
import java.io.InputStream;
import java.util.List;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.QueryAnd;
import org.kar.archidata.dataAccess.QueryCondition;
import org.kar.archidata.dataAccess.addOn.AddOnManyToMany;
import org.kar.archidata.dataAccess.options.Condition;
import org.kar.archidata.tools.DataTools;
import org.kar.karideo.model.Season;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.PATCH;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@Path("/season")
@Produces({ MediaType.APPLICATION_JSON })
public class SeasonResource {
static final Logger LOGGER = LoggerFactory.getLogger(SeasonResource.class);
@GET
@Path("{id}")
@RolesAllowed("USER")
public static Season getWithId(@PathParam("id") final Long id) throws Exception {
return DataAccess.get(Season.class, id);
}
@GET
@RolesAllowed("USER")
public List<Season> get() throws Exception {
return DataAccess.gets(Season.class);
}
@GET
@Path("{id}")
@RolesAllowed("USER")
@Consumes(MediaType.APPLICATION_JSON)
public Season get(@PathParam("id") final Long id) throws Exception {
return DataAccess.get(Season.class, id);
}
/* =============================================================================
* ADMIN SECTION:
* ============================================================================= */
@POST
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
public Season put(final String jsonRequest) throws Exception {
return DataAccess.insertWithJson(Season.class, jsonRequest);
}
@PATCH
@Path("{id}")
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
public Season put(@PathParam("id") final Long id, final String jsonRequest) throws Exception {
DataAccess.updateWithJson(Season.class, id, jsonRequest);
return DataAccess.get(Season.class, id);
}
@DELETE
@Path("{id}")
@RolesAllowed("ADMIN")
public Response delete(@PathParam("id") final Long id) throws Exception {
DataAccess.delete(Season.class, id);
return Response.ok().build();
}
@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(Season.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(Season.class, id, "cover", coverId);
return Response.ok(DataAccess.get(Season.class, id)).build();
}
public static Season getOrCreate(final String name, final Long seriesId) {
try {
Season out = DataAccess.getWhere(Season.class, new Condition(new QueryAnd(new QueryCondition("name", "=", name), new QueryCondition("parentId", "=", seriesId))));
if (out == null) {
out = new Season();
out.name = name;
out.parentId = seriesId;
out = DataAccess.insert(out);
}
return out;
} catch (final Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}

View File

@@ -1,118 +0,0 @@
package org.kar.karideo.api;
import java.io.InputStream;
import java.util.List;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.QueryAnd;
import org.kar.archidata.dataAccess.QueryCondition;
import org.kar.archidata.dataAccess.addOn.AddOnManyToMany;
import org.kar.archidata.dataAccess.options.Condition;
import org.kar.archidata.tools.DataTools;
import org.kar.karideo.model.Series;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.PATCH;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@Path("/series")
@Produces({ MediaType.APPLICATION_JSON })
public class SeriesResource {
static final Logger LOGGER = LoggerFactory.getLogger(SeriesResource.class);
@GET
@Path("{id}")
@RolesAllowed("USER")
public static Series getWithId(@PathParam("id") final Long id) throws Exception {
return DataAccess.get(Series.class, id);
}
@GET
@RolesAllowed("USER")
public List<Series> get() throws Exception {
return DataAccess.gets(Series.class);
}
@GET
@Path("{id}")
@RolesAllowed("USER")
@Consumes(MediaType.APPLICATION_JSON)
public Series get(@PathParam("id") final Long id) throws Exception {
return DataAccess.get(Series.class, id);
}
/* =============================================================================
* ADMIN SECTION:
* ============================================================================= */
@POST
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
public Series put(final String jsonRequest) throws Exception {
return DataAccess.insertWithJson(Series.class, jsonRequest);
}
@PATCH
@Path("{id}")
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
public Series put(@PathParam("id") final Long id, final String jsonRequest) throws Exception {
DataAccess.updateWithJson(Series.class, id, jsonRequest);
return DataAccess.get(Series.class, id);
}
@DELETE
@Path("{id}")
@RolesAllowed("ADMIN")
public Response delete(@PathParam("id") final Long id) throws Exception {
DataAccess.delete(Series.class, id);
return Response.ok().build();
}
@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(Series.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(Series.class, id, "cover", coverId);
return Response.ok(DataAccess.get(Series.class, id)).build();
}
public static Series getOrCreate(final String name, final Long typeId) {
try {
Series out = DataAccess.getWhere(Series.class, new Condition(new QueryAnd(new QueryCondition("name", "=", name), new QueryCondition("parentId", "=", typeId))));
if (out == null) {
out = new Series();
out.name = name;
out.parentId = typeId;
out = DataAccess.insert(out);
}
return out;
} catch (final Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}

View File

@@ -1,120 +0,0 @@
package org.kar.karideo.api;
import java.io.InputStream;
import java.util.List;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
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.tools.DataTools;
import org.kar.karideo.model.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.PATCH;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@Path("/type")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public class TypeResource {
static final Logger LOGGER = LoggerFactory.getLogger(TypeResource.class);
@GET
@Path("{id}")
@RolesAllowed("USER")
public static Type getWithId(@PathParam("id") final Long id) throws Exception {
return DataAccess.get(Type.class, id);
}
@GET
@RolesAllowed("USER")
public List<Type> get() throws Exception {
return DataAccess.gets(Type.class);
}
@GET
@Path("{id}")
@RolesAllowed("USER")
@Consumes(MediaType.APPLICATION_JSON)
public Type get(@PathParam("id") final Long id) throws Exception {
return DataAccess.get(Type.class, id);
}
public static Type getId(final Long id) throws Exception {
return DataAccess.get(Type.class, id);
}
/* =============================================================================
* ADMIN SECTION:
* ============================================================================= */
@POST
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
public Type put(final String jsonRequest) throws Exception {
return DataAccess.insertWithJson(Type.class, jsonRequest);
}
@PATCH
@Path("{id}")
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
public Type put(@PathParam("id") final Long id, final String jsonRequest) throws Exception {
DataAccess.updateWithJson(Type.class, id, jsonRequest);
return DataAccess.get(Type.class, id);
}
@DELETE
@Path("{id}")
@RolesAllowed("ADMIN")
public Response delete(@PathParam("id") final Long id) throws Exception {
DataAccess.delete(Type.class, id);
return Response.ok().build();
}
@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(Type.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(Type.class, id, "cover", coverId);
return Response.ok(DataAccess.get(Type.class, id)).build();
}
public static Type getOrCreate(final String name) {
try {
Type out = DataAccess.getWhere(Type.class, new Condition(new QueryCondition("name", "=", name)));
if (out == null) {
out = new Type();
out.name = name;
out = DataAccess.insert(out);
}
return out;
} catch (final Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}

View File

@@ -1,109 +0,0 @@
package org.kar.karideo.api;
import java.util.List;
import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.QueryAnd;
import org.kar.archidata.dataAccess.QueryCondition;
import org.kar.archidata.dataAccess.options.Condition;
import org.kar.archidata.filter.GenericContext;
import org.kar.karideo.model.UserMediaAdvancement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.PATCH;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.SecurityContext;
@Path("/advancement")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public class UserMediaAdvancementResource {
static final Logger LOGGER = LoggerFactory.getLogger(UserMediaAdvancementResource.class);
@GET
@Path("{id}")
@RolesAllowed("USER")
public UserMediaAdvancement getWithId(@Context final SecurityContext sc, @PathParam("id") final Long id) throws Exception {
final GenericContext gc = (GenericContext) sc.getUserPrincipal();
return DataAccess.getWhere(UserMediaAdvancement.class, new Condition(new QueryAnd(new QueryCondition("mediaId", "=", id), new QueryCondition("userId", "=", gc.userByToken.id))));
}
@GET
@RolesAllowed("USER")
public List<UserMediaAdvancement> gets(@Context final SecurityContext sc) throws Exception {
final GenericContext gc = (GenericContext) sc.getUserPrincipal();
return DataAccess.getsWhere(UserMediaAdvancement.class, new Condition(new QueryCondition("userId", "=", gc.userByToken.id)));
}
/* =============================================================================
* Modification SECTION:
* ============================================================================= */
public record MediaInformations(
int time,
float percent,
int count) {}
//@POST
//@Path("{id}")
//@RolesAllowed("USER")
//@Consumes(MediaType.APPLICATION_JSON)
public UserMediaAdvancement post(@Context final SecurityContext sc, @PathParam("id") final Long id, final MediaInformations data) throws Exception {
final GenericContext gc = (GenericContext) sc.getUserPrincipal();
final UserMediaAdvancement elem = new UserMediaAdvancement();
elem.userId = gc.userByToken.id;
elem.mediaId = id;
elem.time = data.time;
elem.percent = data.percent;
elem.count = data.count;
return DataAccess.insert(elem);
}
public record MediaInformationsDelta(
int time,
float percent,
boolean addCount) {}
@PATCH
@Path("{id}")
@RolesAllowed("USER")
@Consumes(MediaType.APPLICATION_JSON)
public UserMediaAdvancement put(@Context final SecurityContext sc, @PathParam("id") final Long id, final MediaInformationsDelta data) throws Exception {
final UserMediaAdvancement elem = getWithId(sc, id);
if (elem == null) {
// insert element
if (data.addCount) {
return post(sc, id, new MediaInformations(data.time(), data.percent(), 1));
} else {
return post(sc, id, new MediaInformations(data.time(), data.percent(), 0));
}
}
elem.time = data.time;
elem.percent = data.percent;
if (data.addCount) {
elem.count++;
}
LOGGER.info("{},{},{}", elem.time, elem.percent, elem.count);
final int nbAfected = DataAccess.update(elem, elem.id, List.of("time", "percent", "count"));
return DataAccess.get(UserMediaAdvancement.class, elem.id);
}
@DELETE
@Path("{id}")
@RolesAllowed("USER")
public Response delete(@Context final SecurityContext sc, @PathParam("id") final Long id) throws Exception {
final UserMediaAdvancement elem = getWithId(sc, id);
DataAccess.delete(UserMediaAdvancement.class, elem.id);
return Response.ok().build();
}
}

View File

@@ -1,223 +0,0 @@
package org.kar.karideo.api;
import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.List;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.kar.archidata.api.DataResource;
import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.addOn.AddOnManyToMany;
import org.kar.archidata.exception.FailException;
import org.kar.archidata.exception.InputException;
import org.kar.archidata.model.Data;
import org.kar.archidata.tools.DataTools;
import org.kar.karideo.model.Media;
import org.kar.karideo.model.Season;
import org.kar.karideo.model.Series;
import org.kar.karideo.model.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.PATCH;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@Path("/video")
@Produces({ MediaType.APPLICATION_JSON })
public class VideoResource {
static final Logger LOGGER = LoggerFactory.getLogger(VideoResource.class);
@GET
@RolesAllowed("USER")
public List<Media> get() throws Exception {
return DataAccess.gets(Media.class);
}
@GET
@Path("{id}")
@RolesAllowed("USER")
public Media get(@PathParam("id") final Long id) throws Exception {
return DataAccess.get(Media.class, id);
}
@PATCH
@Path("{id}")
@RolesAllowed("ADMIN")
@Consumes(MediaType.APPLICATION_JSON)
public Media put(@PathParam("id") final Long id, final String jsonRequest) throws Exception {
System.out.println("update video " + id + " ==> '" + jsonRequest + "'");
DataAccess.updateWithJson(Media.class, id, jsonRequest);
return DataAccess.get(Media.class, id);
}
private String multipartCorrection(final String data) {
if (data == null) {
return null;
}
if (data.isEmpty()) {
return null;
}
if (data.contentEquals("null")) {
return null;
}
return data;
}
@POST
@Path("/upload/")
@RolesAllowed("ADMIN")
@Consumes({ MediaType.MULTIPART_FORM_DATA })
public Response uploadFile(@FormDataParam("fileName") String fileName, @FormDataParam("universe") String universe, @FormDataParam("series") String series,
//@FormDataParam("seriesId") String seriesId, Not used ...
@FormDataParam("season") String season, @FormDataParam("episode") String episode, @FormDataParam("title") String title, @FormDataParam("typeId") String typeId,
@FormDataParam("file") final InputStream fileInputStream, @FormDataParam("file") final FormDataContentDisposition fileMetaData) throws FailException {
try {
// correct input string stream :
fileName = multipartCorrection(fileName);
universe = multipartCorrection(universe);
series = multipartCorrection(series);
season = multipartCorrection(season);
episode = multipartCorrection(episode);
title = multipartCorrection(title);
typeId = multipartCorrection(typeId);
//public NodeSmall uploadFile(final FormDataMultiPart form) {
System.out.println("Upload media file: " + fileMetaData);
System.out.println(" - fileName: " + fileName);
System.out.println(" - universe: " + universe);
System.out.println(" - series: " + series);
System.out.println(" - season: " + season);
System.out.println(" - episode: " + episode);
System.out.println(" - title: " + title);
System.out.println(" - type: " + typeId);
System.out.println(" - fileInputStream: " + fileInputStream);
System.out.println(" - fileMetaData: " + fileMetaData);
System.out.flush();
if (typeId == null) {
throw new InputException("typeId", "TypiId is not specified");
}
final long tmpUID = DataResource.getTmpDataId();
final String sha512 = DataResource.saveTemporaryFile(fileInputStream, tmpUID);
Data data = DataResource.getWithSha512(sha512);
if (data == null) {
System.out.println("Need to add the data in the BDD ... ");
System.out.flush();
try {
data = DataResource.createNewData(tmpUID, fileName, sha512);
} catch (final IOException ex) {
DataResource.removeTemporaryFile(tmpUID);
ex.printStackTrace();
throw new FailException("can not create input media (the data model has an internal error");
}
} else if (data.deleted) {
System.out.println("Data already exist but deleted");
System.out.flush();
DataResource.undelete(data.id);
data.deleted = false;
} else {
System.out.println("Data already exist ... all good");
System.out.flush();
}
// Fist step: retive all the Id of each parents:...
System.out.println("Find typeNode");
// check if id of type exist:
final Type typeNode = TypeResource.getId(Long.parseLong(typeId));
if (typeNode == null) {
DataResource.removeTemporaryFile(tmpUID);
throw new InputException("typeId", "TypeId does not exist ...");
}
System.out.println(" ==> " + typeNode);
System.out.println("Find seriesNode");
// get uid of group:
Series seriesNode = null;
if (series != null) {
seriesNode = SeriesResource.getOrCreate(series, typeNode.id);
}
System.out.println(" ==> " + seriesNode);
System.out.println("Find seasonNode");
// get uid of season:
Season seasonNode = null;
if (seriesNode == null && season != null) {
DataResource.removeTemporaryFile(tmpUID);
throw new InputException("season", "Season is set but no seraies is set !!");
}
if (season != null) {
seasonNode = SeasonResource.getOrCreate(season, seriesNode.id);
}
System.out.println(" ==> " + seasonNode);
System.out.println("add media");
final long uniqueSQLID = -1;
try {
final Media media = new Media();
media.name = title;
media.dataId = data.id;
media.typeId = typeNode.id;
media.seriesId = null;
if (seriesNode != null) {
media.seriesId = seriesNode.id;
}
media.seasonId = null;
if (seasonNode != null) {
media.seasonId = seasonNode.id;
}
media.episode = null;
if (episode != null && !episode.contentEquals("")) {
media.episode = Integer.parseInt(episode);
}
final Media out = DataAccess.insert(media);
DataResource.removeTemporaryFile(tmpUID);
System.out.println("uploaded .... compleate: " + uniqueSQLID);
final Media creation = get(uniqueSQLID);
return Response.ok(creation).build();
} catch (final SQLException ex) {
ex.printStackTrace();
System.out.println("Catch error:" + ex.getMessage());
throw new FailException("Catch SQLerror ==> check server logs");
}
} catch (final Exception ex) {
System.out.println("Catch an unexpected error ... " + ex.getMessage());
ex.printStackTrace();
throw new FailException("Catch Exception ==> check server logs");
}
}
@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(Media.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(Media.class, id, "cover", coverId);
return Response.ok(DataAccess.get(Media.class, id)).build();
}
@DELETE
@Path("{id}")
@RolesAllowed("ADMIN")
public Response delete(@PathParam("id") final Long id) throws Exception {
DataAccess.delete(Media.class, id);
return Response.ok().build();
}
}

View File

@@ -1,60 +0,0 @@
package org.kar.karideo.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,69 +0,0 @@
package org.kar.karideo.migration;
import org.kar.archidata.migration.MigrationSqlStep;
import org.kar.archidata.model.Data;
import org.kar.archidata.model.User;
import org.kar.karideo.model.Media;
import org.kar.karideo.model.Season;
import org.kar.karideo.model.Series;
import org.kar.karideo.model.Type;
import org.kar.karideo.model.UserMediaAdvancement;
public class Initialization extends MigrationSqlStep {
public static final int KARSO_INITIALISATION_ID = 1;
@Override
public String getName() {
return "Initialization";
}
public Initialization() {
}
@Override
public void generateStep() throws Exception {
addClass(Data.class);
addClass(Media.class);
addClass(Type.class);
addClass(Series.class);
addClass(Season.class);
addClass(User.class);
addClass(UserMediaAdvancement.class);
addAction("""
INSERT INTO `type` (`id`, `name`, `description`) VALUES
(1, 'Documentary', 'Documentary (animals, space, earth...)'),
(2, 'Movie', 'Movie with real humans (film)'),
(3, 'Animation', 'Animation movies (film)'),
(4, 'Short movie', 'Small movies (less 2 minutes)'),
(5, 'TV show', 'TV show for old peoples'),
(6, 'Animation TV show', 'TV show for young peoples'),
(7, 'Theater', 'Theater play'),
(8, 'One man show', 'Recorded stand up'),
(9, 'Concert', 'Recorded concert'),
(10, 'Opera', 'Recorded opera');
""");
// set start increment element to permit to add after default elements
addAction("""
ALTER TABLE `data` AUTO_INCREMENT = 1000;
""", "mysql");
addAction("""
ALTER TABLE `media` AUTO_INCREMENT = 1000;
""", "mysql");
addAction("""
ALTER TABLE `type` AUTO_INCREMENT = 1000;
""", "mysql");
addAction("""
ALTER TABLE `series` AUTO_INCREMENT = 1000;
""", "mysql");
addAction("""
ALTER TABLE `season` AUTO_INCREMENT = 1000;
""", "mysql");
addAction("""
ALTER TABLE `userMediaAdvancement` AUTO_INCREMENT = 1000;
""", "mysql");
}
}

View File

@@ -1,28 +0,0 @@
package org.kar.karideo.migration;
import org.kar.archidata.migration.MigrationSqlStep;
import org.kar.karideo.model.UserMediaAdvancement;
public class Migration20230810 extends MigrationSqlStep {
public static final int KARSO_INITIALISATION_ID = 1;
@Override
public String getName() {
return "migration-2023-08-10";
}
public Migration20230810() {
}
@Override
public void generateStep() throws Exception {
addClass(UserMediaAdvancement.class);
addAction("""
ALTER TABLE `userMediaAdvancement` AUTO_INCREMENT = 1000;
""");
}
}

View File

@@ -1,32 +0,0 @@
package org.kar.karideo.migration;
import java.util.List;
import org.kar.archidata.migration.MigrationSqlStep;
public class Migration20231015 extends MigrationSqlStep {
public static final int KARSO_INITIALISATION_ID = 1;
@Override
public String getName() {
return "migration-2023-10-15: refactor creation and update time";
}
public Migration20231015() {
}
@Override
public void generateStep() throws Exception {
for (String elem : List.of("data", "media", "media_link_cover", "season", "season_link_cover", "series", "series_link_cover", "type", "type_link_cover", "user", "userMediaAdvancement")) {
addAction("""
ALTER TABLE `""" + elem + """
`
RENAME COLUMN `create_date` TO `createdAt`,
RENAME COLUMN `modify_date` TO `updatedAt`;
""");
}
display();
}
}

View File

@@ -1,160 +0,0 @@
package org.kar.karideo.migration;
import org.kar.archidata.migration.MigrationSqlStep;
public class Migration20231126 extends MigrationSqlStep {
public static final int KARSO_INITIALISATION_ID = 1;
@Override
public String getName() {
return "migration-2023-11-26: re-order 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 `data`
CHANGE `id` `id` bigint NOT NULL COMMENT 'Primary key of the base' AUTO_INCREMENT FIRST,
CHANGE `createdAt` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Create time of the object' AFTER `id`,
CHANGE `updatedAt` `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 `media`
CHANGE `id` `id` bigint NOT NULL COMMENT 'Primary key of the base' AUTO_INCREMENT FIRST,
CHANGE `createdAt` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Create time of the object' AFTER `id`,
CHANGE `updatedAt` `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` text COLLATE 'utf8mb3_general_ci' NOT NULL COMMENT 'Name of the media (this represent the title)' AFTER `deleted`,
CHANGE `description` `description` text COLLATE 'utf8mb3_general_ci' NULL COMMENT 'Description of the media' AFTER `name`,
CHANGE `dataId` `dataId` bigint NOT NULL COMMENT 'Foreign Key Id of the data' AFTER `description`,
CHANGE `typeId` `typeId` bigint NULL COMMENT 'Type of the media' AFTER `dataId`,
CHANGE `seriesId` `seriesId` bigint NULL COMMENT 'Series reference of the media' AFTER `typeId`,
CHANGE `seasonId` `seasonId` bigint NULL COMMENT 'Saison reference of the media' AFTER `seriesId`,
CHANGE `episode` `episode` int NULL COMMENT 'Episide Id' AFTER `seasonId`,
CHANGE `date` `date` int NULL AFTER `episode`,
CHANGE `time` `time` int NULL COMMENT 'Creation years of the media' AFTER `date`,
CHANGE `ageLimit` `ageLimit` int NULL COMMENT 'Limitation Age of the media' AFTER `time`;
""");
addAction("""
ALTER TABLE `media_link_cover`
CHANGE `createdAt` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) AFTER `id`,
CHANGE `updatedAt` `updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) AFTER `createdAt`,
CHANGE `deleted` `deleted` tinyint(1) NOT NULL DEFAULT '0' AFTER `updatedAt`,
CHANGE `media_id` `object1id` bigint NOT NULL AFTER `deleted`,
CHANGE `cover_id` `object2id` bigint NOT NULL AFTER `object1id`;
""");
addAction("""
ALTER TABLE `season`
CHANGE `id` `id` bigint NOT NULL COMMENT 'Primary key of the base' AUTO_INCREMENT FIRST,
CHANGE `createdAt` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Create time of the object' AFTER `id`,
CHANGE `updatedAt` `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` text COLLATE 'utf8mb3_general_ci' NOT NULL COMMENT 'Name of the media (this represent the title)' AFTER `deleted`,
CHANGE `description` `description` text COLLATE 'utf8mb3_general_ci' NULL COMMENT 'Description of the media' AFTER `name`,
CHANGE `parentId` `parentId` bigint NOT NULL COMMENT 'series parent ID' AFTER `description`;
""");
addAction("""
ALTER TABLE `season_link_cover`
CHANGE `createdAt` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) AFTER `id`,
CHANGE `updatedAt` `updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) AFTER `createdAt`,
CHANGE `deleted` `deleted` tinyint(1) NOT NULL DEFAULT '0' AFTER `updatedAt`,
CHANGE `season_id` `object1id` bigint NOT NULL AFTER `deleted`,
CHANGE `cover_id` `object2id` bigint NOT NULL AFTER `object1id`;
""");
addAction("""
ALTER TABLE `series`
CHANGE `id` `id` bigint NOT NULL COMMENT 'Primary key of the base' AUTO_INCREMENT FIRST,
CHANGE `createdAt` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Create time of the object' AFTER `id`,
CHANGE `updatedAt` `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` text COLLATE 'utf8mb3_general_ci' NOT NULL COMMENT 'Name of the media (this represent the title)' AFTER `deleted`,
CHANGE `description` `description` text COLLATE 'utf8mb3_general_ci' NULL COMMENT 'Description of the media' AFTER `name`,
CHANGE `parentId` `parentId` bigint NOT NULL COMMENT 'series parent ID' AFTER `description`;
""");
addAction("""
ALTER TABLE `series_link_cover`
CHANGE `createdAt` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) AFTER `id`,
CHANGE `updatedAt` `updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) AFTER `createdAt`,
CHANGE `deleted` `deleted` tinyint(1) NOT NULL DEFAULT '0' AFTER `updatedAt`,
CHANGE `series_id` `object1id` bigint NOT NULL AFTER `deleted`,
CHANGE `cover_id` `object2id` bigint NOT NULL AFTER `object1id`;
""");
addAction("""
ALTER TABLE `type`
CHANGE `id` `id` bigint NOT NULL COMMENT 'Primary key of the base' AUTO_INCREMENT FIRST,
CHANGE `createdAt` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Create time of the object' AFTER `id`,
CHANGE `updatedAt` `updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'When update the object' AFTER `createdAt`,
CHANGE `name` `name` text COLLATE 'utf8mb3_general_ci' NOT NULL COMMENT 'Name of the media (this represent the title)' AFTER `deleted`,
CHANGE `description` `description` text COLLATE 'utf8mb3_general_ci' NULL COMMENT 'Description of the media' AFTER `name`;
""");
addAction("""
ALTER TABLE `type_link_cover`
CHANGE `createdAt` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) AFTER `id`,
CHANGE `updatedAt` `updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) AFTER `createdAt`,
CHANGE `deleted` `deleted` tinyint(1) NOT NULL DEFAULT '0' AFTER `updatedAt`,
CHANGE `type_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 `createdAt` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Create time of the object' AFTER `id`,
CHANGE `updatedAt` `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("""
ALTER TABLE `userMediaAdvancement`
CHANGE `id` `id` bigint NOT NULL COMMENT 'Primary key of the base' AUTO_INCREMENT FIRST,
CHANGE `createdAt` `createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Create time of the object' AFTER `id`,
CHANGE `updatedAt` `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 `userId` `userId` bigint NOT NULL COMMENT 'Foreign Key Id of the user' AFTER `deleted`,
CHANGE `mediaId` `mediaId` bigint NOT NULL COMMENT 'Id of the media' AFTER `userId`,
CHANGE `percent` `percent` float NOT NULL COMMENT 'Percent of admencement in the media' AFTER `mediaId`,
CHANGE `time` `time` int NOT NULL COMMENT 'Number of second of admencement in the media' AFTER `percent`,
CHANGE `count` `count` int NOT NULL COMMENT 'Number of time this media has been read' AFTER `time`;
""");
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

@@ -1,53 +0,0 @@
package org.kar.karideo.model;
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.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
@Entity
@Table(name = "media")
@JsonInclude(JsonInclude.Include.NON_NULL)
//@SQLDelete(sql = "UPDATE table_product SET deleted = true WHERE id=?")
//@Where(clause = "deleted=false")
public class Media extends GenericDataSoftDelete {
// Name of the media (this represent the title)
@Column(nullable = false, length = 0)
public String name;
// Description of the media
@Column(length = 0)
public String description;
// Foreign Key Id of the data
@ManyToOne(fetch = FetchType.LAZY, targetEntity = Data.class)
@Column(nullable = false)
public Long dataId;
// Type of the media")
@ManyToOne(fetch = FetchType.LAZY, targetEntity = Type.class)
public Long typeId;
// Series reference of the media
@ManyToOne(fetch = FetchType.LAZY, targetEntity = Series.class)
public Long seriesId;
// Saison reference of the media
@ManyToOne(fetch = FetchType.LAZY, targetEntity = Season.class)
public Long seasonId;
// Episide Id
public Integer episode;
// ")
public Integer date;
// Creation years of the media
public Integer time;
// Limitation Age of the media
public Integer ageLimit;
// List of Id of the specific covers
@ManyToMany(fetch = FetchType.LAZY, targetEntity = Data.class)
public List<Long> covers = null;
}

View File

@@ -1,35 +0,0 @@
package org.kar.karideo.model;
import java.util.List;
import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.model.Data;
import org.kar.archidata.model.GenericDataSoftDelete;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column;
import jakarta.persistence.FetchType;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
@Table(name = "season")
@DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Season extends GenericDataSoftDelete {
@Column(nullable = false, length = 0)
@Schema(description = "Name of the media (this represent the title)")
public String name;
@Column(length = 0)
@Schema(description = "Description of the media")
public String description;
@Column(nullable = false)
@Schema(description = "series parent ID")
@ManyToOne(fetch = FetchType.LAZY, targetEntity = Series.class)
public Long parentId;
@Schema(description = "List of Id of the sopecific covers")
@ManyToMany(fetch = FetchType.LAZY, targetEntity = Data.class)
public List<Long> covers = null;
}

View File

@@ -1,35 +0,0 @@
package org.kar.karideo.model;
import java.util.List;
import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.model.Data;
import org.kar.archidata.model.GenericDataSoftDelete;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column;
import jakarta.persistence.FetchType;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
@Table(name = "series")
@DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Series extends GenericDataSoftDelete {
@Column(nullable = false, length = 0)
@Schema(description = "Name of the media (this represent the title)")
public String name;
@Column(length = 0)
@Schema(description = "Description of the media")
public String description;
@Column(nullable = false)
@Schema(description = "series parent ID")
@ManyToOne(fetch = FetchType.LAZY, targetEntity = Type.class)
public Long parentId;
@Schema(description = "List of Id of the sopecific covers")
@ManyToMany(fetch = FetchType.LAZY, targetEntity = Data.class)
public List<Long> covers = null;
}

View File

@@ -1,30 +0,0 @@
package org.kar.karideo.model;
import java.util.List;
import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.model.Data;
import org.kar.archidata.model.GenericDataSoftDelete;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column;
import jakarta.persistence.FetchType;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
@Table(name = "type")
@DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Type extends GenericDataSoftDelete {
@Column(nullable = false, length = 0)
@Schema(description = "Name of the media (this represent the title)")
public String name;
@Column(length = 0)
@Schema(description = "Description of the media")
public String description;
@Schema(description = "List of Id of the sopecific covers")
@ManyToMany(fetch = FetchType.LAZY, targetEntity = Data.class)
public List<Long> covers = null;
}

View File

@@ -1,35 +0,0 @@
package org.kar.karideo.model;
import org.kar.archidata.annotation.DataIfNotExists;
import org.kar.archidata.model.GenericDataSoftDelete;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column;
import jakarta.persistence.FetchType;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
@Table(name = "userMediaAdvancement")
@DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL)
public class UserMediaAdvancement extends GenericDataSoftDelete {
@Column(nullable = false)
@Schema(description = "Foreign Key Id of the user")
@ManyToOne(fetch = FetchType.LAZY, targetEntity = UserKarideo.class)
public long userId;
@Column(nullable = false)
@Schema(description = "Id of the media")
@ManyToOne(fetch = FetchType.LAZY, targetEntity = Media.class)
public long mediaId;
@Column(nullable = false)
@Schema(description = "Percent of admencement in the media")
public float percent;
@Column(nullable = false)
@Schema(description = "Number of second of admencement in the media")
public int time;
@Column(nullable = false)
@Schema(description = "Number of time this media has been read")
public int count;
}

View File

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

View File

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

View File

@@ -0,0 +1,115 @@
package test.atriasoft.karideo;
import java.io.IOException;
import org.atriasoft.archidata.dataAccess.DBAccess;
import org.atriasoft.archidata.db.DbConfig;
import org.atriasoft.archidata.db.DbIoFactory;
import org.atriasoft.archidata.exception.DataAccessException;
import org.atriasoft.archidata.tools.ConfigBaseVariable;
import org.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";
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_Karideo_db";
ConfigBaseVariable.dbPort = "3906";
ConfigBaseVariable.dbUser = "root";
} else if ("MONGO".equalsIgnoreCase(modeTest)) {
ConfigBaseVariable.dbType = "mongo";
ConfigBaseVariable.bdDatabase = "test_Karideo_db";
} else {
// User local modification ...
ConfigBaseVariable.bdDatabase = "test_Karideo_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,4 +1,4 @@
package test.kar.karideo; package test.atriasoft.karideo;
import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExecutionCondition;

View File

@@ -0,0 +1,48 @@
package test.atriasoft.karideo;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith;
import org.atriasoft.archidata.tools.ConfigBaseVariable;
import org.atriasoft.archidata.tools.RESTApi;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@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;
@BeforeAll
public static void configureWebServer() throws Exception {
ConfigureDb.configure();
LOGGER.info("configure server ...");
webInterface = new WebLauncherTest();
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 Exception {
LOGGER.info("Kill the web server");
webInterface.stop();
webInterface = null;
ConfigureDb.clear();
}
@Test
public static void TestEmpty() throws Exception {
}
}

View File

@@ -1,7 +1,9 @@
package test.kar.karideo; package test.atriasoft.karideo;
import java.io.IOException;
import org.atriasoft.archidata.exception.RESTErrorResponseException;
import org.atriasoft.archidata.tools.ConfigBaseVariable;
import org.atriasoft.archidata.tools.RESTApi;
import org.atriasoft.karideo.api.HealthCheck.HealthResult;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
@@ -10,11 +12,6 @@ import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.kar.archidata.db.DBEntry;
import org.kar.archidata.exception.RESTErrorResponseExeption;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.kar.archidata.tools.RESTApi;
import org.kar.karideo.api.HealthCheck.HealthResult;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -28,43 +25,36 @@ public class TestHealthCheck {
@BeforeAll @BeforeAll
public static void configureWebServer() throws Exception { public static void configureWebServer() throws Exception {
ConfigureDb.configure();
LOGGER.info("configure server ..."); LOGGER.info("configure server ...");
webInterface = new WebLauncherTest(); webInterface = new WebLauncherTest();
LOGGER.info("Create DB");
try {
webInterface.migrateDB();
} catch (final Exception ex) {
ex.printStackTrace();
LOGGER.error("Detect an error: {}", ex.getMessage());
}
LOGGER.info("Start REST (BEGIN)"); LOGGER.info("Start REST (BEGIN)");
webInterface.process(); webInterface.process();
LOGGER.info("Start REST (DONE)"); LOGGER.info("Start REST (DONE)");
api = new RESTApi(ConfigBaseVariable.apiAdress); api = new RESTApi(ConfigBaseVariable.apiAdress);
api.setToken(Common.ADMIN_TOKEN);
} }
@AfterAll @AfterAll
public static void stopWebServer() throws InterruptedException, IOException { public static void stopWebServer() throws Exception {
LOGGER.info("Kill the web server"); LOGGER.info("Kill the web server");
webInterface.stop(); webInterface.stop();
webInterface = null; webInterface = null;
LOGGER.info("Remove the test db"); ConfigureDb.clear();
DBEntry.closeAllForceMode();
ConfigBaseVariable.clearAllValue();
} }
@Order(1) @Order(1)
@Test @Test
// @RepeatedTest(10) // @RepeatedTest(10)
public void checkHealthCheck() throws Exception { public void checkHealthCheck() throws Exception {
final HealthResult result = api.get(HealthResult.class, "health_check"); final HealthResult result = api.request("health_check").get().fetch(HealthResult.class);
Assertions.assertEquals(result.value(), "alive and kicking"); Assertions.assertEquals(result.value(), "alive and kicking");
} }
@Order(2) @Order(2)
@Test @Test
public void checkHealthCheckWrongAPI() throws Exception { public void checkHealthCheckWrongAPI() throws Exception {
Assertions.assertThrows(RESTErrorResponseExeption.class, () -> api.get(HealthResult.class, "health_checks")); Assertions.assertThrows(RESTErrorResponseException.class, () -> api.request("health_check_kaboom").get().fetch());
} }
} }

View File

@@ -0,0 +1,12 @@
package test.atriasoft.karideo;
import org.atriasoft.karideo.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() {}
}

View File

@@ -1,28 +0,0 @@
package test.kar.karideo;
import org.kar.archidata.tools.ConfigBaseVariable;
import org.kar.karideo.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.testMode = "true";
//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";
}
}

1
front/.env Normal file
View File

@@ -0,0 +1 @@
NODE_ENV=development

2
front/.env.production Normal file
View File

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

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

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

View File

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

View File

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

2
front/LICENSE Normal file
View File

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

View File

@@ -1,134 +0,0 @@
{
"$schema" : "./node_modules/@angular/cli/lib/config/schema.json",
"version" : 1,
"newProjectRoot" : "projects",
"defaultProject" : "karideo",
"projects" : {
"karideo" : {
"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" : "karideo:build"
},
"configurations" : {
"production" : {
"browserTarget" : "karideo:build:production"
},
"develop" : {
"browserTarget" : "karideo:build:develop"
}
}
},
"extract-i18n" : {
"builder" : "@angular-devkit/build-angular:extract-i18n",
"options" : {
"browserTarget" : "karideo: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/**" ]
}
}
}
},
"karideo-e2e" : {
"root" : "e2e",
"sourceRoot" : "e2e",
"projectType" : "application",
"architect" : {
"e2e" : {
"builder" : "@angular-devkit/build-angular:protractor",
"options" : {
"protractorConfig" : "./protractor.conf.js",
"devServerTarget" : "karideo: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"
}
}
}

10637
front/config sample.yaml Normal file

File diff suppressed because it is too large Load Diff

14
front/index.html Normal file
View File

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

9
front/knip.ts Normal file
View File

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

21757
front/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,41 +1,89 @@
{ {
"name": "karideo", "name": "karideo",
"version": "0.0.0",
"license": "MPL-2",
"scripts": {
"all": "npm run build && npm run test",
"ng": "ng",
"dev": "ng serve --configuration=develop --watch --port 4202",
"build": "ng build --prod",
"test": "ng test",
"lint": "ng lint",
"style": "prettier --write .",
"e2e": "ng e2e"
},
"private": true, "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": "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": { "dependencies": {
"@angular/animations": "^14.2.10", "react-speech-recognition": "4.0.1",
"@angular/cdk": "^14.2.7", "regenerator-runtime": "0.14.1",
"@angular/common": "^14.2.10", "@locator/babel-jsx": "0.4.4",
"@angular/compiler": "^14.2.10", "@trivago/prettier-plugin-sort-imports": "5.2.2",
"@angular/core": "^14.2.10", "@chakra-ui/cli": "3.27.1",
"@angular/forms": "^14.2.10", "@chakra-ui/react": "3.27.1",
"@angular/material": "^14.2.7", "@emotion/react": "11.14.0",
"@angular/platform-browser": "^14.2.10", "allotment": "1.20.4",
"@angular/platform-browser-dynamic": "^14.2.10", "css-mediaquery": "0.1.2",
"@angular/router": "^14.2.10", "dayjs": "1.11.18",
"rxjs": "^7.5.7", "history": "5.3.0",
"zone.js": "^0.12.0" "next-themes": "^0.4.6",
"react": "19.2.0",
"react-dom": "19.2.0",
"react-error-boundary": "6.0.0",
"react-icons": "5.5.0",
"react-router-dom": "7.9.4",
"react-select": "5.10.2",
"react-use": "17.6.0",
"zod": "4.1.12",
"zustand": "5.0.8"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^14.2.9", "@chakra-ui/styled-system": "^2.12.0",
"@angular-eslint/builder": "14.2.0", "@playwright/test": "1.56.1",
"@angular-eslint/eslint-plugin": "14.2.0", "@storybook/addon-links": "9.1.13",
"@angular-eslint/eslint-plugin-template": "14.2.0", "@storybook/react-vite": "9.1.13",
"@angular-eslint/schematics": "14.2.0", "@testing-library/jest-dom": "6.9.1",
"@angular-eslint/template-parser": "14.2.0", "@testing-library/react": "16.3.0",
"@angular/cli": "^14.2.9", "@testing-library/user-event": "14.6.1",
"@angular/compiler-cli": "^14.2.10", "@trivago/prettier-plugin-sort-imports": "5.2.2",
"@angular/language-service": "^14.2.10" "@types/jest": "30.0.0",
"@types/node": "24.8.1",
"@types/react": "19.2.2",
"@types/react-dom": "19.2.2",
"@typescript-eslint/eslint-plugin": "8.46.1",
"@typescript-eslint/parser": "8.46.1",
"@vitejs/plugin-react": "5.0.4",
"eslint": "9.38.0",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-react": "7.37.5",
"eslint-plugin-react-hooks": "7.0.0",
"eslint-plugin-storybook": "9.1.13",
"jest": "30.2.0",
"jest-environment-jsdom": "30.2.0",
"knip": "5.66.0",
"lint-staged": "16.2.4",
"npm-check-updates": "^19.1.1",
"prettier": "3.6.2",
"puppeteer": "24.25.0",
"react-is": "19.2.0",
"storybook": "9.1.13",
"ts-node": "10.9.2",
"typescript": "5.9.3",
"vite": "7.1.10",
"vitest": "3.2.4"
} }
} }

11995
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

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

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

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

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

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>

View File

@@ -1,19 +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">
Season {{numberSeason}}
</div>
<div class="description-small" *ngIf="count > 1">
{{count}} Episodes
</div>
<div class="description-small" *ngIf="count == 1">
{{count}} Episode
</div>

View File

@@ -1,24 +0,0 @@
<div>
<div class="count-base">
<div class="count" *ngIf="countvideo">
{{countvideo}}
</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,18 +0,0 @@
<div>
<div class="count-base">
<span class="count" *ngIf="countvideo">
{{countvideo}}
</span>
</div>
<div class="imgContainer-small">
<div *ngIf="covers">
<img src="{{covers[0]}}" class="miniature-small"/>
</div>
</div>
<div class="title-small">
{{name}}
</div>
<div class="description-small" *ngIf="description">
{{description}}
</div>
</div>

View File

@@ -1,28 +0,0 @@
<div>
<div class="count-base">
<span class="views" *ngIf="advancement">
{{advancement.count}}
</span>
</div>
<div class="videoImgContainer">
<div *ngIf="covers">
<!--<data-image id="{{cover}}"></data-image>-->
<img src="{{covers[0]}}" />
</div>
<div *ngIf="!covers" class="noImage">
</div>
</div>
<div class="view-progess" [ngStyle]="updateAdvancement()"></div>
<div class="title-small" *ngIf="data">
{{episodeDisplay}} {{name}}
</div>
<div class="title-small" *ngIf="!data">
Error media: {{element?.id}}
</div>
<!--
<div class="description-small" *ngIf="description">
{{description}}
</div>
-->
</div>

View File

@@ -1,5 +0,0 @@
import { Media, isMedia } from "./media";
export {
Media, isMedia,
}

View File

@@ -1,47 +0,0 @@
import { isNumberFinite, isString, isOptionalOf } from "common/utils";
import { isNodeData, NodeData } from "common/model/node";
export interface Media extends NodeData {
dataId?: number;
typeId?: number;
seriesId?: number;
seasonId?: number;
episode?: number;
date?: number;
time?: number;
ageLimit?: number;
};
export function isMedia(data: any): data is Media {
if (!isNodeData(data) as any) {
return false;
}
if (!isOptionalOf(data.dataId, isNumberFinite)) {
return false;
}
if (!isOptionalOf(data.typeId, isNumberFinite)) {
return false;
}
if (!isOptionalOf(data.seriesId, isNumberFinite)) {
return false;
}
if (!isOptionalOf(data.seasonId, isNumberFinite)) {
return false;
}
if (!isOptionalOf(data.episode, isNumberFinite)) {
return false;
}
if (!isOptionalOf(data.date, isNumberFinite)) {
return false;
}
if (!isOptionalOf(data.time, isNumberFinite)) {
return false;
}
if (!isOptionalOf(data.ageLimit, isNumberFinite)) {
return false;
}
return true;
}

View File

@@ -1,37 +0,0 @@
import { isNumberFinite, isString, isOptionalOf, isNumber } from "common/utils";
import { isNodeData, NodeData } from "common/model/node";
export interface UserMediaAdvancement {
id: number;
// Id of the media
mediaId?: number;
// Percent of advancement in the media
percent?: number;
// "Number of second of advancement in the media
time?: number;
// Number of time this media has been read
count?: number;
};
export function isUserMediaAdvancement(data: any): data is UserMediaAdvancement {
if (!isNumberFinite(data.id)) {
return false;
}
if (!isNumberFinite(data.mediaId)) {
return false;
}
if (!isNumber(data.percent)) {
return false;
}
if (!isNumberFinite(data.time)) {
return false;
}
if (!isNumberFinite(data.count)) {
return false;
}
return true;
}

View File

@@ -1,11 +0,0 @@
<div class="generic-page">
<div class="title">
Karideo
</div>
<div class="fill-all colomn_mutiple">
<div *ngFor="let data of dataList" class="item-home" (click)="onSelectType($event, data.id)" (auxclick)="onSelectType($event, data.id)">
<app-element-type [element]="data"></app-element-type>
</div>
<div class="clear"></div>
</div>
</div>

View File

@@ -1,140 +0,0 @@
<div class="main-reduce edit-page">
<div class="title">
Edit season
</div>
<div class="fill-all" *ngIf="itemIsRemoved">
<div class="message-big">
<br/><br/><br/>
The season has been removed
<br/><br/><br/>
</div>
</div>
<div class="fill-all" *ngIf="itemIsNotFound">
<div class="message-big">
<br/><br/><br/>
The season does not exist
<br/><br/><br/>
</div>
</div>
<div class="fill-all" *ngIf="itemIsLoading">
<div class="message-big">
<br/><br/><br/>
Loading ...<br/>
Please wait.
<br/><br/><br/>
</div>
</div>
<div class="fill-all" *ngIf="!itemIsRemoved && !itemIsNotFound && !itemIsLoading">
<div class="request_raw">
<div class="label">
Number:
</div>
<div class="input">
<input type="number"
placeholder="Id of the season"
[value]="numberVal"
(input)="onNumber($event.target.value)"
/>
</div>
</div>
<div class="request_raw">
<div class="label">
Description:
</div>
<div class="input">
<input type="text"
placeholder="Description of the Media"
[value]="description"
(input)="onDescription($event.target.value)"/>
</div>
</div>
<div class="send_value">
<button class="button fill-x color-button-validate color-shadow-black" (click)="sendValues()" type="submit"><i class="material-icons">save_alt</i> Save</button>
</div>
<div class="clear"></div>
</div>
<!-- ------------------------- Cover section --------------------------------- -->
<div class="title" *ngIf="!itemIsRemoved && !itemIsNotFound && !itemIsLoading">
Covers
</div>
<div class="fill-all" *ngIf="!itemIsRemoved && !itemIsNotFound && !itemIsLoading">
<div class="hide-element">
<input type="file"
#fileInput
(change)="onChangeCover($event.target)"
placeholder="Select a cover file"
accept=".png,.jpg,.jpeg,.webp"/>
</div>
<div class="request_raw">
<div class="input">
<div class="cover" *ngFor="let element of coversDisplay">
<div class="cover-image">
<img src="{{element.url}}"/>
</div>
<div class="cover-button">
<button (click)="removeCover(element.id)">
<i class="material-icons button-remove">highlight_off</i>
</button>
</div>
</div>
<div class="cover">
<div class="cover-no-image">
</div>
<div class="cover-button">
<button (click)="fileInput.click()">
<i class="material-icons button-add">add_circle_outline</i>
</button>
</div>
</div>
</div>
</div>
<div class="clear"></div>
</div>
<!-- ------------------------- ADMIN section --------------------------------- -->
<div class="title" *ngIf="!itemIsRemoved && !itemIsNotFound && !itemIsLoading">
Administration
</div>
<div class="fill-all" *ngIf="!itemIsRemoved && !itemIsNotFound && !itemIsLoading">
<div class="request_raw">
<div class="label">
<i class="material-icons">data_usage</i> ID:
</div>
<div class="input">
{{idSeason}}
</div>
</div>
<div class="clear"></div>
<div class="request_raw">
<div class="label">
Videos:
</div>
<div class="input">
{{videoCount}}
</div>
</div>
<div class="clear"></div>
<div class="request_raw">
<div class="label">
<i class="material-icons">delete_forever</i> Trash:
</div>
<div class="input" *ngIf="(videoCount == '0')">
<button class="button color-button-cancel color-shadow-black" (click)="removeItem()" type="submit">
<i class="material-icons">delete</i> Remove season
</button>
</div>
<div class="input" *ngIf="(videoCount != '0')">
<i class="material-icons">new_releases</i> Can not remove season, video depending on it
</div>
</div>
<div class="clear"></div>
</div>
</div>
<upload-progress [mediaTitle]="upload.labelMediaTitle"
[mediaUploaded]="upload.mediaSendSize"
[mediaSize]="upload.mediaSize"
[result]="upload.result"
[error]="upload.error"></upload-progress>
<delete-confirm
[comment]="confirmDeleteComment"
[imageUrl]=confirmDeleteImageUrl
(callback)="deleteConfirmed()"></delete-confirm>

View File

@@ -1,29 +0,0 @@
<div class="generic-page">
<div class="fill-title colomn_mutiple">
<div class="cover-area">
<div class="cover" *ngIf="cover != null" >
<img src="{{cover}}"/>
</div>
</div>
<div [className]="cover != null ? 'description-area description-area-cover' : 'description-area description-area-no-cover'">
<div *ngIf="seriesName" class="title">
{{seriesName}}
</div>
<div class="sub-title-main">
Season {{name}}
</div>
<div class="description" *ngIf="description">
{{description}}
</div>
</div>
</div>
<div class="fill-content colomn_mutiple">
<div class="clear"></div>
<div class="title" *ngIf="videos.length > 1">Videos:</div>
<div class="title" *ngIf="videos.length == 1">Video:</div>
<div *ngFor="let data of videos" class="item item-video" (click)="onSelectVideo($event, data.id)" (auxclick)="onSelectVideo($event, data.id)">
<app-element-video [element]="data"></app-element-video>
</div>
<div class="clear"></div>
</div>
</div>

View File

@@ -1,162 +0,0 @@
<div class="main-reduce edit-page">
<div class="title">
Edit series
</div>
<div class="fill-all" *ngIf="itemIsRemoved">
<div class="message-big">
<br/><br/><br/>
The series has been removed
<br/><br/><br/>
</div>
</div>
<div class="fill-all" *ngIf="itemIsNotFound">
<div class="message-big">
<br/><br/><br/>
The series does not exist
<br/><br/><br/>
</div>
</div>
<div class="fill-all" *ngIf="itemIsLoading">
<div class="message-big">
<br/><br/><br/>
Loading ...<br/>
Please wait.
<br/><br/><br/>
</div>
</div>
<div class="fill-all" *ngIf="!itemIsRemoved && !itemIsNotFound && !itemIsLoading">
<div class="request_raw">
<div class="label">
Type:
</div>
<div class="input">
<select [ngModel]="typeId"
(ngModelChange)="onChangeType($event)">
<option *ngFor="let element of listType" [ngValue]="element.value">{{element.label}}</option>
</select>
</div>
</div>
<div class="request_raw">
<div class="label">
Name:
</div>
<div class="input">
<input type="text"
placeholder="Name of the Series"
[value]="name"
(input)="onName($event.target.value)"
/>
</div>
</div>
<div class="request_raw">
<div class="label">
Description:
</div>
<div class="input">
<input type="text"
placeholder="Description of the Media"
[value]="description"
(input)="onDescription($event.target.value)"/>
</div>
</div>
<div class="clear"></div>
<div class="send_value">
<button class="button fill-x color-button-validate color-shadow-black" (click)="sendValues()" type="submit"><i class="material-icons">save_alt</i> Save</button>
</div>
<div class="clear"></div>
</div>
<!-- ------------------------- Cover section --------------------------------- -->
<div class="title" *ngIf="!itemIsRemoved && !itemIsNotFound && !itemIsLoading">
Covers
</div>
<div class="fill-all" *ngIf="!itemIsRemoved && !itemIsNotFound && !itemIsLoading">
<div class="hide-element">
<input type="file"
#fileInput
(change)="onChangeCover($event.target)"
placeholder="Select a cover file"
accept=".png,.jpg,.jpeg,.webp"/>
</div>
<div class="request_raw">
<div class="input">
<div class="cover" *ngFor="let element of coversDisplay">
<div class="cover-image">
<img src="{{element.url}}"/>
</div>
<div class="cover-button">
<button (click)="removeCover(element.id)">
<i class="material-icons button-remove">highlight_off</i>
</button>
</div>
</div>
<div class="cover">
<div class="cover-no-image">
</div>
<div class="cover-button">
<button (click)="fileInput.click()">
<i class="material-icons button-add">add_circle_outline</i>
</button>
</div>
</div>
</div>
</div>
<div class="clear"></div>
</div>
<!-- ------------------------- ADMIN section --------------------------------- -->
<div class="title" *ngIf="!itemIsRemoved && !itemIsNotFound && !itemIsLoading">
Administration
</div>
<div class="fill-all" *ngIf="!itemIsRemoved && !itemIsNotFound && !itemIsLoading">
<div class="request_raw">
<div class="label">
<i class="material-icons">data_usage</i> ID:
</div>
<div class="input">
{{idSeries}}
</div>
</div>
<div class="clear"></div>
<div class="request_raw">
<div class="label">
Seasons:
</div>
<div class="input">
{{seasonsCount}}
</div>
</div>
<div class="clear"></div>
<div class="request_raw">
<div class="label">
Videos:
</div>
<div class="input">
{{videoCount}}
</div>
</div>
<div class="clear"></div>
<div class="request_raw">
<div class="label">
<i class="material-icons">delete_forever</i> Trash:
</div>
<div class="input" *ngIf="(videoCount == '0' && seasonsCount == '0')">
<button class="button color-button-cancel color-shadow-black" (click)="removeItem()" type="submit">
<i class="material-icons">delete</i> Remove Series
</button>
</div>
<div class="input" *ngIf="(videoCount != '0' || seasonsCount != '0')">
<i class="material-icons">new_releases</i> Can not remove season or video depending on it
</div>
</div>
<div class="clear"></div>
</div>
</div>
<upload-progress [mediaTitle]="upload.labelMediaTitle"
[mediaUploaded]="upload.mediaSendSize"
[mediaSize]="upload.mediaSize"
[result]="upload.result"
[error]="upload.error"></upload-progress>
<delete-confirm
[comment]="confirmDeleteComment"
[imageUrl]=confirmDeleteImageUrl
(callback)="deleteConfirmed()"></delete-confirm>

View File

@@ -1,34 +0,0 @@
<div class="generic-page">
<div class="fill-title colomn_mutiple">
<div class="cover-area">
<div class="cover" *ngIf="cover != null" >
<img src="{{cover}}"/>
</div>
</div>
<div [className]="cover != null ? 'description-area description-area-cover' : 'description-area description-area-no-cover'">
<div class="title">
{{name}}
</div>
<div class="description" *ngIf="description">
{{description}}
</div>
</div>
</div>
<div class="fill-content colomn_mutiple" *ngIf="seasons.length != 0">
<div class="clear"></div>
<div class="title" *ngIf="seasons.length > 1">Seasons:</div>
<div class="title" *ngIf="seasons.length == 1">Season:</div>
<div *ngFor="let data of seasons" class="item-list" (click)="onSelectSeason($event, data.id)" (auxclick)="onSelectSeason($event, data.id)">
<app-element-season [element]="data"></app-element-season>
</div>
</div>
<div class="fill-content colomn_mutiple" *ngIf="videos.length != 0">
<div class="clear"></div>
<div class="title" *ngIf="videos.length > 1">Videos:</div>
<div class="title" *ngIf="videos.length == 1">Video:</div>
<div *ngFor="let data of videos" class="item item-video" (click)="onSelectVideo($event, data.id)" (auxclick)="onSelectVideo($event, data.id)">
<app-element-video [element]="data"></app-element-video>
</div>
</div>
<div class="clear"></div>
</div>

View File

@@ -1,30 +0,0 @@
<div class="generic-page">
<div class="fill-title colomn_mutiple">
<div class="cover-area">
<div class="cover" *ngIf="cover != null" >
<img src="{{cover}}"/>
</div>
</div>
<div [className]="cover != null ? 'description-area description-area-cover' : 'description-area description-area-no-cover'">
<div class="title">
{{name}}
</div>
<div class="description" *ngIf="description">
{{description}}
</div>
</div>
</div>
<div class="fill-content colomn_mutiple">
<div class="clear"></div>
<div *ngFor="let data of series" class="item" (click)="onSelectSeries($event, data.id)" (auxclick)="onSelectSeries($event, data.id)">
<app-element-series [element]="data"></app-element-series>
</div>
</div>
<div class="fill-content colomn_mutiple">
<div class="clear"></div>
<div *ngFor="let data of videos" class="item item-video" (click)="onSelectVideo($event, data.id)" (auxclick)="onSelectVideo($event, data.id)">
<app-element-video [element]="data"></app-element-video>
</div>
</div>
<div class="clear"></div>
</div>

View File

@@ -1,216 +0,0 @@
<div class="generic-page">
<div class="title">
Upload Media
</div>
<div class="clear"><br/></div>
<div class="request_raw_table">
<table>
<colgroup>
<col style="width:10%">
<col style="width:80%">
</colgroup>
<tbody>
<tr>
<td class="left-colomn">format:</td>
<td class="right-colomn">
The format of the media permit to automatic find meta-data:<br/>
Univers:Series name-sXX-eXX-my name of my media.mkv<br/>
<b>example:</b> Stargate:SG1-s55-e22-Asgard.mkv <br/>
</td>
</tr>
<tr>
<td class="left-colomn">Media:</td>
<td class="right-colomn">
<input type="file"
(change)="onChangeFile($event.target)"
placeholder="Select a media file"
accept=".mkv,.webm"
width="90%"
multiple/>
</td>
</tr>
</tbody>
</table>
</div>
<div *ngIf="this.parsedElement.length !== 0" class="title">
Meta-data:
</div>
<div class="clear"><br/></div>
<div *ngIf="this.parsedElement.length !== 0" class="fill-all">
<div class="request_raw_table">
<table>
<colgroup>
<col style="width:10%">
<col style="width:70%">
<col style="width:10%">
</colgroup>
<tbody>
<tr>
<td class="left-colomn">Type:</td>
<td class="right-colomn">
<select [ngModel]="typeId"
(ngModelChange)="onChangeType($event)"
[class.error]="typeId === undefined">
<option *ngFor="let element of listType" [ngValue]="element.value">{{element.label}}</option>
</select>
</td>
</tr>
<tr>
<td class="left-colomn">Series:</td>
<td class="right-colomn">
<input type="text"
placeholder="Series of the Media"
[value]="globalSeries"
(input)="onSeries($event.target.value)"
/>
</td>
</tr>
<tr>
<td class="left-colomn"></td>
<td class="right-colomn">
<select [ngModel]="seriesId"
(ngModelChange)="onChangeSeries($event)">
<option *ngFor="let element of listSeries" [ngValue]="element.value">{{element.label}}</option>
</select>
</td>
<td class="tool-colomn">
<!--
<button class="button color-button-normal color-shadow-black" (click)="newSeries()" type="submit">
<i class="material-icons">add_circle_outline</i>
</button>
-->
</td>
</tr>
<tr>
<td class="left-colomn">Season:</td>
<td class="right-colomn">
<input type="number"
pattern="[0-9]{0-4}"
placeholder="season of the Media"
[value]="globalSeason"
(input)="onSeason($event.target.value)"
/>
</td>
</tr>
</tbody>
</table>
</div>
<div class="clear"></div>
<div class="request_raw_table">
<table>
<colgroup>
<col style="width:10%">
<col style="width:70%">
<col style="width:10%">
</colgroup>
<thead>
<tr>
<th>Episode ID:</th>
<th>Episode Title:</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let data of this.parsedElement">
<td class="left-colomn">
<input type="number"
pattern="[0-9]{0-4}"
placeholder="e?"
[value]="data.episode"
(input)="onEpisode(data, $event.target.value)"
[class.error]="data.episodeDetected === true"
/>
</td>
<td class="right-colomn" >
<input type="text"
placeholder="Name of the Media"
[value]="data.title"
(input)="onTitle(data, $event.target.value)"
[class.error]="data.title === ''"
/>
<span *ngIf="data.nameDetected === true" class="error">
^^^This title already exist !!!
</span>
</td>
<td class="tool-colomn" >
<button class="button color-button-cancel color-shadow-black"
(click)="removeElmentFromList(data, $event.target.value)"
type="submit"
alt="Delete">
<i class="material-icons">delete</i>
</button>
</td>
</tr>
</tbody>
</table>
</div>
<div class="clear"></div>
<div class="send_value">
<button class="button fill-x color-button-validate color-shadow-black"
[disabled]="!needSend"
(click)="sendFile()"
type="submit">
<i class="material-icons">cloud_upload</i> Upload
</button>
</div>
<div class="clear"></div>
<div class="request_raw_table" *ngIf="this.listFileInBdd !== undefined">
<table>
<colgroup>
<col style="width:10%">
<col style="width:70%">
<col style="width:10%">
</colgroup>
<thead>
<tr>
<th>Episode ID:</th>
<th>Episode Title:</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let data of this.listFileInBdd">
<td class="left-colomn" [class.error]="data.episodeDetected === true">{{data.episode}}</td>
<td class="right-colomn" [class.error]="data.nameDetected === true">{{data.name}}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div *ngIf="this.parsedElement.length !== 0" class="fill-all">
<div class="request_raw_table">
<table>
<colgroup>
<col style="width:10%">
<col style="width:80%">
</colgroup>
<tbody>
<!-- no need
<tr *ngFor="let data of this.parsedElement">
<td class="left-colomn">Keep:</td>
<td class="right-colomn">
{{data.file.name}}
</td>
</tr>
-->
<tr *ngFor="let data of this.parsedFailedElement">
<td class="left-colomn">Rejected:</td>
<td class="right-colomn">
{{data.file.name}}<br/> ==&gt; {{data.reason}}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<upload-progress [mediaTitle]="upload.labelMediaTitle"
[mediaUploaded]="upload.mediaSendSize"
[mediaSize]="upload.mediaSize"
[result]="upload.result"
[error]="upload.error"></upload-progress>
<!--
TODO: add a pop-in with:
- upload done
- upload error
-->

View File

@@ -1,230 +0,0 @@
<div class="main-reduce edit-page">
<div class="title">
Edit Media
</div>
<div class="fill-all" *ngIf="itemIsRemoved">
<div class="message-big">
<br/><br/><br/>
The media has been removed
<br/><br/><br/>
</div>
</div>
<div class="fill-all" *ngIf="itemIsNotFound">
<div class="message-big">
<br/><br/><br/>
The media does not exist
<br/><br/><br/>
</div>
</div>
<div class="fill-all" *ngIf="itemIsLoading">
<div class="message-big">
<br/><br/><br/>
Loading ...<br/>
Please wait.
<br/><br/><br/>
</div>
</div>
<div class="fill-all" *ngIf="!itemIsRemoved && !itemIsNotFound && !itemIsLoading">
<div class="request_raw">
<div class="label">
Title:
</div>
<div class="input">
<input type="text"
placeholder="Name of the Media"
[value]="data.name"
(input)="onName($event.target.value)"
/>
</div>
</div>
<div class="request_raw2">
<div class="label">
<i class="material-icons">description</i> Description:
</div>
<div class="input">
<textarea (input)="onDescription($event.target.value)" placeholder="Description of the Media" rows=6>{{data.description}}</textarea>
<!--<input type="text"
placeholder="Description of the Media"
[value]="data.description"
(input)="onDescription($event.target.value)"/>-->
</div>
</div>
<div class="request_raw">
<div class="label">
<i class="material-icons">date_range</i> Date:
</div>
<div class="input">
<input type="number"
pattern="[0-9]{0-4}"
placeholder="2112"
[value]="data.time"
(input)="onDate($event.target)"/>
</div>
</div>
<div class="request_raw">
<div class="label">
Type:
</div>
<div class="input">
<select [ngModel]="data.typeId"
(ngModelChange)="onChangeType($event)">
<option *ngFor="let element of listType" [ngValue]="element.value">{{element.label}}</option>
</select>
</div>
<div class="input_add">
<button class="button color-button-normal color-shadow-black" (click)="newType()" type="submit">
<i class="material-icons">add_circle_outline</i>
</button>
</div>
</div>
<div class="request_raw">
<div class="label">
Series:
</div>
<div class="input">
<select [ngModel]="data.seriesId"
(ngModelChange)="onChangeSeries($event)">
<option *ngFor="let element of listSeries" [ngValue]="element.value">{{element.label}}</option>
</select>
</div>
<div class="input_add">
<button class="button color-button-normal color-shadow-black" (click)="newSeries()" type="submit">
<i class="material-icons">add_circle_outline</i>
</button>
</div>
</div>
<div class="request_raw">
<div class="label">
Season:
</div>
<div class="input">
<select [ngModel]="data.seasonId"
(ngModelChange)="onChangeSeason($event)">
<option *ngFor="let element of listSeason" [ngValue]="element.value">{{element.label}}</option>
</select>
</div>
<div class="input_add">
<button class="button color-button-normal color-shadow-black" (click)="newSeason()" type="submit">
<i class="material-icons">add_circle_outline</i>
</button>
</div>
</div>
<div class="request_raw">
<div class="label">
Episode:
</div>
<div class="input">
<input type="number"
pattern="[0-9]{0-4}"
placeholder="5"
[value]="data.episode"
(input)="onEpisode($event.target)"/>
</div>
</div>
<div class="send_value">
<button class="button fill-x color-button-validate color-shadow-black" [disabled]="!needSend" (click)="sendValues()" type="submit"><i class="material-icons">save_alt</i> Save</button>
</div>
<div class="clear"></div>
</div>
<!-- ------------------------- Cover section --------------------------------- -->
<div class="title" *ngIf="!itemIsRemoved && !itemIsNotFound && !itemIsLoading">
Covers
</div>
<div class="fill-all" *ngIf="!itemIsRemoved && !itemIsNotFound && !itemIsLoading">
<div class="hide-element">
<input type="file"
#fileInput
(change)="onChangeCover($event.target)"
placeholder="Select a cover file"
accept=".png,.jpg,.jpeg,.webp"/>
</div>
<div class="request_raw">
<div class="input">
<div class="cover" *ngFor="let element of coversDisplay">
<div class="cover-image">
<img src="{{element.url}}"/>
</div>
<div class="cover-button">
<button (click)="removeCover(element.id)">
<i class="material-icons button-remove">highlight_off</i>
</button>
</div>
</div>
<div class="cover">
<div class="cover-no-image">
</div>
<div class="cover-button">
<button (click)="fileInput.click()">
<i class="material-icons button-add">add_circle_outline</i>
</button>
</div>
</div>
</div>
</div>
<div class="clear"></div>
</div>
<!-- ------------------------- ADMIN section --------------------------------- -->
<div class="title" *ngIf="!itemIsRemoved && !itemIsNotFound && !itemIsLoading">
Administration
</div>
<div class="fill-all" *ngIf="!itemIsRemoved && !itemIsNotFound && !itemIsLoading">
<div class="request_raw">
<div class="label">
<i class="material-icons">data_usage</i> ID:
</div>
<div class="input">
{{data.dataId}}
</div>
</div>
<div class="clear"></div>
<div class="request_raw">
<div class="label">
<i class="material-icons">delete_forever</i> Trash:
</div>
<div class="input">
<button class="button color-button-cancel color-shadow-black" (click)="removeItem()" type="submit">
<i class="material-icons">delete</i> Remove Media
</button>
</div>
</div>
<div class="clear"></div>
</div>
</div>
<create-type ></create-type>
<upload-progress [mediaTitle]="upload.labelMediaTitle"
[mediaUploaded]="upload.mediaSendSize"
[mediaSize]="upload.mediaSize"
[result]="upload.result"
[error]="upload.error"></upload-progress>
<delete-confirm
[comment]="confirmDeleteComment"
[imageUrl]=confirmDeleteImageUrl
(callback)="deleteConfirmed()"></delete-confirm>
<app-popin id="popin-new-season"
popTitle="Create a new season"
closeTopRight="true"
closeTitle="Cancel"
validateTitle="Create"
(callback)="eventPopUpSeason($event[0])">
<p>
Name: <!-- <input type="text" [(ngModel)]="bodyText" /> -->
</p>
</app-popin>
<app-popin id="popin-new-series"
popSize="small"
popTitle="Create a new series"
closeTopRight="true"
closeTitle="Cancel"
validateTitle="Create"
(callback)="eventPopUpSeries($event[0])">
<p>
Name: <!-- <input type="text" [(ngModel)]="bodyText" /> -->
</p>
</app-popin>

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