diff --git a/.gitignore b/.gitignore index eda632f6..8639c812 100644 --- a/.gitignore +++ b/.gitignore @@ -34,5 +34,4 @@ build/ ## Node JS -package-lock.json -.env \ No newline at end of file +package-lock.json \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile index 86b5b49a..a9a95092 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -3,7 +3,10 @@ pipeline { environment { DOCKER_IMAGE = 'anataarisa/ademy' DOCKER_COMPOSE_NAME = 'ademy-dev-application' - DOCKER_COMPOSE_DEV_FILE = 'docker-compose.dev.yaml' + DOCKER_COMPOSE_QA_FILE = 'docker-compose.qa.yaml' + DOCKER_COMPOSE_PROD_FILE = 'docker-compose.prod.yaml' + DOCKER_IMAGE_BACKEND_API = 'api-backend-server-qa' + DOCKER_IMAGE_FRONTEND_SERVER = 'svelte-frontend-server-qa' } tools { maven 'Arisa CI/CD Maven' @@ -17,28 +20,37 @@ pipeline { } stage('Clean Docker Enviroments'){ steps{ - sh 'docker compose -f ' + DOCKER_COMPOSE_DEV_FILE + ' -p ' + DOCKER_COMPOSE_NAME + ' down --rmi all -v' + sh 'docker compose -f ' + DOCKER_COMPOSE_QA_FILE + ' -p ' + DOCKER_COMPOSE_NAME + ' down --rmi all -v' } } - // stage('Source Testing'){ - // steps{ - // dir('./api/drawingcouseselling'){ - // sh 'mvn test -Pdev' - // } - // // dir('./frontend/my-app'){ - // // sh 'pnpm i' - // // sh 'pnpm dev' - // // } - // } - // } - stage('Build docker-compose dev and push images'){ + stage('Build docker-compose qa enviroment and push images to docker-hub'){ steps{ withDockerRegistry(credentialsId: 'Arisa Docker Hub Account', url: 'https://index.docker.io/v1/'){ - sh 'docker compose -f ' + DOCKER_COMPOSE_DEV_FILE + ' -p ' + DOCKER_COMPOSE_NAME + ' build' - sh 'docker compose -f ' + DOCKER_COMPOSE_DEV_FILE + ' -p ' + DOCKER_COMPOSE_NAME + ' up -d' + sh 'docker compose -f ' + DOCKER_COMPOSE_QA_FILE + ' -p ' + DOCKER_COMPOSE_NAME + ' build' + sh 'docker compose -f ' + DOCKER_COMPOSE_QA_FILE + ' -p ' + DOCKER_COMPOSE_NAME + ' up -d' + sh 'docker tag ' + DOCKER_IMAGE_BACKEND_API + ':latest anataarisa/' + DOCKER_IMAGE_BACKEND_API + ':latest' + sh 'docker tag ' + DOCKER_IMAGE_FRONTEND_SERVER + ':latest anataarisa/' + DOCKER_IMAGE_FRONTEND_SERVER + ':latest' + sh 'docker push anataarisa/' + DOCKER_IMAGE_BACKEND_API + ':latest' + sh 'docker push anataarisa/' + DOCKER_IMAGE_FRONTEND_SERVER + ':latest' + sh 'docker rmi anataarisa/api-backend-server-qa:latest anataarisa/svelte-frontend-server-qa:latest' } } } + // stage('Build docker-compose prod and push images for prod deployment'){ + // steps{ + // withDockerRegistry(credentialsId: 'Arisa Docker Hub Account', url: 'https://index.docker.io/v1/'){ + // sh 'docker compose -f ' + DOCKER_COMPOSE_PROD_FILE + ' -p ' + DOCKER_COMPOSE_NAME + ' build' + // sh 'docker compose -f ' + DOCKER_COMPOSE_PROD_FILE + ' -p ' + DOCKER_COMPOSE_NAME + ' push' + // // Tag the images with 'latest' before pushing + // sh 'docker tag ' + DOCKER_IMAGE_BACKEND_API + ':latest ' + DOCKER_IMAGE_BACKEND_API + ':latest' + // sh 'docker tag ' + DOCKER_IMAGE_FRONTEND_SERVER + ':latest ' + DOCKER_IMAGE_FRONTEND_SERVER + ':latest' + + // // Push the images with the 'latest' tag + // sh 'docker push ' + DOCKER_IMAGE_BACKEND_API + ':latest' + // sh 'docker push ' + DOCKER_IMAGE_FRONTEND_SERVER + ':latest' + // } + // } + // } } post { diff --git a/api/drawingcouseselling/Dockerfile b/api/drawingcouseselling/Dockerfile index 26aa9a38..3e1dcf14 100644 --- a/api/drawingcouseselling/Dockerfile +++ b/api/drawingcouseselling/Dockerfile @@ -1,6 +1,8 @@ FROM maven:3.8.5-openjdk-17 LABEL authors="AnataArisa" WORKDIR /api-backend +ARG API_ENV COPY . . -RUN mvn clean install -Pdev +ENV SPRING_PROFILES_ACTIVE=${API_ENV} +RUN mvn clean install ENTRYPOINT ["java", "-jar", "./target/drawingcouseselling-0.1.jar"] \ No newline at end of file diff --git a/api/drawingcouseselling/pom.xml b/api/drawingcouseselling/pom.xml index c469d4b8..11dabd1a 100644 --- a/api/drawingcouseselling/pom.xml +++ b/api/drawingcouseselling/pom.xml @@ -230,7 +230,6 @@ dev dev - 5005 true @@ -243,9 +242,9 @@ - qa + staging - qa + staging diff --git a/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/ApplicationWebConfig.java b/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/ApplicationWebConfig.java index 5943e44c..0b891cd9 100644 --- a/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/ApplicationWebConfig.java +++ b/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/ApplicationWebConfig.java @@ -21,7 +21,7 @@ protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { public CorsFilter corsFilter() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.setAllowCredentials(true); - corsConfiguration.setAllowedOrigins(Arrays.asList("http://localhost:3000")); + corsConfiguration.setAllowedOrigins(Arrays.asList("http://localhost:3000", "http://anataarisa.hopto.org:3000")); corsConfiguration.setAllowedHeaders(Arrays.asList("Origin", "Access-Control-Allow-Origin", "Content-Type", "Accept", "Authorization", "Origin, Accept", "X-Requested-With", "Access-Control-Request-Method", "Access-Control-Request-Headers")); diff --git a/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/model/dto/CourseContentDto.java b/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/model/dto/CourseContentDto.java index f06d8fda..5818b95e 100644 --- a/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/model/dto/CourseContentDto.java +++ b/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/model/dto/CourseContentDto.java @@ -17,7 +17,7 @@ public record CourseContentDto(BigDecimal id, @NotBlank(message = "Description can't be empty") @NotNull(message = "Description can't be empty") String description, - @Pattern(regexp = "^(https?://)?(www\\.)?youtube\\.com/watch\\?v=[a-zA-Z0-9_-]+.*$", message = "Invalid YouTube link") + @Pattern(regexp = "^(https?://)?(www\\.)?(youtube\\.com/watch\\?v=|youtu\\.be/|youtube\\.com/embed/)[a-zA-Z0-9_-]+.*$", message = "Invalid YouTube link") @NotBlank(message = "The youtube link must not be empty") String videoLink, ECourseContentType courseType, diff --git a/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/model/dto/CourseContentEditDto.java b/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/model/dto/CourseContentEditDto.java index 84c1a286..40e7530a 100644 --- a/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/model/dto/CourseContentEditDto.java +++ b/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/model/dto/CourseContentEditDto.java @@ -12,7 +12,7 @@ public record CourseContentEditDto(BigDecimal id, String title, @NotBlank(message = "Description can't be empty") String description, - @Pattern(regexp = "^(https?://)?(www\\.)?youtube\\.com/watch\\?v=[a-zA-Z0-9_-]+.*$", message = "Invalid YouTube link") + @Pattern(regexp = "^(https?://)?(www\\.)?(youtube\\.com/watch\\?v=|youtu\\.be/|youtube\\.com/embed/)[a-zA-Z0-9_-]+.*$", message = "Invalid YouTube link") @NotBlank(message = "The youtube link must not be empty") String videoLink) { } diff --git a/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/model/dto/CustomerDto.java b/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/model/dto/CustomerDto.java index 8532d9ed..c830c5f5 100644 --- a/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/model/dto/CustomerDto.java +++ b/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/model/dto/CustomerDto.java @@ -18,6 +18,9 @@ public record CustomerDto(BigDecimal customerID, EGender gender, @Email(message = "Please enter a valid email address") @NotBlank(message = "Email can't be empty") - String email, Date joinDate) { + String email, + Date joinDate, + String avatarLink +) { } diff --git a/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/model/entity/Customer.java b/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/model/entity/Customer.java index 094bb91a..cfa01f9d 100644 --- a/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/model/entity/Customer.java +++ b/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/model/entity/Customer.java @@ -113,6 +113,7 @@ public CustomerDto convertEntityToDto(Customer data) { .birthDate(data.getBirthDate()) .gender(data.getGender()) .email(data.getAccount().getEmail()) + .avatarLink(getPath()) .build(); } } diff --git a/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/repository/CourseContentCompletionRepository.java b/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/repository/CourseContentCompletionRepository.java index 1a333fb2..aec0fe67 100644 --- a/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/repository/CourseContentCompletionRepository.java +++ b/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/repository/CourseContentCompletionRepository.java @@ -19,7 +19,7 @@ public interface CourseContentCompletionRepository extends JpaRepository getCourseContentCompletionByCustomerID(@Param(value = "customerID") BigDecimal customerID, @Param(value = "courseID") BigDecimal courseID); @Query("SELECT COUNT(c3) FROM course_content_completion c3 WHERE c3.courseContent.id = :courseSectionID") diff --git a/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/service/impl/CustomerServiceImpl.java b/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/service/impl/CustomerServiceImpl.java index e2913695..52786fa6 100644 --- a/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/service/impl/CustomerServiceImpl.java +++ b/api/drawingcouseselling/src/main/java/com/group1/drawingcouseselling/service/impl/CustomerServiceImpl.java @@ -60,6 +60,7 @@ public Optional searchCustomerByEmailDto(String customerEmailDto) { .gender(data.getGender()) .email(customerEmailDto) .joinDate(accountService.checkAccountByEmail(customerEmailDto).getCreateDate()) + .avatarLink(data.getPath()) .build(); } assert customerDto != null; diff --git a/api/drawingcouseselling/src/main/resources/application-staging.yml b/api/drawingcouseselling/src/main/resources/application-staging.yml new file mode 100644 index 00000000..08534f67 --- /dev/null +++ b/api/drawingcouseselling/src/main/resources/application-staging.yml @@ -0,0 +1,81 @@ +aws: + access: + key: + id: AKIA2EWSF45CBHDRXQJK + s3: + region: ap-southeast-1 + bucket: + name: ademyimage + secret: + access: + key: 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 + +spring: + datasource: + url: jdbc:mariadb://${DB_HOST:anataarisa.hopto.org}:${DB_PORT:3306}/ademy + # url: jdbc:mariadb://localhost:3307/ademy + username: ${DB_USERNAME:pim} + password: ${DB_PASSWORD:Vinh12345.} + driver-class-name: org.mariadb.jdbc.Driver + jpa: + hibernate: + ddl-auto: update + sql: + init: + encoding: utf-8 + # session: + # store-type=jdbc: + mail: + host: smtp.gmail.com + port: 587 + username: no.reply.ademy@gmail.com + password: ztgzeyizzyicozuc + properties: + mail: + smtp: + auth: true + starttls: + enable: true + security: + oauth2: + client: + registration: + google: + client-id: 509336143279-ng2g9fdpg8pkt1j3jlj24au6l96hk0qn.apps.googleusercontent.com + client-secret: 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 + +server: + servlet: + context-path: /api/v1 + port: 9090 + error: + include-message: always + + +logging: + level: + org: + springframework: + security: TRACE + hibernate: + type: trace + orm: + jdbc: + bind: trace + +jwt-key: + secret-key: ${JWT_KEY:49906c40c2bd2ce2a35571ebf991e74573d0e5cc5db9e032a7b1c5af42c8b038} + +paypal: + client: + secret: EHGsrSIQG-aODbOaHmd5YnhCLNS3Zq3x3einniprXjRkABpA-U0UEXy9w_hp-YElLEwsQv6tmQNJ4ZVS + app: AWbOtZPREQ6DxSCza2yGZT82kQTW9T17r8xezCuKARBoBM5jli0FdOPrdRVkytKLdeaaFaiM6k-tHlr6 + mode: sandbox + + +frontend: + url: http://anataarisa.hopto.org:3000/ + +api: + base: + url: http://anataarisa.hopto.org:7070/api/v1/ diff --git a/api/drawingcouseselling/src/main/resources/application.qa.yml b/api/drawingcouseselling/src/main/resources/application.qa.yml deleted file mode 100644 index e69de29b..00000000 diff --git a/docker-compose.dev.yaml b/docker-compose.local.yaml similarity index 94% rename from docker-compose.dev.yaml rename to docker-compose.local.yaml index 4c175096..7345a1d4 100644 --- a/docker-compose.dev.yaml +++ b/docker-compose.local.yaml @@ -26,6 +26,8 @@ services: build: context: "./api/drawingcouseselling/" dockerfile: Dockerfile + args: + - API_ENV=dev ports: - "7070:9090" networks: @@ -50,6 +52,8 @@ services: build: context: "./frontend/my-app/" dockerfile: Dockerfile + args: + - ENV_DP=development ports: - "3000:3000" networks: @@ -58,5 +62,6 @@ services: depends_on: api-backend: condition: service_healthy + networks: - dev-network: \ No newline at end of file + dev-network: diff --git a/docker-compose.qa.yaml b/docker-compose.qa.yaml new file mode 100644 index 00000000..71e8e66f --- /dev/null +++ b/docker-compose.qa.yaml @@ -0,0 +1,44 @@ +version: '3.8' +services: + api-backend: + image: api-backend-server-qa + container_name: api-backend-server-qa + build: + context: "./api/drawingcouseselling/" + dockerfile: Dockerfile + args: + - API_ENV=staging + ports: + - "7070:9090" + networks: + - dev-network + environment: + DB_HOST: anataarisa.hopto.org + DB_PORT: 3306 + DB_USERNAME: pim + DB_PASSWORD: Vinh12345. + restart: always + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9090/api/v1/courses"] + interval: 30s + timeout: 10s + retries: 3 + + front-end: + image: svelte-frontend-server-qa + container_name: svelte-frontend-server-qa + build: + context: "./frontend/my-app/" + dockerfile: Dockerfile + args: + - ENV_DP=testing + ports: + - "3000:3000" + networks: + - dev-network + restart: always + depends_on: + api-backend: + condition: service_healthy +networks: + dev-network: diff --git a/frontend/.gitignore b/frontend/.gitignore index c80e912f..965aac71 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -3,9 +3,6 @@ node_modules /build /.svelte-kit /package -.env -.env.* -!.env.example vite.config.js.timestamp-* vite.config.ts.timestamp-* .vscode/ diff --git a/frontend/my-app/.env b/frontend/my-app/.env new file mode 100644 index 00000000..d735d919 --- /dev/null +++ b/frontend/my-app/.env @@ -0,0 +1,4 @@ +# Public +PUBLIC_API_BASE_URL_DEV=http://localhost:7070/api/v1/ +PUBLIC_API_BASE_URL_PROD=http://anataarisa.hopto.org:7070/api/v1/ +PUBLIC_API_BASE_URL_QA=http://anataarisa.hopto.org:7070/api/v1/ \ No newline at end of file diff --git a/frontend/my-app/Dockerfile b/frontend/my-app/Dockerfile index 58bb5c48..408c56f8 100644 --- a/frontend/my-app/Dockerfile +++ b/frontend/my-app/Dockerfile @@ -1,5 +1,7 @@ FROM node:latest WORKDIR /svelte-frontend +ARG ENV_DP +ENV NODE_ENV=${ENV_DP} RUN npm install -g pnpm RUN npm i vite COPY package*.json . @@ -7,4 +9,5 @@ RUN pnpm i COPY . . EXPOSE 3000 EXPOSE 24678 -ENTRYPOINT [ "pnpm", "dev" ] \ No newline at end of file +RUN pnpm build +ENTRYPOINT [ "node", "build" ] \ No newline at end of file diff --git a/frontend/my-app/src/app.html b/frontend/my-app/src/app.html index 61e99fb3..0bb67d23 100644 --- a/frontend/my-app/src/app.html +++ b/frontend/my-app/src/app.html @@ -11,7 +11,7 @@ Ademy - + %sveltekit.head% diff --git a/frontend/my-app/src/routes/CardScrollContainer.svelte b/frontend/my-app/src/routes/CardScrollContainer.svelte index 535cf39c..7b11b31b 100644 --- a/frontend/my-app/src/routes/CardScrollContainer.svelte +++ b/frontend/my-app/src/routes/CardScrollContainer.svelte @@ -13,6 +13,7 @@ durations: string; instructorID: number; instructorName: string; + thumbnail_path: string; } let searchValue: string; let course: CourseTemplate[] = []; @@ -22,9 +23,7 @@ async function handlePull() { try { await axios - .get( - apiBaseUrl + `courses?maxPage=20` - ) + .get(apiBaseUrl + `courses?maxPage=20`) .then((response) => { console.log(response.data); course = response.data; @@ -34,23 +33,26 @@ } } +

