From a2c3d605d4534c9ba704122367cd85356b77e2f3 Mon Sep 17 00:00:00 2001 From: Alperen Elhan Date: Sun, 16 Oct 2022 02:18:39 +0300 Subject: [PATCH] feat: add metadata --- docker-compose.yml | 22 ++++++ .../20221015132249_init/migration.sql | 32 --------- .../20221015165819_add_dates/migration.sql | 39 ----------- .../20221015225209_init/migration.sql | 70 +++++++++++++++++++ prisma/migrations/migration_lock.toml | 2 +- prisma/schema.prisma | 40 ++++++++--- src/components/addManga/index.tsx | 1 + src/components/addManga/steps/index.tsx | 6 +- src/components/headerSearch.tsx | 11 +-- src/components/navbar.tsx | 7 +- src/pages/index.tsx | 8 ++- src/server/trpc/router/manga.ts | 53 +++++++++++--- src/server/utils/mangal.ts | 4 +- 13 files changed, 190 insertions(+), 105 deletions(-) delete mode 100644 prisma/migrations/20221015132249_init/migration.sql delete mode 100644 prisma/migrations/20221015165819_add_dates/migration.sql create mode 100644 prisma/migrations/20221015225209_init/migration.sql diff --git a/docker-compose.yml b/docker-compose.yml index 86dad86..dd21b1e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,29 @@ version: '3' +volumes: + db: + + services: redis: image: redis:7-alpine ports: - 6379:6379 + db: + image: postgres:alpine + restart: unless-stopped + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U kaizoku" ] + interval: 5s + timeout: 5s + retries: 5 + env_file: + - .env + environment: + POSTGRES_USER: ${DATABASE_USER} + POSTGRES_DB: ${DATABASE_SCHEMA} + POSTGRES_PASSWORD: ${DATABASE_PASSWORD} + volumes: + - db:/var/lib/postgresql/data + ports: + - "${DATABASE_PORT}:5432" diff --git a/prisma/migrations/20221015132249_init/migration.sql b/prisma/migrations/20221015132249_init/migration.sql deleted file mode 100644 index a894f5b..0000000 --- a/prisma/migrations/20221015132249_init/migration.sql +++ /dev/null @@ -1,32 +0,0 @@ --- CreateTable -CREATE TABLE "Library" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "path" TEXT NOT NULL -); - --- CreateTable -CREATE TABLE "Manga" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "title" TEXT NOT NULL, - "cover" TEXT NOT NULL, - "interval" TEXT NOT NULL, - "source" TEXT NOT NULL, - "libraryId" INTEGER NOT NULL, - CONSTRAINT "Manga_libraryId_fkey" FOREIGN KEY ("libraryId") REFERENCES "Library" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "Chapter" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "index" INTEGER NOT NULL, - "fileName" TEXT NOT NULL, - "size" INTEGER NOT NULL, - "mangaId" INTEGER NOT NULL, - CONSTRAINT "Chapter_mangaId_fkey" FOREIGN KEY ("mangaId") REFERENCES "Manga" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateIndex -CREATE UNIQUE INDEX "Library_path_key" ON "Library"("path"); - --- CreateIndex -CREATE UNIQUE INDEX "Manga_title_key" ON "Manga"("title"); diff --git a/prisma/migrations/20221015165819_add_dates/migration.sql b/prisma/migrations/20221015165819_add_dates/migration.sql deleted file mode 100644 index 2e93242..0000000 --- a/prisma/migrations/20221015165819_add_dates/migration.sql +++ /dev/null @@ -1,39 +0,0 @@ --- RedefineTables -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_Chapter" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "index" INTEGER NOT NULL, - "fileName" TEXT NOT NULL, - "size" INTEGER NOT NULL, - "mangaId" INTEGER NOT NULL, - CONSTRAINT "Chapter_mangaId_fkey" FOREIGN KEY ("mangaId") REFERENCES "Manga" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); -INSERT INTO "new_Chapter" ("fileName", "id", "index", "mangaId", "size") SELECT "fileName", "id", "index", "mangaId", "size" FROM "Chapter"; -DROP TABLE "Chapter"; -ALTER TABLE "new_Chapter" RENAME TO "Chapter"; -CREATE TABLE "new_Manga" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "title" TEXT NOT NULL, - "cover" TEXT NOT NULL, - "interval" TEXT NOT NULL, - "source" TEXT NOT NULL, - "libraryId" INTEGER NOT NULL, - CONSTRAINT "Manga_libraryId_fkey" FOREIGN KEY ("libraryId") REFERENCES "Library" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); -INSERT INTO "new_Manga" ("cover", "id", "interval", "libraryId", "source", "title") SELECT "cover", "id", "interval", "libraryId", "source", "title" FROM "Manga"; -DROP TABLE "Manga"; -ALTER TABLE "new_Manga" RENAME TO "Manga"; -CREATE UNIQUE INDEX "Manga_title_key" ON "Manga"("title"); -CREATE TABLE "new_Library" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "path" TEXT NOT NULL -); -INSERT INTO "new_Library" ("id", "path") SELECT "id", "path" FROM "Library"; -DROP TABLE "Library"; -ALTER TABLE "new_Library" RENAME TO "Library"; -CREATE UNIQUE INDEX "Library_path_key" ON "Library"("path"); -PRAGMA foreign_key_check; -PRAGMA foreign_keys=ON; diff --git a/prisma/migrations/20221015225209_init/migration.sql b/prisma/migrations/20221015225209_init/migration.sql new file mode 100644 index 0000000..951ef44 --- /dev/null +++ b/prisma/migrations/20221015225209_init/migration.sql @@ -0,0 +1,70 @@ +-- CreateTable +CREATE TABLE "Library" ( + "id" SERIAL NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "path" TEXT NOT NULL, + + CONSTRAINT "Library_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Manga" ( + "id" SERIAL NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "title" TEXT NOT NULL, + "interval" TEXT NOT NULL, + "source" TEXT NOT NULL, + "libraryId" INTEGER NOT NULL, + "metadataId" INTEGER NOT NULL, + + CONSTRAINT "Manga_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Chapter" ( + "id" SERIAL NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "index" INTEGER NOT NULL, + "fileName" TEXT NOT NULL, + "size" INTEGER NOT NULL, + "mangaId" INTEGER NOT NULL, + + CONSTRAINT "Chapter_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Metadata" ( + "id" SERIAL NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "genres" TEXT[] DEFAULT ARRAY[]::TEXT[], + "summary" TEXT NOT NULL DEFAULT '', + "authors" TEXT[] DEFAULT ARRAY[]::TEXT[], + "cover" TEXT NOT NULL DEFAULT '/cover-not-found.jpg', + "tags" TEXT[] DEFAULT ARRAY[]::TEXT[], + "characters" TEXT[] DEFAULT ARRAY[]::TEXT[], + "status" TEXT NOT NULL DEFAULT 'UNKNOWN', + "startDate" TIMESTAMP(3), + "endDate" TIMESTAMP(3), + "synonyms" TEXT[] DEFAULT ARRAY[]::TEXT[], + "urls" TEXT[] DEFAULT ARRAY[]::TEXT[], + + CONSTRAINT "Metadata_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Library_path_key" ON "Library"("path"); + +-- CreateIndex +CREATE UNIQUE INDEX "Manga_title_key" ON "Manga"("title"); + +-- CreateIndex +CREATE UNIQUE INDEX "Manga_metadataId_key" ON "Manga"("metadataId"); + +-- AddForeignKey +ALTER TABLE "Manga" ADD CONSTRAINT "Manga_libraryId_fkey" FOREIGN KEY ("libraryId") REFERENCES "Library"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Manga" ADD CONSTRAINT "Manga_metadataId_fkey" FOREIGN KEY ("metadataId") REFERENCES "Metadata"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Chapter" ADD CONSTRAINT "Chapter_mangaId_fkey" FOREIGN KEY ("mangaId") REFERENCES "Manga"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml index e5e5c47..fbffa92 100644 --- a/prisma/migrations/migration_lock.toml +++ b/prisma/migrations/migration_lock.toml @@ -1,3 +1,3 @@ # Please do not edit this file manually # It should be added in your version-control system (i.e. Git) -provider = "sqlite" \ No newline at end of file +provider = "postgresql" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0f2338b..2bc0a2c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -7,8 +7,8 @@ generator client { } datasource db { - provider = "sqlite" - url = "file:./kaizoku.db" + provider = "postgresql" + url = env("DATABASE_URL") } model Library { @@ -19,15 +19,16 @@ model Library { } model Manga { - id Int @id @default(autoincrement()) - createdAt DateTime @default(now()) - title String @unique - cover String - interval String - source String - library Library @relation(fields: [libraryId], references: [id], onDelete: Cascade) - libraryId Int - chapter Chapter[] + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + title String @unique + interval String + source String + library Library @relation(fields: [libraryId], references: [id], onDelete: Cascade) + libraryId Int + chapter Chapter[] + metadata Metadata @relation(fields: [metadataId], references: [id], onDelete: Cascade) + metadataId Int @unique } model Chapter { @@ -39,3 +40,20 @@ model Chapter { manga Manga @relation(fields: [mangaId], references: [id], onDelete: Cascade) mangaId Int } + +model Metadata { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + manga Manga? + genres String[] @default([]) + summary String @default("") + authors String[] @default([]) + cover String @default("/cover-not-found.jpg") + tags String[] @default([]) + characters String[] @default([]) + status String @default("UNKNOWN") + startDate DateTime? + endDate DateTime? + synonyms String[] @default([]) + urls String[] @default([]) +} diff --git a/src/components/addManga/index.tsx b/src/components/addManga/index.tsx index ebb9618..3dae6b0 100644 --- a/src/components/addManga/index.tsx +++ b/src/components/addManga/index.tsx @@ -32,6 +32,7 @@ export function AddManga({ onAdd }: { onAdd: () => void }) { trapFocus: true, size: 'xl', closeOnClickOutside: false, + closeOnEscape: true, title: 'Add a new manga', centered: true, children: ( diff --git a/src/components/addManga/steps/index.tsx b/src/components/addManga/steps/index.tsx index bc73eb3..4552f7f 100644 --- a/src/components/addManga/steps/index.tsx +++ b/src/components/addManga/steps/index.tsx @@ -50,7 +50,7 @@ export default function AddMangaSteps({ 0} + allowStepSelect={false} color={active > 0 ? 'teal' : 'blue'} > @@ -58,7 +58,7 @@ export default function AddMangaSteps({ 1} + allowStepSelect={false} color={active > 1 ? 'teal' : 'blue'} > @@ -66,7 +66,7 @@ export default function AddMangaSteps({ 2} + allowStepSelect={false} color={active > 2 ? 'teal' : 'blue'} > diff --git a/src/components/headerSearch.tsx b/src/components/headerSearch.tsx index dd85438..cab9b36 100644 --- a/src/components/headerSearch.tsx +++ b/src/components/headerSearch.tsx @@ -31,15 +31,15 @@ export function SearchControl() { setActions( mangaQuery.data.map((m) => ({ title: m.title, - description: m.title, + description: `${m.metadata.summary.split(' ').slice(0, 50).join(' ')}...`, group: m.source, icon: ( {m.title}} - src={m.cover} - width={42} - height={64} + placeholder={{m.title}} + src={m.metadata.cover} + width={60} + height={100} /> ), closeOnTrigger: true, @@ -54,6 +54,7 @@ export function SearchControl() { actions={actions} searchIcon={} highlightQuery + limit={5} disabled={isDisabled} searchPlaceholder="Search..." shortcut="ctrl + p" diff --git a/src/components/navbar.tsx b/src/components/navbar.tsx index 1803a53..1a14353 100644 --- a/src/components/navbar.tsx +++ b/src/components/navbar.tsx @@ -140,7 +140,7 @@ function History({ data }: { data: HistoryType }) { } + bullet={header} title={} > @@ -235,6 +235,11 @@ export function KaizokuNavbar() { const historyQuery = trpc.manga.history.useQuery(); const activityQuery = trpc.manga.activity.useQuery(); + const libraryQuery = trpc.library.query.useQuery(); + + if (!libraryQuery.data) { + return null; + } return ( diff --git a/src/pages/index.tsx b/src/pages/index.tsx index b3fc955..cbaae66 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,4 +1,4 @@ -import { Code, Grid, Text } from '@mantine/core'; +import { Code, Grid, LoadingOverlay, Text } from '@mantine/core'; import { showNotification } from '@mantine/notifications'; import { IconCheck, IconX } from '@tabler/icons'; import { useRouter } from 'next/router'; @@ -15,6 +15,10 @@ export default function IndexPage() { const mangaQuery = trpc.manga.query.useQuery(); + if (libraryQuery.isLoading) { + return ; + } + if (mangaQuery.isLoading || libraryQuery.isLoading) { return ( @@ -90,7 +94,7 @@ export default function IndexPage() { handleRemove(manga.id, manga.title)} onClick={() => router.push(`/manga/${manga.id}`)} /> diff --git a/src/server/trpc/router/manga.ts b/src/server/trpc/router/manga.ts index 72bd127..5940428 100644 --- a/src/server/trpc/router/manga.ts +++ b/src/server/trpc/router/manga.ts @@ -4,12 +4,12 @@ import { z } from 'zod'; import { sanitizer } from '../../../utils/sanitize'; import { removeJob, schedule } from '../../queue/checkChapters'; import { downloadQueue } from '../../queue/download'; -import { getAvailableSources, getMangaDetail, removeManga, search } from '../../utils/mangal'; +import { getAvailableSources, getMangaDetail, Manga, removeManga, search } from '../../utils/mangal'; import { t } from '../trpc'; export const mangaRouter = t.router({ query: t.procedure.query(async ({ ctx }) => { - return ctx.prisma.manga.findMany(); + return ctx.prisma.manga.findMany({ include: { metadata: true } }); }), sources: t.procedure.query(async () => { return getAvailableSources(); @@ -41,6 +41,7 @@ export const mangaRouter = t.router({ }, }, library: true, + metadata: true, }, where: { id }, }); @@ -91,9 +92,9 @@ export const mangaRouter = t.router({ ) .mutation(async ({ input, ctx }) => { const { source, title, interval } = input; - const detail = await getMangaDetail(source, title); + const mangaDetail: Manga | undefined = await getMangaDetail(source, title); const library = await ctx.prisma.library.findFirst(); - if (!detail || !library) { + if (!mangaDetail || !library) { throw new TRPCError({ code: 'NOT_FOUND', message: `Cannot find the ${title}.`, @@ -111,7 +112,7 @@ export const mangaRouter = t.router({ }); } - if (detail.Name !== title) { + if (mangaDetail.Name !== title) { throw new TRPCError({ code: 'NOT_FOUND', message: `${title} does not match the found manga.`, @@ -123,11 +124,41 @@ export const mangaRouter = t.router({ library: true, }, data: { - cover: detail.Metadata.Cover, source, - title: detail.Name, - libraryId: library.id, + title: mangaDetail.Name, + library: { + connect: { + id: library.id, + }, + }, interval, + metadata: { + create: { + cover: mangaDetail.Metadata.Cover, + authors: mangaDetail.Metadata.Author ? [mangaDetail.Metadata.Author] : [], + characters: mangaDetail.Metadata.Characters, + genres: mangaDetail.Metadata.Genres, + startDate: mangaDetail.Metadata.StartDate + ? new Date( + mangaDetail.Metadata.StartDate.Year, + mangaDetail.Metadata.StartDate.Month, + mangaDetail.Metadata.StartDate.Day, + ) + : undefined, + endDate: mangaDetail.Metadata.EndDate + ? new Date( + mangaDetail.Metadata.EndDate.Year, + mangaDetail.Metadata.EndDate.Month, + mangaDetail.Metadata.EndDate.Day, + ) + : undefined, + status: mangaDetail.Metadata.Status, + summary: mangaDetail.Metadata.Summary, + synonyms: mangaDetail.Metadata.Synonyms, + tags: mangaDetail.Metadata.Tags, + urls: mangaDetail.Metadata.URLs, + }, + }, }, }); @@ -142,7 +173,11 @@ export const mangaRouter = t.router({ }, take: 10, include: { - manga: true, + manga: { + include: { + metadata: true, + }, + }, }, }); }), diff --git a/src/server/utils/mangal.ts b/src/server/utils/mangal.ts index 230a67b..3eb9e4e 100644 --- a/src/server/utils/mangal.ts +++ b/src/server/utils/mangal.ts @@ -8,7 +8,7 @@ interface IOutput { Manga: Manga[]; } -interface Manga { +export interface Manga { Name: string; URL: string; Index: number; @@ -105,7 +105,7 @@ export const getChaptersFromRemote = async (source: string, title: string): Prom return []; }; -export const getMangaDetail = async (source: string, title: string) => { +export const getMangaDetail = async (source: string, title: string): Promise => { try { const { stdout, command } = await execa('mangal', [ 'inline',