feature: ci/cd pipeline

# Conflicts:
#	api/mangamochi.ts
This commit is contained in:
Rodrigo Verdiani 2025-10-28 22:27:56 -03:00
parent 8196548f1d
commit 39710d9135
8 changed files with 78 additions and 67 deletions

4
.gitignore vendored
View File

@ -142,7 +142,7 @@ yarn-error.log*
.pnpm-debug.log* .pnpm-debug.log*
# local env files # local env files
.env*.local .env.development
# vercel # vercel
.vercel .vercel
@ -226,7 +226,7 @@ web_modules/
.env.development.local .env.development.local
.env.test.local .env.test.local
.env.production.local .env.production.local
.env.local .env.development
# parcel-bundler cache (https://parceljs.org/) # parcel-bundler cache (https://parceljs.org/)
.cache .cache

View File

@ -1,44 +1,57 @@
# .woodpecker.yml # .pipeline.yml
# -----------------
# Run pipeline on pushes to main and pull requests
when: when:
event: [push, pull_request] event: [ push, pull_request ]
branch: [main, develop, feat/*, feature/*]
# Define environment variables
environment:
NODE_ENV: production
# Pipeline steps
steps: steps:
# Install dependencies - name: publish-image
- 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
image: woodpeckerci/plugin-docker-buildx image: woodpeckerci/plugin-docker-buildx
settings: settings:
platforms: linux/amd64 platforms: linux/amd64
repo: git.badger-pirarucu.ts.net/mangamochi/frontend repo: git.badger-pirarucu.ts.net/mangamochi/frontend
registry: git.badger-pirarucu.ts.net registry: git.badger-pirarucu.ts.net
dockerfile: Dockerfile
context: .
username: username:
from_secret: DOCKER_USER from_secret: DOCKER_USER
password: password:
from_secret: DOCKER_PASSWORD from_secret: DOCKER_PASSWORD
when: tags:
event: [push, pull_request] - 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 \
--env-file /home/rov/mangamochi/.env \
-p 80:3000 \
$IMAGE
"
when:
event: [ push, pull_request ]
branch: [ main, dev, feature/*, feat/* ]

View File

@ -3,7 +3,7 @@ import { User } from "@/contexts/auth-context";
import { toast } from "sonner"; import { toast } from "sonner";
export const Api = axios.create({ export const Api = axios.create({
baseURL: "http://localhost:8080", baseURL: process.env.NEXT_PUBLIC_API_BASE_URL,
responseType: "json", responseType: "json",
}); });

View File

@ -109,18 +109,18 @@ export interface PageMangaListDTO {
content?: MangaListDTO[]; content?: MangaListDTO[];
number?: number; number?: number;
pageable?: PageableObject; pageable?: PageableObject;
sort?: SortObject;
first?: boolean; first?: boolean;
last?: boolean; last?: boolean;
sort?: SortObject;
numberOfElements?: number; numberOfElements?: number;
empty?: boolean; empty?: boolean;
} }
export interface PageableObject { export interface PageableObject {
offset?: number; offset?: number;
paged?: boolean;
pageNumber?: number; pageNumber?: number;
pageSize?: number; pageSize?: number;
paged?: boolean;
sort?: SortObject; sort?: SortObject;
unpaged?: boolean; unpaged?: boolean;
} }
@ -279,7 +279,7 @@ export const sendRecord = (
return customInstance<string>( return customInstance<string>(
{url: `http://mangamochi.badger-pirarucu.ts.net:8080/records`, method: 'POST', {url: `/records`, method: 'POST',
headers: {'Content-Type': 'application/json', }, headers: {'Content-Type': 'application/json', },
data: updateMangaDataCommand, signal data: updateMangaDataCommand, signal
}, },
@ -342,7 +342,7 @@ export const fetchMangaChapters = (
return customInstance<DefaultResponseDTOVoid>( return customInstance<DefaultResponseDTOVoid>(
{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); options);
} }
@ -406,7 +406,7 @@ export const fetchAllChapters = (
return customInstance<DefaultResponseDTOVoid>( return customInstance<DefaultResponseDTOVoid>(
{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); options);
} }
@ -470,7 +470,7 @@ export const setUnfavorite = (
return customInstance<DefaultResponseDTOVoid>( return customInstance<DefaultResponseDTOVoid>(
{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); options);
} }
@ -534,7 +534,7 @@ export const setFavorite = (
return customInstance<DefaultResponseDTOVoid>( return customInstance<DefaultResponseDTOVoid>(
{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); options);
} }
@ -598,7 +598,7 @@ export const markAsRead = (
return customInstance<DefaultResponseDTOVoid>( return customInstance<DefaultResponseDTOVoid>(
{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); options);
} }
@ -662,7 +662,7 @@ export const fetchChapter = (
return customInstance<DefaultResponseDTOVoid>( return customInstance<DefaultResponseDTOVoid>(
{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); options);
} }
@ -727,7 +727,7 @@ export const downloadChapterArchive = (
return customInstance<Blob>( return customInstance<Blob>(
{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, params,
responseType: 'blob', signal responseType: 'blob', signal
}, },
@ -796,7 +796,7 @@ formData.append(`malId`, importMultipleFilesBody.malId)
importMultipleFilesBody.files.forEach(value => formData.append(`files`, value)); importMultipleFilesBody.files.forEach(value => formData.append(`files`, value));
return customInstance<DefaultResponseDTOVoid>( return customInstance<DefaultResponseDTOVoid>(
{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', }, headers: {'Content-Type': 'multipart/form-data', },
data: formData, signal data: formData, signal
}, },
@ -862,7 +862,7 @@ export const getImportReviews = (
return customInstance<DefaultResponseDTOListImportReviewDTO>( return customInstance<DefaultResponseDTOListImportReviewDTO>(
{url: `http://mangamochi.badger-pirarucu.ts.net:8080/manga/import/review`, method: 'GET', signal {url: `/manga/import/review`, method: 'GET', signal
}, },
options); options);
} }
@ -872,7 +872,7 @@ export const getImportReviews = (
export const getGetImportReviewsQueryKey = () => { export const getGetImportReviewsQueryKey = () => {
return [ return [
`http://mangamochi.badger-pirarucu.ts.net:8080/manga/import/review` `/manga/import/review`
] as const; ] as const;
} }
@ -955,7 +955,7 @@ export const resolveImportReview = (
return customInstance<DefaultResponseDTOVoid>( return customInstance<DefaultResponseDTOVoid>(
{url: `http://mangamochi.badger-pirarucu.ts.net:8080/manga/import/review`, method: 'POST', {url: `/manga/import/review`, method: 'POST',
params, signal params, signal
}, },
options); options);
@ -1020,7 +1020,7 @@ export const importFromMangaDex = (
return customInstance<DefaultResponseDTOImportMangaDexResponseDTO>( return customInstance<DefaultResponseDTOImportMangaDexResponseDTO>(
{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', }, headers: {'Content-Type': 'application/json', },
data: importMangaDexRequestDTO, signal data: importMangaDexRequestDTO, signal
}, },
@ -1086,7 +1086,7 @@ export const registerUser = (
return customInstance<DefaultResponseDTOVoid>( return customInstance<DefaultResponseDTOVoid>(
{url: `http://mangamochi.badger-pirarucu.ts.net:8080/auth/register`, method: 'POST', {url: `/auth/register`, method: 'POST',
headers: {'Content-Type': 'application/json', }, headers: {'Content-Type': 'application/json', },
data: registrationRequestDTO, signal data: registrationRequestDTO, signal
}, },
@ -1152,7 +1152,7 @@ export const authenticateUser = (
return customInstance<DefaultResponseDTOAuthenticationResponseDTO>( return customInstance<DefaultResponseDTOAuthenticationResponseDTO>(
{url: `http://mangamochi.badger-pirarucu.ts.net:8080/auth/login`, method: 'POST', {url: `/auth/login`, method: 'POST',
headers: {'Content-Type': 'application/json', }, headers: {'Content-Type': 'application/json', },
data: authenticationRequestDTO, signal data: authenticationRequestDTO, signal
}, },
@ -1218,7 +1218,7 @@ export const getMangas = (
return customInstance<DefaultResponseDTOPageMangaListDTO>( return customInstance<DefaultResponseDTOPageMangaListDTO>(
{url: `http://mangamochi.badger-pirarucu.ts.net:8080/mangas`, method: 'GET', {url: `/mangas`, method: 'GET',
params, signal params, signal
}, },
options); options);
@ -1229,7 +1229,7 @@ export const getMangas = (
export const getGetMangasQueryKey = (params?: GetMangasParams,) => { export const getGetMangasQueryKey = (params?: GetMangasParams,) => {
return [ return [
`http://mangamochi.badger-pirarucu.ts.net:8080/mangas`, ...(params ? [params]: []) `/mangas`, ...(params ? [params]: [])
] as const; ] as const;
} }
@ -1312,7 +1312,7 @@ export const getMangaChapters = (
return customInstance<DefaultResponseDTOListMangaChapterDTO>( return customInstance<DefaultResponseDTOListMangaChapterDTO>(
{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); options);
} }
@ -1322,7 +1322,7 @@ export const getMangaChapters = (
export const getGetMangaChaptersQueryKey = (mangaProviderId?: number,) => { export const getGetMangaChaptersQueryKey = (mangaProviderId?: number,) => {
return [ return [
`http://mangamochi.badger-pirarucu.ts.net:8080/mangas/${mangaProviderId}/chapters` `/mangas/${mangaProviderId}/chapters`
] as const; ] as const;
} }
@ -1405,7 +1405,7 @@ export const getManga = (
return customInstance<DefaultResponseDTOMangaDTO>( return customInstance<DefaultResponseDTOMangaDTO>(
{url: `http://mangamochi.badger-pirarucu.ts.net:8080/mangas/${encodeURIComponent(String(mangaId))}`, method: 'GET', signal {url: `/mangas/${encodeURIComponent(String(mangaId))}`, method: 'GET', signal
}, },
options); options);
} }
@ -1415,7 +1415,7 @@ export const getManga = (
export const getGetMangaQueryKey = (mangaId?: number,) => { export const getGetMangaQueryKey = (mangaId?: number,) => {
return [ return [
`http://mangamochi.badger-pirarucu.ts.net:8080/mangas/${mangaId}` `/mangas/${mangaId}`
] as const; ] as const;
} }
@ -1498,7 +1498,7 @@ export const getMangaChapterImages = (
return customInstance<DefaultResponseDTOMangaChapterImagesDTO>( return customInstance<DefaultResponseDTOMangaChapterImagesDTO>(
{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); options);
} }
@ -1508,7 +1508,7 @@ export const getMangaChapterImages = (
export const getGetMangaChapterImagesQueryKey = (chapterId?: number,) => { export const getGetMangaChapterImagesQueryKey = (chapterId?: number,) => {
return [ return [
`http://mangamochi.badger-pirarucu.ts.net:8080/mangas/chapters/${chapterId}/images` `/mangas/chapters/${chapterId}/images`
] as const; ] as const;
} }
@ -1591,7 +1591,7 @@ export const getGenres = (
return customInstance<DefaultResponseDTOListGenreDTO>( return customInstance<DefaultResponseDTOListGenreDTO>(
{url: `http://mangamochi.badger-pirarucu.ts.net:8080/genres`, method: 'GET', signal {url: `/genres`, method: 'GET', signal
}, },
options); options);
} }
@ -1601,7 +1601,7 @@ export const getGenres = (
export const getGetGenresQueryKey = () => { export const getGetGenresQueryKey = () => {
return [ return [
`http://mangamochi.badger-pirarucu.ts.net:8080/genres` `/genres`
] as const; ] as const;
} }
@ -1683,7 +1683,7 @@ export const deleteImportReview = (
return customInstance<DefaultResponseDTOVoid>( return customInstance<DefaultResponseDTOVoid>(
{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); options);
} }

View File

@ -142,8 +142,7 @@ export default function ChapterReaderPage() {
{/* Manga Page */} {/* Manga Page */}
<div className="relative mx-auto mb-8 overflow-hidden rounded-lg border border-border bg-muted"> <div className="relative mx-auto mb-8 overflow-hidden rounded-lg border border-border bg-muted">
<Image <Image
src={ src={process.env.NEXT_PUBLIC_OMV_BASE_URL + "/" +
"http://omv.badger-pirarucu.ts.net:9000/mangamochi/" +
data.data.chapterImageKeys[currentPage - 1] || data.data.chapterImageKeys[currentPage - 1] ||
"/placeholder.svg" "/placeholder.svg"
} }

