From 4143ddbb7da417a26cc7fb96ff8fa25aaa2226d9 Mon Sep 17 00:00:00 2001 From: Rodrigo Verdiani Date: Tue, 28 Oct 2025 22:27:56 -0300 Subject: [PATCH] feature: ci/cd pipeline --- .gitignore | 4 +- .woodpecker/pipeline.yaml | 77 +++++++++++-------- Dockerfile | 2 + api/api.ts | 2 +- api/mangamochi.ts | 56 +++++++------- .../[id]/chapter/[chapterNumber]/page.tsx | 3 +- app/manga/[id]/page.tsx | 2 +- components/manga-card.tsx | 2 +- orval.config.ts | 1 - 9 files changed, 82 insertions(+), 67 deletions(-) diff --git a/.gitignore b/.gitignore index 7332353..f23ba7c 100644 --- a/.gitignore +++ b/.gitignore @@ -142,7 +142,7 @@ yarn-error.log* .pnpm-debug.log* # local env files -.env*.local +.env.development # vercel .vercel @@ -226,7 +226,7 @@ web_modules/ .env.development.local .env.test.local .env.production.local -.env.local +.env.development # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/.woodpecker/pipeline.yaml b/.woodpecker/pipeline.yaml index e0e463f..7af950e 100644 --- a/.woodpecker/pipeline.yaml +++ b/.woodpecker/pipeline.yaml @@ -1,44 +1,59 @@ -# .woodpecker.yml +# .pipeline.yml +# ----------------- -# Run pipeline on pushes to main and pull requests when: - event: [push, pull_request] - branch: [main, develop, feat/*, feature/*] + event: [ push, pull_request ] -# Define environment variables -environment: - NODE_ENV: production - -# Pipeline steps steps: - # Install dependencies - - name: install - image: node:18-alpine - commands: - - echo "🔧 Installing dependencies..." - - npm ci --silent - when: - event: [push, pull_request] - - # Build application - - name: build - image: node:18-alpine - commands: - - echo "🏗️ Building application..." - - npm run build - when: - event: [push, pull_request] - - - name: publish + - name: publish-image image: woodpeckerci/plugin-docker-buildx settings: platforms: linux/amd64 repo: git.badger-pirarucu.ts.net/mangamochi/frontend registry: git.badger-pirarucu.ts.net + dockerfile: Dockerfile + context: . username: from_secret: DOCKER_USER password: from_secret: DOCKER_PASSWORD - when: - event: [push, pull_request] - + build_args: + NEXT_PUBLIC_API_BASE_URL: + from_secret: NEXT_PUBLIC_API_BASE_URL + tags: + - latest + - ${CI_COMMIT_SHA} + when: + event: [ push, pull_request ] + branch: [ main, dev, feature/*, feat/* ] + + - name: deploy + depends_on: [ publish-image ] + image: alpine:3.20 + environment: + DEPLOY_USER: rov + DEPLOY_HOST: mangamochi.badger-pirarucu.ts.net + DEPLOY_PORT: 22 + IMAGE: git.badger-pirarucu.ts.net/mangamochi/frontend:${CI_COMMIT_SHA} + DEPLOY_SSH_KEY: + from_secret: DEPLOY_SSH_KEY + commands: + - echo "🚀 Deploying Next.js app to $DEPLOY_HOST...." + - apk add --no-cache openssh-client docker-cli + - mkdir -p ~/.ssh + - echo "$DEPLOY_SSH_KEY" > ~/.ssh/id_rsa + - chmod 600 ~/.ssh/id_rsa + - ssh-keyscan -p $DEPLOY_PORT $DEPLOY_HOST >> ~/.ssh/known_hosts + - > + ssh -p $DEPLOY_PORT $DEPLOY_USER@$DEPLOY_HOST " + docker pull $IMAGE && + docker stop mangamochi-frontend 2>/dev/null || true && + docker rm mangamochi-frontend 2>/dev/null || true && + docker run -d --name mangamochi-frontend \ + --restart always \ + -p 80:3000 \ + $IMAGE + " + when: + event: [ push, pull_request ] + branch: [ main, dev, feature/*, feat/* ] diff --git a/Dockerfile b/Dockerfile index 92bccdc..5dd63e4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,6 +14,8 @@ RUN npm ci COPY . . # Build the application +ARG NEXT_PUBLIC_API_BASE_URL +ENV NEXT_PUBLIC_API_BASE_URL=${NEXT_PUBLIC_API_BASE_URL} RUN npm run build # Stage 2: Production stage diff --git a/api/api.ts b/api/api.ts index 33ba9e9..8fefde1 100644 --- a/api/api.ts +++ b/api/api.ts @@ -3,7 +3,7 @@ import { User } from "@/contexts/auth-context"; import { toast } from "sonner"; export const Api = axios.create({ - baseURL: "http://localhost:8080", + baseURL: process.env.NEXT_PUBLIC_API_BASE_URL, responseType: "json", }); diff --git a/api/mangamochi.ts b/api/mangamochi.ts index 2277b67..0372a64 100644 --- a/api/mangamochi.ts +++ b/api/mangamochi.ts @@ -109,18 +109,18 @@ export interface PageMangaListDTO { content?: MangaListDTO[]; number?: number; pageable?: PageableObject; - sort?: SortObject; first?: boolean; last?: boolean; + sort?: SortObject; numberOfElements?: number; empty?: boolean; } export interface PageableObject { offset?: number; + paged?: boolean; pageNumber?: number; pageSize?: number; - paged?: boolean; sort?: SortObject; unpaged?: boolean; } @@ -279,7 +279,7 @@ export const sendRecord = ( return customInstance( - {url: `http://mangamochi.badger-pirarucu.ts.net:8080/records`, method: 'POST', + {url: `/records`, method: 'POST', headers: {'Content-Type': 'application/json', }, data: updateMangaDataCommand, signal }, @@ -342,7 +342,7 @@ export const fetchMangaChapters = ( return customInstance( - {url: `http://mangamochi.badger-pirarucu.ts.net:8080/mangas/${encodeURIComponent(String(mangaProviderId))}/fetch-chapters`, method: 'POST', signal + {url: `/mangas/${encodeURIComponent(String(mangaProviderId))}/fetch-chapters`, method: 'POST', signal }, options); } @@ -406,7 +406,7 @@ export const fetchAllChapters = ( return customInstance( - {url: `http://mangamochi.badger-pirarucu.ts.net:8080/mangas/${encodeURIComponent(String(mangaProviderId))}/fetch-all-chapters`, method: 'POST', signal + {url: `/mangas/${encodeURIComponent(String(mangaProviderId))}/fetch-all-chapters`, method: 'POST', signal }, options); } @@ -470,7 +470,7 @@ export const setUnfavorite = ( return customInstance( - {url: `http://mangamochi.badger-pirarucu.ts.net:8080/mangas/${encodeURIComponent(String(id))}/unfavorite`, method: 'POST', signal + {url: `/mangas/${encodeURIComponent(String(id))}/unfavorite`, method: 'POST', signal }, options); } @@ -534,7 +534,7 @@ export const setFavorite = ( return customInstance( - {url: `http://mangamochi.badger-pirarucu.ts.net:8080/mangas/${encodeURIComponent(String(id))}/favorite`, method: 'POST', signal + {url: `/mangas/${encodeURIComponent(String(id))}/favorite`, method: 'POST', signal }, options); } @@ -598,7 +598,7 @@ export const markAsRead = ( return customInstance( - {url: `http://mangamochi.badger-pirarucu.ts.net:8080/mangas/chapters/${encodeURIComponent(String(chapterId))}/mark-as-read`, method: 'POST', signal + {url: `/mangas/chapters/${encodeURIComponent(String(chapterId))}/mark-as-read`, method: 'POST', signal }, options); } @@ -662,7 +662,7 @@ export const fetchChapter = ( return customInstance( - {url: `http://mangamochi.badger-pirarucu.ts.net:8080/mangas/chapters/${encodeURIComponent(String(chapterId))}/fetch`, method: 'POST', signal + {url: `/mangas/chapters/${encodeURIComponent(String(chapterId))}/fetch`, method: 'POST', signal }, options); } @@ -727,7 +727,7 @@ export const downloadChapterArchive = ( return customInstance( - {url: `http://mangamochi.badger-pirarucu.ts.net:8080/mangas/chapters/${encodeURIComponent(String(chapterId))}/download`, method: 'POST', + {url: `/mangas/chapters/${encodeURIComponent(String(chapterId))}/download`, method: 'POST', params, responseType: 'blob', signal }, @@ -796,7 +796,7 @@ formData.append(`malId`, importMultipleFilesBody.malId) importMultipleFilesBody.files.forEach(value => formData.append(`files`, value)); return customInstance( - {url: `http://mangamochi.badger-pirarucu.ts.net:8080/manga/import/upload`, method: 'POST', + {url: `/manga/import/upload`, method: 'POST', headers: {'Content-Type': 'multipart/form-data', }, data: formData, signal }, @@ -862,7 +862,7 @@ export const getImportReviews = ( return customInstance( - {url: `http://mangamochi.badger-pirarucu.ts.net:8080/manga/import/review`, method: 'GET', signal + {url: `/manga/import/review`, method: 'GET', signal }, options); } @@ -872,7 +872,7 @@ export const getImportReviews = ( export const getGetImportReviewsQueryKey = () => { return [ - `http://mangamochi.badger-pirarucu.ts.net:8080/manga/import/review` + `/manga/import/review` ] as const; } @@ -955,7 +955,7 @@ export const resolveImportReview = ( return customInstance( - {url: `http://mangamochi.badger-pirarucu.ts.net:8080/manga/import/review`, method: 'POST', + {url: `/manga/import/review`, method: 'POST', params, signal }, options); @@ -1020,7 +1020,7 @@ export const importFromMangaDex = ( return customInstance( - {url: `http://mangamochi.badger-pirarucu.ts.net:8080/manga/import/manga-dex`, method: 'POST', + {url: `/manga/import/manga-dex`, method: 'POST', headers: {'Content-Type': 'application/json', }, data: importMangaDexRequestDTO, signal }, @@ -1086,7 +1086,7 @@ export const registerUser = ( return customInstance( - {url: `http://mangamochi.badger-pirarucu.ts.net:8080/auth/register`, method: 'POST', + {url: `/auth/register`, method: 'POST', headers: {'Content-Type': 'application/json', }, data: registrationRequestDTO, signal }, @@ -1152,7 +1152,7 @@ export const authenticateUser = ( return customInstance( - {url: `http://mangamochi.badger-pirarucu.ts.net:8080/auth/login`, method: 'POST', + {url: `/auth/login`, method: 'POST', headers: {'Content-Type': 'application/json', }, data: authenticationRequestDTO, signal }, @@ -1218,7 +1218,7 @@ export const getMangas = ( return customInstance( - {url: `http://mangamochi.badger-pirarucu.ts.net:8080/mangas`, method: 'GET', + {url: `/mangas`, method: 'GET', params, signal }, options); @@ -1229,7 +1229,7 @@ export const getMangas = ( export const getGetMangasQueryKey = (params?: GetMangasParams,) => { return [ - `http://mangamochi.badger-pirarucu.ts.net:8080/mangas`, ...(params ? [params]: []) + `/mangas`, ...(params ? [params]: []) ] as const; } @@ -1312,7 +1312,7 @@ export const getMangaChapters = ( return customInstance( - {url: `http://mangamochi.badger-pirarucu.ts.net:8080/mangas/${encodeURIComponent(String(mangaProviderId))}/chapters`, method: 'GET', signal + {url: `/mangas/${encodeURIComponent(String(mangaProviderId))}/chapters`, method: 'GET', signal }, options); } @@ -1322,7 +1322,7 @@ export const getMangaChapters = ( export const getGetMangaChaptersQueryKey = (mangaProviderId?: number,) => { return [ - `http://mangamochi.badger-pirarucu.ts.net:8080/mangas/${mangaProviderId}/chapters` + `/mangas/${mangaProviderId}/chapters` ] as const; } @@ -1405,7 +1405,7 @@ export const getManga = ( return customInstance( - {url: `http://mangamochi.badger-pirarucu.ts.net:8080/mangas/${encodeURIComponent(String(mangaId))}`, method: 'GET', signal + {url: `/mangas/${encodeURIComponent(String(mangaId))}`, method: 'GET', signal }, options); } @@ -1415,7 +1415,7 @@ export const getManga = ( export const getGetMangaQueryKey = (mangaId?: number,) => { return [ - `http://mangamochi.badger-pirarucu.ts.net:8080/mangas/${mangaId}` + `/mangas/${mangaId}` ] as const; } @@ -1498,7 +1498,7 @@ export const getMangaChapterImages = ( return customInstance( - {url: `http://mangamochi.badger-pirarucu.ts.net:8080/mangas/chapters/${encodeURIComponent(String(chapterId))}/images`, method: 'GET', signal + {url: `/mangas/chapters/${encodeURIComponent(String(chapterId))}/images`, method: 'GET', signal }, options); } @@ -1508,7 +1508,7 @@ export const getMangaChapterImages = ( export const getGetMangaChapterImagesQueryKey = (chapterId?: number,) => { return [ - `http://mangamochi.badger-pirarucu.ts.net:8080/mangas/chapters/${chapterId}/images` + `/mangas/chapters/${chapterId}/images` ] as const; } @@ -1591,7 +1591,7 @@ export const getGenres = ( return customInstance( - {url: `http://mangamochi.badger-pirarucu.ts.net:8080/genres`, method: 'GET', signal + {url: `/genres`, method: 'GET', signal }, options); } @@ -1601,7 +1601,7 @@ export const getGenres = ( export const getGetGenresQueryKey = () => { return [ - `http://mangamochi.badger-pirarucu.ts.net:8080/genres` + `/genres` ] as const; } @@ -1683,7 +1683,7 @@ export const deleteImportReview = ( return customInstance( - {url: `http://mangamochi.badger-pirarucu.ts.net:8080/manga/import/review/${encodeURIComponent(String(id))}`, method: 'DELETE' + {url: `/manga/import/review/${encodeURIComponent(String(id))}`, method: 'DELETE' }, options); } diff --git a/app/manga/[id]/chapter/[chapterNumber]/page.tsx b/app/manga/[id]/chapter/[chapterNumber]/page.tsx index 1f5a3ab..79e4285 100644 --- a/app/manga/[id]/chapter/[chapterNumber]/page.tsx +++ b/app/manga/[id]/chapter/[chapterNumber]/page.tsx @@ -142,8 +142,7 @@ export default function ChapterReaderPage() { {/* Manga Page */}