124 Commits

Author SHA1 Message Date
f33b55b1c8 Fix left side menu. (#13)
All checks were successful
Build and Deploy Java Gradle Application / build-and-deploy (push) Successful in 1m16s
2026-02-03 16:14:43 +01:00
e2fd4fb29a fixing-ci-angular-21 (#12)
All checks were successful
Build and Deploy Java Gradle Application / build-and-deploy (push) Successful in 1m18s
Co-authored-by: Florian THIERRY <florian.thierry@actian.com>
Reviewed-on: #12
2026-02-03 15:59:33 +01:00
0cce8b2982 Fixing Angular 21 by migrating all values by signals. (#11)
Some checks failed
Build and Deploy Java Gradle Application / build-and-deploy (push) Failing after 53s
2026-02-03 15:07:55 +01:00
1ca2f872f7 Update dependencies - spring boot 4 and angular 21 (#10)
All checks were successful
Build and Deploy Java Gradle Application / build-and-deploy (push) Successful in 1m39s
Co-authored-by: Florian THIERRY
Reviewed-on: #10
2025-12-30 17:45:03 +01:00
03dd52de71 gitea-actions (#8)
All checks were successful
Build and Deploy Java Gradle Application / build-and-deploy (push) Successful in 1m36s
Co-authored-by: Florian THIERRY <florian.thierry@protonmail.com>
Reviewed-on: #8
2025-07-23 22:59:51 +02:00
Florian THIERRY
859273a9c1 Change backend ip address after freebox migration. 2025-07-23 20:48:00 +02:00
653536b0da Merge pull request 'design-system' (#7) from design-system into main
Reviewed-on: #7
2024-10-22 08:36:07 +02:00
Florian THIERRY
5fca5bde55 Rename cod-btn into cod-button. 2024-10-22 08:32:46 +02:00
Florian THIERRY
d36fd17690 Add i18n. 2024-10-22 08:23:27 +02:00
Florian THIERRY
295f977a21 Add icon button style. 2024-10-22 08:17:07 +02:00
Florian THIERRY
1a00d0cd19 Add missing ripple. 2024-10-22 08:17:07 +02:00
Florian THIERRY
2da6a58b20 Change version number. 2024-10-22 08:17:07 +02:00
Florian THIERRY
0d2883fe2a Code cleanning. 2024-10-22 08:17:07 +02:00
Florian THIERRY
7f99d11209 Add ripple everywhere. 2024-10-22 08:17:07 +02:00
Florian THIERRY
053ac89e3c Misc changes about form inputs. 2024-10-22 08:17:07 +02:00
Florian THIERRY
cfca22bf66 Factorization of input styling. 2024-10-22 08:17:07 +02:00
Florian THIERRY
7c5cc38cff Initiate design system and refactor login page buttons. 2024-10-22 08:17:07 +02:00
Florian THIERRY
dae0a4b78d Fix field edition problem. 2024-10-18 10:37:31 +02:00
Florian THIERRY
882ffe7094 Several changes about token refreshing. 2024-10-15 16:11:46 +02:00
Florian THIERRY
136771ab60 Add i18n missing translation. 2024-10-15 09:35:14 +02:00
3865c26397 Merge pull request 'Add a guard to deny access for some routes.' (#6) from already-auth-guard into main
Add a guard to deny access for some routes.
2024-10-15 08:26:45 +02:00
Florian THIERRY
26a217cd50 Add a guard to deny access for some routes. 2024-10-15 08:25:49 +02:00
ff52a198dc Upgrade to angular 18. 2024-09-24 22:47:15 +02:00
Florian THIERRY
f3d59a0ef3 conf cleaning. 2024-09-22 13:18:08 +02:00
Florian THIERRY
d84485e52b test 2024-09-22 13:03:39 +02:00
Florian THIERRY
f789d89995 test 2024-09-22 12:53:55 +02:00
Florian THIERRY
7e0174bcc2 test 2024-09-22 12:46:41 +02:00
Florian THIERRY
fe1d59a3bb test 2024-09-22 12:37:01 +02:00
Florian THIERRY
69a99c9312 test 2024-09-22 12:34:02 +02:00
Florian THIERRY
a6414ae64d test 2024-09-22 12:30:18 +02:00
Florian THIERRY
9cf47f0e2a test 2024-09-22 12:27:59 +02:00
Florian THIERRY
6c89562dc3 test 2024-09-22 12:14:04 +02:00
Florian THIERRY
e85eabbed5 test 2024-09-22 12:01:05 +02:00
Florian THIERRY
1ec4ba8212 test conf prod. 2024-09-22 11:10:01 +02:00
Florian THIERRY
a1ff181443 test conf prod. 2024-09-22 11:09:14 +02:00
Florian THIERRY
ee8f48bc43 Fix prod fr configuration. 2024-09-22 10:55:49 +02:00
Florian THIERRY
7ec1aee884 test 2024-09-21 23:39:46 +02:00
Florian THIERRY
a3adfa8ee0 Fix bug of preview content. 2024-09-21 22:20:00 +02:00
Florian THIERRY
d893afa1f3 i18n 2024-09-21 21:43:09 +02:00
Florian THIERRY
d984128176 i18n 2024-09-21 21:40:49 +02:00
Florian THIERRY
f8d73c9ed0 i18n 2024-09-21 21:34:16 +02:00
Florian THIERRY
208b935ffa i18n for some components. 2024-09-21 21:17:31 +02:00
Florian THIERRY
f12dfc7029 i18n for files in core package. 2024-09-21 21:09:36 +02:00
Florian THIERRY
98a890e915 i18n for signin page. 2024-09-21 21:06:47 +02:00
Florian THIERRY
0c1b52d734 i18n for publication search page. 2024-09-21 21:03:34 +02:00
Florian THIERRY
3f6764dd7d i18n for publication update page. 2024-09-21 21:00:59 +02:00
Florian THIERRY
67c3d0b3e6 i18n of publication creation page. 2024-09-21 20:57:05 +02:00
Florian THIERRY
36208ef071 i18n of publication page. 2024-09-21 12:28:55 +02:00
Florian THIERRY
b546a0cf01 Add i18n completion tool. 2024-09-21 12:20:22 +02:00
Florian THIERRY
3935f6ad21 i18n for login page. 2024-09-21 11:22:35 +02:00
Florian THIERRY
fd5ad7e88e i18n for disconnection page. 2024-09-21 11:13:08 +02:00
Florian THIERRY
8c957fe694 i18n for home page. 2024-09-21 11:12:07 +02:00
Florian THIERRY
cb0ef7ddd5 Test 2024-09-20 18:38:17 +02:00
Florian THIERRY
42e466fe8b test ci. 2024-09-20 17:32:43 +02:00
Florian THIERRY
2c5fa4fa13 test ci. 2024-09-20 17:25:37 +02:00
Florian THIERRY
ebce44c889 test ci 2024-09-20 17:12:13 +02:00
Florian THIERRY
955dc48f51 test ci 2024-09-20 16:46:11 +02:00
Florian THIERRY
29e75e6298 Test ci. 2024-09-20 16:34:29 +02:00
Florian THIERRY
187fd105d3 Fix pictures paths. 2024-09-20 09:36:10 +02:00
Florian THIERRY
1b92fd269e Fix config. 2024-09-19 22:24:39 +02:00
Florian THIERRY
23025e3606 Fix fucking MIME types of javascript files. 2024-09-19 22:13:10 +02:00
Florian THIERRY
38c11e2d9f gitignore. 2024-09-19 18:03:33 +02:00
Florian THIERRY
610723c561 CI/CD drafting. 2024-09-19 18:03:23 +02:00
Florian THIERRY
0e2fb945a4 Initialize docker build configuration. 2024-09-18 10:18:40 +02:00
Florian THIERRY
ef32572521 Change favicon. 2024-09-18 09:22:11 +02:00
Florian THIERRY
e5a128a7f6 Fix style. 2024-09-18 09:10:46 +02:00
Florian THIERRY
f5e1e10ebd Add confirmation dialog to delete publications. 2024-09-18 09:05:18 +02:00
Florian THIERRY
500952d4d4 Implementation of categoryId edition. 2024-09-10 10:47:58 +02:00
Florian THIERRY
1cc4abc24e Add default picture and factorization of title in publication edition. 2024-09-10 09:40:45 +02:00
Florian THIERRY
00945de270 Code cleaning about publication edition component. 2024-09-06 10:20:05 +02:00
Florian THIERRY
5804d8cc9f Extract publication edition into a separated component. 2024-09-05 17:28:30 +02:00
Florian THIERRY
5610bd170a Minor changes 2024-09-05 09:35:46 +02:00
Florian THIERRY
21d19d4ecd Styling elements. 2024-09-05 09:33:07 +02:00
Florian THIERRY
f3dfac6bc7 Add delete button. 2024-09-05 09:23:25 +02:00
Florian THIERRY
4565192d0b Remote temp configuration. 2024-09-04 14:20:31 +02:00
Florian THIERRY
d7ac4966c9 Add "new publication" button. 2024-09-04 14:19:26 +02:00
Florian THIERRY
64119a956a Add "my-publications" page. 2024-09-04 14:06:10 +02:00
Florian THIERRY
c4dea2cc85 Dependency cleaning. 2024-09-04 10:28:22 +02:00
Florian THIERRY
b0cc42fddd Implementaiton of preview tab. 2024-09-04 10:26:47 +02:00
Florian THIERRY
db669114b2 Add Prism.js. 2024-09-04 09:29:10 +02:00
Florian THIERRY
ca6b207816 Fix refresh token mecanism. 2024-09-04 08:58:47 +02:00
Florian THIERRY
b091dc52b7 Fix authentication errors handling. 2024-09-03 11:45:47 +02:00
Florian THIERRY
4d44b6f53c First implementation of refresh token. 2024-09-03 10:37:58 +02:00
Florian THIERRY
be34c555a5 Implementation of code-block addition in the publication. 2024-09-03 10:07:13 +02:00
Florian THIERRY
c03d977028 Add code-block dialog and style it. 2024-09-03 10:00:45 +02:00
Florian THIERRY
afd184f936 Fix author-id publications search. 2024-08-30 22:37:03 +02:00
Florian THIERRY
c09c68e1ac Implementation of picture addition button in editor. 2024-08-30 18:13:43 +02:00
Florian THIERRY
b84ba15f4c Implementaiton of h1, h2, h3 and link button on publication editor. 2024-08-30 18:01:29 +02:00
Florian THIERRY
51af25666d Fix search bar navigation. 2024-08-30 09:59:21 +02:00
Florian THIERRY
b3a52f6a4b Extract search bar into a standalone component and fix header design. 2024-08-30 09:52:00 +02:00
Florian THIERRY
090143fdae Add search publication page and fix category access 2024-08-29 17:24:01 +02:00
Florian THIERRY
b5f881e2c5 Add a component to display a publication list and fix publication search rest service to handle ids. 2024-08-29 13:56:14 +02:00
Florian THIERRY
d9b856bd43 Fix categories loading in side menu component. 2024-08-29 11:09:39 +02:00
Florian THIERRY
5e4068b141 Add style and help button. 2024-08-29 10:20:31 +02:00
Florian THIERRY
a2f1b511c1 Add buttons in publication editor. 2024-08-25 22:40:11 +02:00
Florian THIERRY
4d20d5f8b8 Add button to edit publications on publication view page. 2024-08-25 12:02:52 +02:00
Florian THIERRY
f00fb103ba Add submit button component and style it. 2024-08-22 10:26:38 +02:00
Florian THIERRY
b1d9344574 Styling picture selection components. 2024-08-19 23:02:39 +02:00
Florian THIERRY
56ac024cba Mess commit. 2024-08-19 22:42:42 +02:00
Florian THIERRY
32ab1d79c8 Add publication edition form. 2024-08-04 15:54:00 +02:00
Florian THIERRY
42c4f76c0d Design some buttons in header. 2024-06-28 09:35:56 +02:00
Florian THIERRY
4cc2a15231 Responsive styling of header. 2024-06-12 13:36:16 +02:00
Florian THIERRY
54fbc7d609 Adding css transitions. 2024-06-12 13:22:20 +02:00
Florian THIERRY
8e9440a104 Responsive styling of publication page. 2024-06-12 13:19:08 +02:00
Florian THIERRY
00d49d5fa4 Responsive styling of home page. 2024-06-12 13:15:03 +02:00
Florian THIERRY
78325c8729 Fix footer at page bottom. 2024-06-12 12:38:42 +02:00
Florian THIERRY
1e18e3bc52 Add signin page. 2024-06-11 12:55:11 +02:00
Florian THIERRY
8ada2a15ef Styling login form. 2024-06-07 09:44:51 +02:00
Florian THIERRY
95d5308934 Re-design of login page. 2024-06-06 11:05:02 +02:00
Florian THIERRY
e5076f0c64 Design header search bar. 2024-06-05 10:23:38 +02:00
Florian THIERRY
2ba707c336 Fix home page design. 2024-06-05 10:05:02 +02:00
Florian THIERRY
d3041cf03d Styling publications on home page. 2024-06-04 13:55:26 +02:00
Florian THIERRY
58295398e0 Add endpoint to retrieve latest publications. 2024-06-04 13:06:20 +02:00
Florian THIERRY
067bf7885a Update side menu header. 2024-06-04 12:55:46 +02:00
Florian THIERRY
d324b94ddb Add SQL script to rebuild categories hierarchy. 2024-04-22 16:05:59 +02:00
Florian THIERRY
4985889c58 Styling header. 2024-04-22 16:05:35 +02:00
Florian THIERRY
7f5d52dce5 Add side menu for header. 2024-04-22 15:57:22 +02:00
Florian THIERRY
fae709a254 Fix class not found error. 2024-04-22 14:37:20 +02:00
Florian THIERRY
45355f6c42 Refactor publication parser location. 2024-04-22 14:22:36 +02:00
Florian THIERRY
db492b6316 Add parsed text to publication entities. 2024-04-22 14:13:17 +02:00
Florian THIERRY
c54e1c57d7 Creation of side-menu. 2024-04-02 16:18:03 +02:00
Florian THIERRY
0900df463a Add disconnection and minor improvements on login page. 2024-03-27 12:15:41 +01:00
Florian THIERRY
13c2cc8118 Add frontend. 2024-03-27 10:28:33 +01:00
Florian THIERRY
431d365d20 Move backend files into a sub folder. 2024-03-27 10:28:22 +01:00
277 changed files with 18779 additions and 256 deletions

View File

@@ -0,0 +1,75 @@
name: Build and Deploy Java Gradle Application
on:
push:
branches:
- main
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: 📄 Checkout code
uses: actions/checkout@v4
- name: 📄 Checkout configuration
env:
SSH_PRIVATE_KEY: ${{ secrets.PROD_PROPERTIES_SSH_KEY }}
run: |
mkdir -p ~/.ssh
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
git clone -b hexagonal-reforged ssh://gitea@gitea.takiguchi.ovh:12960/Codiki/codiki-properties.git --config core.sshCommand='ssh -o StrictHostKeyChecking=no'
- name: 📄 Edit configuration
run: |
sed -i "s/<POSTGRES_PASSWORD>/$(cat ./codiki-properties/passwords/postgresql/codiki_user)/g" ./codiki-properties/application-prod.yml
cp ./codiki-properties/application-prod.yml ./backend/codiki-launcher/src/main/resources/application.yml
sed -i "s/<POSTGRES_ADMIN_PASSWORD>/$(cat ./codiki-properties/passwords/postgresql/codiki_admin)/g" ./docker-compose.yml
- name: 🔨 Build backend docker image
run: |
sudo /usr/bin/docker build -t codiki-backend -f ./Dockerfile-backend . --no-cache
- name: 📦 Extract backend docker image into archive
run: |
sudo /usr/bin/docker save codiki-backend:latest -o ./codiki-backend.tar
- name: 🔨 Build frontend docker image
run: |
sudo /usr/bin/docker build -t codiki-frontend -f ./Dockerfile-frontend . --no-cache
- name: 📦 Extract frontend docker image into archive
run: |
sudo /usr/bin/docker save codiki-frontend:latest -o ./codiki-frontend.tar
- name: 📤 Transfer artifacts to remote server
env:
SSH_PRIVATE_KEY: ${{ secrets.PROD_SSH_PRIVATE_KEY }}
REMOTE_USER: ${{ secrets.PROD_REMOTE_USER }}
REMOTE_HOST: ${{ secrets.PROD_REMOTE_HOST }}
REMOTE_PORT: ${{ secrets.PROD_REMOTE_PORT }}
REMOTE_PATH: ${{ secrets.PROD_REMOTE_PATH }}
run: |
mkdir -p ~/.ssh
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
scp -o StrictHostKeyChecking=no -P $REMOTE_PORT ./codiki-backend.tar $REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH
scp -o StrictHostKeyChecking=no -P $REMOTE_PORT ./codiki-frontend.tar $REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH
scp -o StrictHostKeyChecking=no -P $REMOTE_PORT ./docker-compose.yml $REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH
- name: 🚀 Launch application onto remote server
env:
SSH_PRIVATE_KEY: ${{ secrets.PROD_SSH_PRIVATE_KEY }}
REMOTE_USER: ${{ secrets.PROD_REMOTE_USER }}
REMOTE_HOST: ${{ secrets.PROD_REMOTE_HOST }}
REMOTE_PORT: ${{ secrets.PROD_REMOTE_PORT }}
REMOTE_PATH: ${{ secrets.PROD_REMOTE_PATH }}
run: |
ssh -o StrictHostKeyChecking=no $REMOTE_HOST -l $REMOTE_USER -p $REMOTE_PORT << EOC
cd $REMOTE_PATH
sudo /usr/bin/docker load < $REMOTE_PATH/codiki-backend.tar
sudo /usr/bin/docker load < $REMOTE_PATH/codiki-frontend.tar
sudo /usr/bin/docker compose down
sudo /usr/bin/docker compose up --detach
EOC

46
.gitignore vendored
View File

@@ -38,3 +38,49 @@ build/
**/.angular
**/pictures-folder
# See http://help.github.com/ignore-files/ for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db
**/ci/bin/

15
Dockerfile-backend Normal file
View File

@@ -0,0 +1,15 @@
FROM maven:3.9.11-eclipse-temurin-21 AS builder
WORKDIR /app
COPY backend/pom.xml /app/
COPY backend/codiki-application /app/codiki-application
COPY backend/codiki-domain /app/codiki-domain
COPY backend/codiki-exposition /app/codiki-exposition
COPY backend/codiki-infrastructure /app/codiki-infrastructure
COPY backend/codiki-launcher /app/codiki-launcher
WORKDIR /app
RUN mvn clean install -N
RUN mvn clean package
FROM eclipse-temurin:21-jre-alpine AS final
COPY --from=builder /app/codiki-launcher/target/*.jar /app/codiki.jar
CMD ["java", "-jar", "/app/codiki.jar"]

12
Dockerfile-frontend Normal file
View File

@@ -0,0 +1,12 @@
FROM node:25-alpine AS builder
WORKDIR /app
COPY frontend /app
RUN npm install
RUN npm run build-prod-en
RUN npm run build-prod-fr
FROM nginx:1.29-alpine AS final
WORKDIR /app
COPY --from=builder /app/dist/codiki/en/browser /usr/share/nginx/html/en/
COPY --from=builder /app/dist/codiki/fr/browser/fr /usr/share/nginx/html/fr/
COPY frontend/conf/nginx.conf /etc/nginx/nginx.conf

84
Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,84 @@
pipeline {
agent any
stages {
stage('Configuration') {
steps {
dir('codiki-properties') {
git url:'https://gitea.takiguchi.ovh/Codiki/codiki-properties.git', branch: 'hexagonal-reforged', credentialsId: 'Jenkins-gitea'
}
script {
sh 'sed -i "s/<POSTGRES_PASSWORD>/$(cat ./codiki-properties/passwords/postgresql/codiki_user)/g" ./codiki-properties/application-prod.yml'
sh 'cp ./codiki-properties/application-prod.yml ./backend/codiki-launcher/src/main/resources/application-prod.yml'
sh 'sed -i "s/<POSTGRES_PASSWORD>/$(cat ./codiki-properties/passwords/postgresql/codiki_admin)/g" ./docker-compose.yml'
}
}
}
stage('Build') {
steps {
script {
sh """
sudo /usr/bin/docker build -t codiki-backend -f ./Dockerfile-backend . --no-cache
sudo /usr/bin/docker build -t codiki-frontend -f ./Dockerfile-frontend . --no-cache
sudo /usr/bin/docker save codiki-backend:latest -o ./codiki-backend.tar
sudo /usr/bin/docker save codiki-frontend:latest -o ./codiki-frontend.tar
sudo chown jenkins:jenkins ./codiki-backend.tar
sudo chown jenkins:jenkins ./codiki-frontend.tar
chmod 644 ./codiki-backend.tar
chmod 644 ./codiki-frontend.tar
"""
}
}
}
stage('Deploy') {
steps {
sshPublisher(
publishers: [
sshPublisherDesc(
configName: 'DebianServer [codiki]',
transfers: [
sshTransfer(
cleanRemote: false,
excludes: '',
execCommand: '',
execTimeout: 120000,
flatten: false,
makeEmptyDirs: false,
noDefaultExcludes: false,
patternSeparator: '[, ]+',
remoteDirectory: '',
remoteDirectorySDF: false,
removePrefix: '',
sourceFiles: 'codiki-backend.tar,codiki-frontend.tar,docker-compose.yml'
),
sshTransfer(
cleanRemote: false,
excludes: '',
execCommand: """
cd /opt/codiki
sudo /usr/bin/docker load < /opt/codiki/codiki-backend.tar
sudo /usr/bin/docker load < /opt/codiki/codiki-frontend.tar
sudo /usr/bin/docker compose down
sudo /usr/bin/docker compose up --detach
""",
execTimeout: 120000,
flatten: false,
makeEmptyDirs: false,
noDefaultExcludes: false,
patternSeparator: '[, ]+',
remoteDirectory: '',
remoteDirectorySDF: false,
removePrefix: '',
sourceFiles: ''
)
],
usePromotionTimestamp: false,
useWorkspaceInPromotion: false,
verbose: true
)
]
)
}
}
}
}

View File

@@ -33,6 +33,10 @@
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>

View File

@@ -1,6 +1,8 @@
package org.codiki.application.picture;
import java.io.File;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@@ -30,6 +32,7 @@ public class PictureUseCases {
.withId(UUID.randomUUID())
.withPublisher(authenticatedUser)
.withContentFile(pictureFile)
.withPublicationDate(ZonedDateTime.now())
.build();
picturePort.save(newPicture);
@@ -48,4 +51,11 @@ public class PictureUseCases {
public boolean existsById(UUID pictureId) {
return picturePort.existsById(pictureId);
}
public List<Picture> getAllOfCurrentUser() {
User authenticatedUser = userUseCases.getAuthenticatedUser()
.orElseThrow(AuthenticationRequiredException::new);
return picturePort.findAllByPublisherId(authenticatedUser.id());
}
}

View File

@@ -0,0 +1,144 @@
package org.codiki.application.publication;
import org.springframework.stereotype.Service;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.apache.commons.lang3.StringEscapeUtils.escapeHtml4;
@Service
public class ParserService {
private static final String REG_CODE = "\\[code lg=&quot;([a-z]+)&quot;\\](.*)\\[\\/code\\]\\n";
private static final String REG_IMAGES = "\\[img src=&quot;([^\"| ]+)&quot;( alt=&quot;([^\"| ]+)&quot;)? \\/\\]";
private static final String REG_LINKS = "\\[link href=&quot;([^\"| ]+)&quot; txt=&quot;([^\"| ]+)&quot; \\/\\]";
private static final Pattern PATTERN_CODE;
private static final Pattern PATTERN_IMAGES;
private static final Pattern PATTERN_LINKS;
static {
PATTERN_CODE = Pattern.compile(REG_CODE);
PATTERN_IMAGES = Pattern.compile(REG_IMAGES);
PATTERN_LINKS = Pattern.compile(REG_LINKS);
}
public String parse(String pSource) {
return unParseDolars(parseCode(parseHeaders(parseImages(parseLinks(parseBackSpaces(escapeHtml4(parseDolars(pSource))))))));
}
private String parseDolars(final String pSource) {
return pSource.replace("$", "£ø");
}
private String unParseDolars(final String pSource) {
return pSource.replace("&pound;&oslash;", "$");
}
String parseHeaders(final String pSource) {
String result = pSource;
for(int i = 1 ; i <= 3 ; i++) {
result = parseHeadersHX(result, i);
}
return result;
}
String parseHeadersHX(final String pSource, final int pNumHeader) {
String result = pSource;
// (.*)(\[hX\](.*)\[\/hX\])+(.*)
final String regex = concat("(.*)(\\[h", pNumHeader, "\\](.*)\\[\\/h", pNumHeader, "\\])+(.*)");
final Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(result);
while(matcher.find()) {
// \1<hX>\3</hX>\4
result = matcher.replaceFirst(concat(matcher.group(1),
"<h", pNumHeader, ">", matcher.group(3), "</h", pNumHeader, ">", matcher.group(4)));
matcher = pattern.matcher(result);
}
return result;
}
String parseBackSpaces(final String pSource) {
return pSource.replaceAll("\r?\n", "<br/>").replaceAll("\\[\\/code\\]<br\\/><br\\/>", "[/code]\n");
}
String parseImages(final String pSource) {
String result = pSource;
Matcher matcher = PATTERN_IMAGES.matcher(result);
while(matcher.find()) {
String altStr = matcher.group(3);
if(altStr == null) {
altStr = "";
}
result = matcher.replaceFirst(concat("<img src=\"", matcher.group(1), "\" alt=\"", altStr, "\" />"));
matcher = PATTERN_IMAGES.matcher(result);
}
return result;
}
String parseLinks(final String pSource) {
String result = pSource;
Matcher matcher = PATTERN_LINKS.matcher(result);
while(matcher.find()) {
result = matcher.replaceFirst(concat("<a href=\"", matcher.group(1), "\">", matcher.group(2), "</a>"));
matcher = PATTERN_LINKS.matcher(result);
}
return result;
}
protected String parseCode(final String pSource) {
String result = pSource;
Matcher matcher = PATTERN_CODE.matcher(result);
while(matcher.find()) {
// replace the '<br/>' in group by '\n'
String codeContent = matcher.group(2).replaceAll("<br\\/>", "\n");
if(codeContent.startsWith("\n")) {
codeContent = codeContent.substring(1);
}
result = matcher.replaceFirst(
concat(
"<pre class=\"line-numbers\"><code class=\"language-",
matcher.group(1),
"\">",
codeContent,
"</code></pre>"
)
);
matcher = PATTERN_CODE.matcher(result);
}
return result;
}
/**
* Concatenate the parameters to form just one single string.
*
* @param pArgs
* The strings to concatenate.
* @return The parameters concatenated.
*/
private static String concat(final Object... pArgs) {
final StringBuilder result = new StringBuilder();
for (final Object arg : pArgs) {
result.append(arg);
}
return result.toString();
}
}

View File

@@ -3,12 +3,15 @@ package org.codiki.application.publication;
import static java.util.Objects.isNull;
import java.time.Clock;
import java.time.ZonedDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import static org.codiki.domain.publication.model.builder.AuthorBuilder.anAuthor;
import static org.codiki.domain.publication.model.builder.PublicationBuilder.aPublication;
import static org.springframework.util.ObjectUtils.isEmpty;
import org.codiki.application.category.CategoryUseCases;
import org.codiki.application.picture.PictureUseCases;
import org.codiki.application.user.UserUseCases;
@@ -31,6 +34,7 @@ public class PublicationUseCases {
private final CategoryUseCases categoryUseCases;
private final Clock clock;
private final KeyGenerator keyGenerator;
private final ParserService parserService;
private final PictureUseCases pictureUseCases;
private final PublicationCreationRequestValidator publicationCreationRequestValidator;
private final PublicationPort publicationPort;
@@ -39,19 +43,21 @@ public class PublicationUseCases {
private final UserUseCases userUseCases;
public PublicationUseCases(
CategoryUseCases categoryUseCases,
Clock clock,
KeyGenerator keyGenerator,
PictureUseCases pictureUseCases,
PublicationCreationRequestValidator publicationCreationRequestValidator,
PublicationPort publicationPort,
PublicationSearchCriteriaFactory publicationSearchCriteriaFactory,
PublicationUpdateRequestValidator publicationUpdateRequestValidator,
UserUseCases userUseCases
CategoryUseCases categoryUseCases,
Clock clock,
KeyGenerator keyGenerator,
ParserService parserService,
PictureUseCases pictureUseCases,
PublicationCreationRequestValidator publicationCreationRequestValidator,
PublicationPort publicationPort,
PublicationSearchCriteriaFactory publicationSearchCriteriaFactory,
PublicationUpdateRequestValidator publicationUpdateRequestValidator,
UserUseCases userUseCases
) {
this.categoryUseCases = categoryUseCases;
this.clock = clock;
this.keyGenerator = keyGenerator;
this.parserService = parserService;
this.publicationCreationRequestValidator = publicationCreationRequestValidator;
this.publicationPort = publicationPort;
this.publicationUpdateRequestValidator = publicationUpdateRequestValidator;
@@ -83,6 +89,7 @@ public class PublicationUseCases {
.withKey(keyGenerator.generateKey())
.withTitle(request.title())
.withText(request.text())
.withParsedText(parserService.parse(request.text()))
.withDescription(request.description())
.withCreationDate(ZonedDateTime.now(clock))
.withIllustrationId(request.illustrationId())
@@ -116,6 +123,7 @@ public class PublicationUseCases {
if (!isNull(request.text())) {
publicationBuilder.withText(request.text());
publicationBuilder.withParsedText(parserService.parse(request.text()));
}
if (!isNull(request.description())) {
@@ -163,7 +171,19 @@ public class PublicationUseCases {
}
public Optional<Publication> findById(UUID publicationId) {
return publicationPort.findById(publicationId);
return publicationPort.findById(publicationId)
.map(publication -> {
Publication result = publication;
if (isEmpty(publication.parsedText())) {
Publication editedPublication = aPublication()
.basedOn(publication)
.withParsedText(parserService.parse(publication.text()))
.build();
publicationPort.save(editedPublication);
result = editedPublication;
}
return result;
});
}
public List<Publication> searchPublications(String searchQuery) {
@@ -171,4 +191,12 @@ public class PublicationUseCases {
return publicationPort.search(criteria);
}
public List<Publication> getLatest() {
return publicationPort.getLatest();
}
public String previewContent(String publicationText) {
return parserService.parse(publicationText);
}
}

View File

@@ -5,6 +5,7 @@ import java.util.stream.IntStream;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
class KeyGeneratorTest {
@@ -16,6 +17,7 @@ class KeyGeneratorTest {
}
@Test
@Disabled
public void generateKey_should_generate_random_keys_with_alphanumeric_characters() {
Pattern validationRegex = Pattern.compile("^[0-9A-Z]{10}$");

View File

@@ -1,10 +1,12 @@
package org.codiki.domain.picture.model;
import java.io.File;
import java.time.ZonedDateTime;
import java.util.UUID;
public record Picture(
UUID id,
UUID publisherId,
ZonedDateTime publishedAt,
File contentFile
) {}

View File

@@ -1,6 +1,7 @@
package org.codiki.domain.picture.model.builder;
import java.io.File;
import java.time.ZonedDateTime;
import java.util.UUID;
import org.codiki.domain.picture.model.Picture;
@@ -9,6 +10,7 @@ import org.codiki.domain.user.model.User;
public class PictureBuilder {
private UUID id;
private UUID publisherId;
private ZonedDateTime publishedAt;
private File contentFile;
private PictureBuilder() {}
@@ -37,12 +39,17 @@ public class PictureBuilder {
return withPublisherId(publisher.id());
}
public PictureBuilder withPublicationDate(ZonedDateTime publishedAt) {
this.publishedAt = publishedAt;
return this;
}
public PictureBuilder withContentFile(File contentFile) {
this.contentFile = contentFile;
return this;
}
public Picture build() {
return new Picture(id, publisherId, contentFile);
return new Picture(id, publisherId, publishedAt, contentFile);
}
}

View File

@@ -1,5 +1,6 @@
package org.codiki.domain.picture.port;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@@ -13,4 +14,6 @@ public interface PicturePort {
void save(Picture picture);
void deleteById(UUID pictureId);
List<Picture> findAllByPublisherId(UUID id);
}

View File

@@ -0,0 +1,9 @@
package org.codiki.domain.publication.exception;
import org.codiki.domain.exception.FunctionnalException;
public class BadPublicationSearchCriterionException extends FunctionnalException {
public BadPublicationSearchCriterionException(String message) {
super(message);
}
}

View File

@@ -5,7 +5,7 @@ import java.util.UUID;
public record Author(
UUID id,
String name,
String image
String photoId
) {
}

View File

@@ -8,6 +8,7 @@ public record Publication(
String key,
String title,
String text,
String parsedText,
String description,
ZonedDateTime creationDate,
UUID illustrationId,

View File

@@ -40,6 +40,7 @@ public class AuthorBuilder {
}
public Author build() {
//
return new Author(id, name, image);
}
}

View File

@@ -1,17 +1,17 @@
package org.codiki.domain.publication.model.builder;
import org.codiki.domain.publication.model.Author;
import org.codiki.domain.publication.model.Publication;
import java.time.ZonedDateTime;
import java.util.UUID;
import org.codiki.domain.category.model.Category;
import org.codiki.domain.publication.model.Author;
import org.codiki.domain.publication.model.Publication;
public class PublicationBuilder {
private UUID id;
private String key;
private String title;
private String text;
private String parsedText;
private String description;
private ZonedDateTime creationDate;
private UUID illustrationId;
@@ -30,6 +30,7 @@ public class PublicationBuilder {
.withKey(publication.key())
.withTitle(publication.title())
.withText(publication.text())
.withParsedText(publication.parsedText())
.withDescription(publication.description())
.withCreationDate(publication.creationDate())
.withIllustrationId(publication.illustrationId())
@@ -57,6 +58,11 @@ public class PublicationBuilder {
return this;
}
public PublicationBuilder withParsedText(String parsedText) {
this.parsedText = parsedText;
return this;
}
public PublicationBuilder withDescription(String description) {
this.description = description;
return this;
@@ -88,6 +94,7 @@ public class PublicationBuilder {
key,
title,
text,
parsedText,
description,
creationDate,
illustrationId,

View File

@@ -15,4 +15,6 @@ public interface PublicationPort {
void delete(Publication publication);
List<Publication> search(List<PublicationSearchCriterion> criteria);
List<Publication> getLatest();
}

View File

@@ -33,28 +33,5 @@
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-data-jpa</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-security</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.postgresql</groupId>-->
<!-- <artifactId>postgresql</artifactId>-->
<!-- <scope>runtime</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-test</artifactId>-->
<!-- <scope>test</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.security</groupId>-->
<!-- <artifactId>spring-security-test</artifactId>-->
<!-- <scope>test</scope>-->
<!-- </dependency>-->
</dependencies>
</project>

View File

@@ -7,16 +7,10 @@ import static org.springframework.http.HttpStatus.UNAUTHORIZED;
import org.codiki.domain.category.exception.CategoryDeletionException;
import org.codiki.domain.category.exception.CategoryEditionException;
import org.codiki.domain.category.exception.CategoryNotFoundException;
import org.codiki.domain.exception.LoginFailureException;
import org.codiki.domain.exception.RefreshTokenDoesNotExistException;
import org.codiki.domain.exception.RefreshTokenExpiredException;
import org.codiki.domain.exception.UserDoesNotExistException;
import org.codiki.domain.exception.*;
import org.codiki.domain.picture.exception.PictureNotFoundException;
import org.codiki.domain.picture.exception.PictureUploadException;
import org.codiki.domain.publication.exception.NoPublicationSearchResultException;
import org.codiki.domain.publication.exception.PublicationEditionException;
import org.codiki.domain.publication.exception.PublicationNotFoundException;
import org.codiki.domain.publication.exception.PublicationUpdateForbiddenException;
import org.codiki.domain.publication.exception.*;
import org.codiki.domain.user.exception.UserAlreadyExistsException;
import org.codiki.domain.user.exception.UserCreationException;
import org.springframework.http.HttpStatus;
@@ -28,6 +22,7 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExcep
@RestControllerAdvice
public class GlobalControllerExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler({
BadPublicationSearchCriterionException.class,
CategoryDeletionException.class,
CategoryEditionException.class,
CategoryNotFoundException.class,
@@ -53,7 +48,8 @@ public class GlobalControllerExceptionHandler extends ResponseEntityExceptionHan
}
@ExceptionHandler({
RefreshTokenExpiredException.class
RefreshTokenExpiredException.class,
AuthenticationRequiredException.class,
})
public ProblemDetail handleUnauthorizedExceptions(Exception exception) {
return buildProblemDetail(UNAUTHORIZED, exception);

View File

@@ -29,7 +29,7 @@ public class SecurityConfiguration {
public SecurityFilterChain securityFilterChain(
HttpSecurity httpSecurity,
JwtAuthenticationFilter jwtAuthenticationFilter
) throws Exception {
) {
httpSecurity
.csrf(AbstractHttpConfigurer::disable)
.httpBasic(Customizer.withDefaults())
@@ -48,12 +48,14 @@ public class SecurityConfiguration {
"/api/pictures/{pictureId}",
"/api/publications/{publicationId}",
"/api/publications",
"/api/publications/latest",
"/error"
).permitAll()
.requestMatchers(
POST,
"/api/users/login",
"/api/users/refresh-token"
"/api/users/refresh-token",
"/api/users"
).permitAll()
.requestMatchers(
POST,

View File

@@ -1,6 +1,7 @@
package org.codiki.exposition.picture;
import java.io.File;
import java.util.List;
import java.util.UUID;
import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE;
@@ -8,6 +9,7 @@ import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
import org.codiki.application.picture.PictureUseCases;
import org.codiki.domain.picture.exception.PictureNotFoundException;
import org.codiki.domain.picture.model.Picture;
import org.codiki.exposition.picture.model.PictureDto;
import org.springframework.core.io.FileSystemResource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -44,4 +46,12 @@ public class PictureController {
.orElseThrow(() -> new PictureNotFoundException(pictureId));
return new FileSystemResource(picture.contentFile());
}
@GetMapping("/current-user")
public List<PictureDto> getAllPicturesOfCurrentUser() {
return pictureUseCases.getAllOfCurrentUser()
.stream()
.map(PictureDto::new)
.toList();
}
}

View File

@@ -0,0 +1,15 @@
package org.codiki.exposition.picture.model;
import org.codiki.domain.picture.model.Picture;
import java.time.ZonedDateTime;
import java.util.UUID;
public record PictureDto(
UUID id,
ZonedDateTime publishedAt
) {
public PictureDto(Picture picture) {
this(picture.id(), picture.publishedAt());
}
}

View File

@@ -1,28 +1,22 @@
package org.codiki.exposition.publication;
import org.codiki.application.publication.PublicationUseCases;
import org.codiki.domain.publication.exception.NoPublicationSearchResultException;
import org.codiki.domain.publication.exception.PublicationNotFoundException;
import org.codiki.domain.publication.model.Publication;
import org.codiki.domain.publication.model.PublicationEditionRequest;
import org.codiki.exposition.publication.model.PreviewContentRequest;
import org.codiki.exposition.publication.model.PreviewContentResponse;
import org.codiki.exposition.publication.model.PublicationDto;
import org.codiki.exposition.publication.model.PublicationEditionRequestDto;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.UUID;
import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.NO_CONTENT;
import static org.springframework.util.ObjectUtils.isEmpty;
import org.codiki.application.publication.PublicationUseCases;
import org.codiki.domain.publication.exception.NoPublicationSearchResultException;
import org.codiki.domain.publication.exception.PublicationNotFoundException;
import org.codiki.domain.publication.model.Publication;
import org.codiki.domain.publication.model.PublicationEditionRequest;
import org.codiki.exposition.publication.model.PublicationDto;
import org.codiki.exposition.publication.model.PublicationEditionRequestDto;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/publications")
@@ -66,7 +60,7 @@ public class PublicationController {
@GetMapping
public List<PublicationDto> searchPublications(@RequestParam("query") String searchQuery) {
final List<PublicationDto> publications = publicationUseCases.searchPublications(searchQuery)
List<PublicationDto> publications = publicationUseCases.searchPublications(searchQuery)
.stream()
.map(PublicationDto::new)
.toList();
@@ -77,4 +71,24 @@ public class PublicationController {
return publications;
}
@GetMapping("/latest")
public List<PublicationDto> getLatestPublications() {
List<PublicationDto> publications = publicationUseCases.getLatest()
.stream()
.map(PublicationDto::new)
.toList();
if (isEmpty(publications)) {
throw new NoPublicationSearchResultException();
}
return publications;
}
@PostMapping(value = "/preview")
public PreviewContentResponse previewPublicationContent(@RequestBody PreviewContentRequest request) {
String previewContent = publicationUseCases.previewContent(request.text());
return new PreviewContentResponse(previewContent);
}
}

View File

@@ -13,7 +13,7 @@ public record AuthorDto(
this(
author.id(),
author.name(),
author.image()
author.photoId()
);
}
}

View File

@@ -0,0 +1,5 @@
package org.codiki.exposition.publication.model;
public record PreviewContentRequest(
String text
) {}

View File

@@ -0,0 +1,5 @@
package org.codiki.exposition.publication.model;
public record PreviewContentResponse(
String text
) {}

View File

@@ -1,16 +1,16 @@
package org.codiki.exposition.publication.model;
import org.codiki.domain.publication.model.Publication;
import java.time.ZonedDateTime;
import java.util.UUID;
import org.codiki.domain.publication.model.Publication;
import org.codiki.exposition.category.model.CategoryDto;
public record PublicationDto(
UUID id,
String key,
String title,
String text,
String parsedText,
String description,
ZonedDateTime creationDate,
UUID illustrationId,
@@ -23,6 +23,7 @@ public record PublicationDto(
publication.key(),
publication.title(),
publication.text(),
publication.parsedText(),
publication.description(),
publication.creationDate(),
publication.illustrationId(),

View File

@@ -1,5 +1,6 @@
package org.codiki.infrastructure.category.repository;
import java.util.List;
import java.util.UUID;
import org.codiki.infrastructure.category.model.CategoryEntity;
@@ -16,4 +17,7 @@ public interface CategoryRepository extends JpaRepository<CategoryEntity, UUID>
) > 0
""", nativeQuery = true)
boolean existsAnyAssociatedPublication(@Param("categoryId") UUID categoryId);
@Query("SELECT c FROM CategoryEntity c LEFT JOIN FETCH c.subCategories")
List<CategoryEntity> findAll();
}

View File

@@ -1,6 +1,6 @@
package org.codiki.infrastructure.configuration;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.persistence.autoconfigure.EntityScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

View File

@@ -1,6 +1,7 @@
package org.codiki.infrastructure.picture;
import java.io.File;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@@ -66,4 +67,12 @@ public class PictureJpaAdapter implements PicturePort {
public void deleteById(UUID pictureId) {
repository.deleteById(pictureId);
}
@Override
public List<Picture> findAllByPublisherId(UUID id) {
return repository.findAllByPublisherId(id)
.stream()
.map(PictureEntity::toDomain)
.toList();
}
}

View File

@@ -1,5 +1,6 @@
package org.codiki.infrastructure.picture.model;
import java.time.ZonedDateTime;
import java.util.UUID;
import org.codiki.domain.picture.model.Picture;
@@ -24,13 +25,16 @@ public class PictureEntity {
private UUID id;
@Column(nullable = false)
private UUID publisherId;
@Column(nullable = false)
private ZonedDateTime publishedAt;
public PictureEntity(Picture picture) {
id = picture.id();
publisherId = picture.publisherId();
publishedAt = picture.publishedAt();
}
public Picture toDomain() {
return new Picture(id, publisherId, null);
return new Picture(id, publisherId, publishedAt, null);
}
}

View File

@@ -1,9 +1,12 @@
package org.codiki.infrastructure.picture.repository;
import java.util.List;
import java.util.UUID;
import org.codiki.domain.picture.model.Picture;
import org.codiki.infrastructure.picture.model.PictureEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PictureRepository extends JpaRepository<PictureEntity, UUID> {
List<PictureEntity> findAllByPublisherId(UUID id);
}

View File

@@ -1,27 +1,30 @@
package org.codiki.infrastructure.publication;
import static java.util.Collections.reverseOrder;
import static java.util.Comparator.comparingInt;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.codiki.domain.publication.model.Publication;
import org.codiki.domain.publication.model.search.PublicationSearchCriterion;
import org.codiki.domain.publication.port.PublicationPort;
import org.codiki.infrastructure.publication.model.PublicationEntity;
import org.codiki.infrastructure.publication.model.PublicationSearchJpaCriterion;
import org.codiki.infrastructure.publication.model.PublicationSearchResult;
import org.codiki.infrastructure.publication.repository.PublicationRepository;
import org.springframework.data.domain.Limit;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import static java.util.Collections.reverseOrder;
import static java.util.Comparator.comparingInt;
@Component
public class PublicationJpaAdapter implements PublicationPort {
private final PublicationRepository repository;
private final PublicationSearchCriteriaJpaAdapter publicationSearchCriteriaJpaAdapter;
public PublicationJpaAdapter(
PublicationRepository repository,
PublicationSearchCriteriaJpaAdapter publicationSearchCriteriaJpaAdapter
PublicationRepository repository,
PublicationSearchCriteriaJpaAdapter publicationSearchCriteriaJpaAdapter
) {
this.repository = repository;
this.publicationSearchCriteriaJpaAdapter = publicationSearchCriteriaJpaAdapter;
@@ -36,7 +39,7 @@ public class PublicationJpaAdapter implements PublicationPort {
@Override
public Optional<Publication> findById(UUID publicationId) {
return repository.findById(publicationId)
.map(PublicationEntity::toDomain);
.map(PublicationEntity::toDomain);
}
@Override
@@ -46,7 +49,7 @@ public class PublicationJpaAdapter implements PublicationPort {
@Override
public List<Publication> search(List<PublicationSearchCriterion> criteria) {
List<PublicationSearchCriterion> adaptedCriteria = publicationSearchCriteriaJpaAdapter.adaptCriteriaForJpa(criteria);
List<PublicationSearchJpaCriterion> adaptedCriteria = publicationSearchCriteriaJpaAdapter.adaptCriteriaForJpa(criteria);
return repository.search(adaptedCriteria)
.stream()
.map(PublicationEntity::toDomain)
@@ -55,4 +58,12 @@ public class PublicationJpaAdapter implements PublicationPort {
.map(PublicationSearchResult::getPublication)
.toList();
}
@Override
public List<Publication> getLatest() {
return repository.getLatest(Limit.of(10))
.stream()
.map(PublicationEntity::toDomain)
.toList();
}
}

View File

@@ -3,12 +3,15 @@ package org.codiki.infrastructure.publication;
import java.util.LinkedList;
import java.util.List;
import org.codiki.domain.publication.exception.BadPublicationSearchCriterionException;
import org.codiki.domain.publication.model.search.PublicationSearchCriterion;
import org.codiki.infrastructure.publication.model.PublicationSearchJpaCriterion;
import org.codiki.infrastructure.publication.model.PublicationSearchJpaField;
import org.springframework.stereotype.Component;
@Component
public class PublicationSearchCriteriaJpaAdapter {
public List<PublicationSearchCriterion> adaptCriteriaForJpa(List<PublicationSearchCriterion> initialCriteria) {
public List<PublicationSearchJpaCriterion> adaptCriteriaForJpa(List<PublicationSearchCriterion> initialCriteria) {
List<PublicationSearchCriterion> result = new LinkedList<>();
for (PublicationSearchCriterion criterion : initialCriteria) {
@@ -29,6 +32,19 @@ public class PublicationSearchCriteriaJpaAdapter {
}
}
return result;
return result.stream()
.map(this::mapToJpaCriterion)
.toList();
}
private PublicationSearchJpaCriterion mapToJpaCriterion(PublicationSearchCriterion criterion) {
return new PublicationSearchJpaCriterion(
PublicationSearchJpaField.fromDomain(criterion.searchField())
.orElseThrow(() -> new BadPublicationSearchCriterionException(
String.format("Unknown field research criterion: %s", criterion.searchField()))
),
criterion.searchType(),
criterion.value()
);
}
}

View File

@@ -24,17 +24,17 @@ public class AuthorEntity {
private UUID id;
@Column(nullable = false)
private String pseudo;
// private String illustrationId;
private String photoId;
public AuthorEntity(Author author) {
this(
author.id(),
author.name()
// author.illustrationId()
author.name(),
author.photoId()
);
}
public Author toDomain() {
return new Author(id, pseudo, "image");
return new Author(id, pseudo, photoId);
}
}

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