Course Most Choices

{#each course as item} -
- -
+
+ +
{/each}
diff --git a/frontend/my-app/src/routes/CourseCard.svelte b/frontend/my-app/src/routes/CourseCard.svelte index c9cfeb65..b77525d9 100644 --- a/frontend/my-app/src/routes/CourseCard.svelte +++ b/frontend/my-app/src/routes/CourseCard.svelte @@ -10,6 +10,7 @@ export let duration: string; export let instructorName: string; export let instructorId: number; + export let image_card: string; export let isFetchManual: boolean; import { Card, Rating, Badge } from "flowbite-svelte"; let open = true; @@ -45,6 +46,7 @@ durations: "", instructorName: "", instructorID: 0, + thumbnail_path: "", }; async function handleAddCart() { jwtToken = GetCookie("USER"); @@ -75,7 +77,7 @@ async function handleGetCourse() { try { let res = await axios - .get(`http://localhost:9090/api/v1/course?id=${id + 1}`) + .get(apiBaseUrl + `course?id=${id + 1}`) .then((response) => { console.log(response.data); course = response.data; @@ -108,6 +110,7 @@ durations: duration, instructorName: instructorName, instructorID: instructorId, + thumbnail_path: image_card, }; getRating(); console.log(rating); @@ -123,7 +126,7 @@ product 1 diff --git a/frontend/my-app/src/routes/CreateCourse.svelte b/frontend/my-app/src/routes/CreateCourse.svelte index 82b2cc38..cf214a76 100644 --- a/frontend/my-app/src/routes/CreateCourse.svelte +++ b/frontend/my-app/src/routes/CreateCourse.svelte @@ -1,229 +1,224 @@
- +
-
-
- - -
-
- -