View File

@ -128,7 +128,7 @@ export default function MangaDetailPage() {
<Image <Image
src={ src={
(mangaData.data?.coverImageKey && (mangaData.data?.coverImageKey &&
"http://omv.badger-pirarucu.ts.net:9000/mangamochi/" + process.env.NEXT_PUBLIC_OMV_BASE_URL + "/" +
mangaData.data?.coverImageKey) || mangaData.data?.coverImageKey) ||
"/placeholder.svg" "/placeholder.svg"
} }

View File

@ -110,7 +110,7 @@ export function MangaCard({ manga, queryKey }: MangaCardProps) {
<Image <Image
src={ src={
(manga.coverImageKey && (manga.coverImageKey &&
"http://omv.badger-pirarucu.ts.net:9000/mangamochi/" + process.env.NEXT_PUBLIC_OMV_BASE_URL + "/" +
manga.coverImageKey) || manga.coverImageKey) ||
"/placeholder.svg" "/placeholder.svg"
} }

View File

@ -7,7 +7,6 @@ module.exports = {
target: "api/mangamochi.ts", target: "api/mangamochi.ts",
client: "react-query", client: "react-query",
httpClient: "axios", httpClient: "axios",
baseUrl: "http://mangamochia.badger-pirarucu.ts.net:8080",
urlEncodeParameters: true, urlEncodeParameters: true,
override: { override: {
mutator: { mutator: {