From c91d0fb4474e79272c9571134156adb286be52c8 Mon Sep 17 00:00:00 2001 From: Type-32 <87076491+Type-32@users.noreply.github.com> Date: Mon, 30 Sep 2024 14:10:23 +0800 Subject: [PATCH] feat: finished backend dashboard of categories and changed delete from cascading to unit Signed-off-by: Type-32 <87076491+Type-32@users.noreply.github.com> --- app/composables/useCategories.ts | 25 ++- app/layouts/DashboardLayout.vue | 5 + app/pages/admin/categories.vue | 171 ++++++++++++++++++ app/pages/admin/category/[categoryId].vue | 147 +++++++++++++++ .../20240930040125_nightly4/migration.sql | 27 +++ prisma/schema.prisma | 24 ++- server/api/v1/article/new.post.ts | 2 +- server/api/v1/categories.get.ts | 8 +- server/api/v1/category/delete.post.ts | 31 +++- server/api/v1/category/edit.post.ts | 24 ++- server/api/v1/category/gallery.get.ts | 40 ++++ server/api/v1/category/new.post.ts | 2 +- server/api/v1/gallery/delete.post.ts | 36 +++- server/api/v1/gallery/new.post.ts | 2 +- server/api/v1/match/new.post.ts | 3 +- server/api/v1/team/new.post.ts | 3 +- 16 files changed, 498 insertions(+), 52 deletions(-) create mode 100644 app/pages/admin/categories.vue create mode 100644 app/pages/admin/category/[categoryId].vue create mode 100644 prisma/migrations/20240930040125_nightly4/migration.sql create mode 100644 server/api/v1/category/gallery.get.ts diff --git a/app/composables/useCategories.ts b/app/composables/useCategories.ts index cfd86a5..ca907a7 100644 --- a/app/composables/useCategories.ts +++ b/app/composables/useCategories.ts @@ -1,12 +1,12 @@ -import type {Category} from "@prisma/client"; +import type {Category, Gallery, Media} from "@prisma/client"; import {$fetch} from "ofetch"; export const useCategories = () => { const getCategories = async () =>{ - return useFetch('/api/v1/categories') + return await $fetch('/api/v1/categories') } const getCategory = async (ids: number[]) => { - return useFetch('/api/v1/categories', { + return await $fetch('/api/v1/categories', { method: 'GET', params: { ids: ids @@ -14,7 +14,7 @@ export const useCategories = () => { }) } const newCategory = async (name: string, token: string) => { - return $fetch('/api/v1/category/new', { + return await $fetch('/api/v1/category/new', { method: 'POST', body: { name: name @@ -25,7 +25,7 @@ export const useCategories = () => { }) } const deleteCategory = async (ids: number[], token: string) => { - return $fetch('/api/v1/category/delete', { + return await $fetch('/api/v1/category/delete', { method: 'POST', body: { ids: ids @@ -35,13 +35,12 @@ export const useCategories = () => { } }) } - const editCategory = async (id: number, name: string, published: boolean, galleries: number[], token: string) => { - return $fetch('/api/v1/category/edit', { + const editCategory = async (id: number, name: string, galleries: number[], token: string) => { + return await $fetch('/api/v1/category/edit', { method: 'POST', body: { id: id, name: name, - published: published, galleries: galleries }, headers: { @@ -50,11 +49,21 @@ export const useCategories = () => { }) } + const getCategoryGalleries = async (id: number) => { + return await $fetch(`/api/v1/category/gallery`, { + method: 'GET', + query: { + id: id + } + }) + } + return { getCategory, getCategories, editCategory, deleteCategory, newCategory, + getCategoryGalleries } } \ No newline at end of file diff --git a/app/layouts/DashboardLayout.vue b/app/layouts/DashboardLayout.vue index 7ede094..895ef8a 100644 --- a/app/layouts/DashboardLayout.vue +++ b/app/layouts/DashboardLayout.vue @@ -21,6 +21,11 @@ const links = [ icon: 'i-lucide-image', to: '/admin/galleries' }, + { + label: 'Categories', + icon: 'i-lucide-archive', + to: '/admin/categories' + }, { label: 'Media Library', icon: 'i-lucide-folder', diff --git a/app/pages/admin/categories.vue b/app/pages/admin/categories.vue new file mode 100644 index 0000000..28638eb --- /dev/null +++ b/app/pages/admin/categories.vue @@ -0,0 +1,171 @@ + + + + + \ No newline at end of file diff --git a/app/pages/admin/category/[categoryId].vue b/app/pages/admin/category/[categoryId].vue new file mode 100644 index 0000000..4dc627f --- /dev/null +++ b/app/pages/admin/category/[categoryId].vue @@ -0,0 +1,147 @@ + + + + + \ No newline at end of file diff --git a/prisma/migrations/20240930040125_nightly4/migration.sql b/prisma/migrations/20240930040125_nightly4/migration.sql new file mode 100644 index 0000000..0acfa73 --- /dev/null +++ b/prisma/migrations/20240930040125_nightly4/migration.sql @@ -0,0 +1,27 @@ +/* + Warnings: + + - You are about to drop the column `categoryId` on the `Gallery` table. All the data in the column will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "Gallery" DROP CONSTRAINT "Gallery_categoryId_fkey"; + +-- AlterTable +ALTER TABLE "Gallery" DROP COLUMN "categoryId"; + +-- CreateTable +CREATE TABLE "CategoryOnGalleries" ( + "galleryId" INTEGER NOT NULL, + "categoryId" INTEGER NOT NULL, + "assignedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "assignedBy" TEXT NOT NULL, + + CONSTRAINT "CategoryOnGalleries_pkey" PRIMARY KEY ("galleryId","categoryId") +); + +-- AddForeignKey +ALTER TABLE "CategoryOnGalleries" ADD CONSTRAINT "CategoryOnGalleries_galleryId_fkey" FOREIGN KEY ("galleryId") REFERENCES "Gallery"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "CategoryOnGalleries" ADD CONSTRAINT "CategoryOnGalleries_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 873ba0c..cbcc978 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -60,8 +60,8 @@ model Category { createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt - name String - galleries Gallery[] + name String + CategoryOnGalleries CategoryOnGalleries[] } model Gallery { @@ -69,11 +69,10 @@ model Gallery { createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt - name String - published Boolean @default(false) - Category Category? @relation(fields: [categoryId], references: [id]) - categoryId Int? - medias MediaOnGalleries[] + name String + published Boolean @default(false) + medias MediaOnGalleries[] + CategoryOnGalleries CategoryOnGalleries[] } model Media { @@ -100,6 +99,17 @@ model MediaOnGalleries { @@id([galleryId, mediaId]) } +model CategoryOnGalleries { + gallery Gallery @relation(fields: [galleryId], references: [id]) + galleryId Int // relation scalar field (used in the `@relation` attribute above) + category Category @relation(fields: [categoryId], references: [id]) + categoryId Int // relation scalar field (used in the `@relation` attribute above) + assignedAt DateTime @default(now()) + assignedBy String + + @@id([galleryId, categoryId]) +} + model Token { id Int @id @default(autoincrement()) createdAt DateTime @default(now()) diff --git a/server/api/v1/article/new.post.ts b/server/api/v1/article/new.post.ts index f7c3a97..eecb710 100644 --- a/server/api/v1/article/new.post.ts +++ b/server/api/v1/article/new.post.ts @@ -5,7 +5,7 @@ const prisma = new PrismaClient() export default defineEventHandler(async (event) => { const body = await readBody(event); const header = getHeader(event, 'Authorization') - console.log(header) + // console.log(header) if (!body.title || !body.slug || !body.description || !body.content || !header) { return sendError(event, createError({ statusCode: 400, statusMessage: 'Requires full parameters or headers' })); diff --git a/server/api/v1/categories.get.ts b/server/api/v1/categories.get.ts index 10d7cf2..94f5c06 100644 --- a/server/api/v1/categories.get.ts +++ b/server/api/v1/categories.get.ts @@ -22,13 +22,7 @@ export default defineEventHandler(async (event) => { } }) } else { - data = await prisma.category.findMany({ - select: { - id: true, - name: true, - galleries: true, - } - }) + data = await prisma.category.findMany() } return data diff --git a/server/api/v1/category/delete.post.ts b/server/api/v1/category/delete.post.ts index 8254a48..2369087 100644 --- a/server/api/v1/category/delete.post.ts +++ b/server/api/v1/category/delete.post.ts @@ -4,7 +4,6 @@ import { v4 as uuidv4 } from 'uuid'; import jwt from 'jsonwebtoken'; import { setCookie } from 'h3'; import { PrismaClient } from '@prisma/client' -import useAuth from "~/composables/useAuth"; import useServerAuth from "~/composables/useServerAuth"; const prisma = new PrismaClient() @@ -22,16 +21,28 @@ export default defineEventHandler(async (event) => { return sendError(event, createError({ statusCode: 403, statusMessage: 'Unauthorized; Please re-login.'})); } - let {data, error} = await prisma.gallery.deleteMany({ - where: { - id: { - in: (body.ids as number[]) - }, - } - }) + const data = await prisma.$transaction(async (prisma) => { + // First, delete all CategoryOnGalleries entries for these categories + await prisma.categoryOnGalleries.deleteMany({ + where: { + categoryId: { + in: body.ids as number[] + } + } + }); - if (error) - return sendError(event, createError({statusCode: 401, statusMessage: 'Gallery not found' })); + // Then, delete the categories + return prisma.category.deleteMany({ + where: { + id: { + in: body.ids as number[] + } + } + }); + }); + + if (!data) + return sendError(event, createError({statusCode: 401, statusMessage: 'Category not found' })); return data; }); \ No newline at end of file diff --git a/server/api/v1/category/edit.post.ts b/server/api/v1/category/edit.post.ts index 10fe7b5..ef029b8 100644 --- a/server/api/v1/category/edit.post.ts +++ b/server/api/v1/category/edit.post.ts @@ -26,18 +26,30 @@ export default defineEventHandler(async (event) => { id: body.id, }, data: { - updatedAt: new Date().toISOString(), + updatedAt: new Date(), name: body.name, - galleries: { - connect: body.galleries.map(galleryId => ({ - id: galleryId, - })), + CategoryOnGalleries: { + deleteMany: {}, // Remove all existing connections + create: body.galleries.map(galleryId => ({ + assignedAt: new Date(), + assignedBy: 'user', // Or you might want to use the authenticated user's info here + gallery: { + connect: { id: galleryId } + } + })) }, + }, + include: { + CategoryOnGalleries: { + include: { + gallery: true + } + } } }) if (!edit) - return sendError(event, createError({statusCode: 401, statusMessage: 'Gallery not found' })); + return sendError(event, createError({statusCode: 401, statusMessage: 'Category not found' })); return edit; }); \ No newline at end of file diff --git a/server/api/v1/category/gallery.get.ts b/server/api/v1/category/gallery.get.ts new file mode 100644 index 0000000..841a24c --- /dev/null +++ b/server/api/v1/category/gallery.get.ts @@ -0,0 +1,40 @@ +import {CategoryOnGalleries, PrismaClient} from '@prisma/client' + +const prisma = new PrismaClient() +export default defineEventHandler(async (event) => { + const query = getQuery(event) + try { + let pageIndex: number = 0 + let pageAmount: number = 90 + if (query.pagination != null && query.paginatePerPage != null) { + pageIndex = parseInt(query.pagination as any) + pageAmount = parseInt(query.paginatePerPage as any) + } + let data: CategoryOnGalleries[] = [] + if (query.id != null) { + const queryId = parseInt(query.id as any as string) + data = await prisma.categoryOnGalleries.findMany({ + where: { + galleryId: queryId + } + }) + } + + return await prisma.gallery.findMany({ + where: { + id: { + in: data.map(arr => arr.galleryId) + } + } + }) + } catch (error: any) { + if (error) { + console.log(`${event.toString()} -> Error at ${error.message}`) + + return sendError(event, createError({ + status: error.code as any as number, + message: error.message, + })) + } + } +}) \ No newline at end of file diff --git a/server/api/v1/category/new.post.ts b/server/api/v1/category/new.post.ts index 3e05dd7..fa1a294 100644 --- a/server/api/v1/category/new.post.ts +++ b/server/api/v1/category/new.post.ts @@ -5,7 +5,7 @@ const prisma = new PrismaClient() export default defineEventHandler(async (event) => { const body = await readBody<{name: string}>(event); const header = getHeader(event, 'Authorization') - console.log(header) + // console.log(header) if (!body.name || !header) { return sendError(event, createError({ statusCode: 400, statusMessage: 'Requires full parameters or headers' })); diff --git a/server/api/v1/gallery/delete.post.ts b/server/api/v1/gallery/delete.post.ts index fff648c..3bca1f9 100644 --- a/server/api/v1/gallery/delete.post.ts +++ b/server/api/v1/gallery/delete.post.ts @@ -21,13 +21,35 @@ export default defineEventHandler(async (event) => { return sendError(event, createError({ statusCode: 403, statusMessage: 'Unauthorized; Please re-login.'})); } - let data = await prisma.gallery.deleteMany({ - where: { - id: { - in: (body.ids as number[]) - }, - } - }) + // Use a transaction to ensure all related entries are deleted before deleting galleries + const data = await prisma.$transaction(async (prisma) => { + // First, delete all CategoryOnGalleries entries for these galleries + await prisma.categoryOnGalleries.deleteMany({ + where: { + galleryId: { + in: body.ids + } + } + }); + + // Then, delete all MediaOnGalleries entries for these galleries + await prisma.mediaOnGalleries.deleteMany({ + where: { + galleryId: { + in: body.ids + } + } + }); + + // Finally, delete the galleries + return prisma.gallery.deleteMany({ + where: { + id: { + in: body.ids + } + } + }); + }); if (!data) return sendError(event, createError({statusCode: 401, statusMessage: 'Gallery not found' })); diff --git a/server/api/v1/gallery/new.post.ts b/server/api/v1/gallery/new.post.ts index b94d273..afcca82 100644 --- a/server/api/v1/gallery/new.post.ts +++ b/server/api/v1/gallery/new.post.ts @@ -5,7 +5,7 @@ const prisma = new PrismaClient() export default defineEventHandler(async (event) => { const body = await readBody<{name: string}>(event); const header = getHeader(event, 'Authorization') - console.log(header) + // console.log(header) if (!body.name || !header) { return sendError(event, createError({ statusCode: 400, statusMessage: 'Requires full parameters or headers' })); diff --git a/server/api/v1/match/new.post.ts b/server/api/v1/match/new.post.ts index 2772847..5478ef6 100644 --- a/server/api/v1/match/new.post.ts +++ b/server/api/v1/match/new.post.ts @@ -4,14 +4,13 @@ import { v4 as uuidv4 } from 'uuid'; import jwt from 'jsonwebtoken'; import { setCookie } from 'h3'; import { PrismaClient } from '@prisma/client' -import useAuth from "~/composables/useAuth"; import useServerAuth from "~/composables/useServerAuth"; const prisma = new PrismaClient() export default defineEventHandler(async (event) => { const {home, guest, homeScore, guestScore} = await readBody(event); const header = getHeader(event, 'Authorization') - console.log(header) + // console.log(header) if (!home || !guest || !header) { return sendError(event, createError({ statusCode: 400, statusMessage: 'Requires full parameters or headers' })); diff --git a/server/api/v1/team/new.post.ts b/server/api/v1/team/new.post.ts index ccfdf56..ef5fdfc 100644 --- a/server/api/v1/team/new.post.ts +++ b/server/api/v1/team/new.post.ts @@ -4,14 +4,13 @@ import { v4 as uuidv4 } from 'uuid'; import jwt from 'jsonwebtoken'; import { setCookie } from 'h3'; import { PrismaClient } from '@prisma/client' -import useAuth from "~/composables/useAuth"; import useServerAuth from "~/composables/useServerAuth"; const prisma = new PrismaClient() export default defineEventHandler(async (event) => { const {name, desc, abbv, banner} = await readBody(event); const header = getHeader(event, 'Authorization') - console.log(header) + // console.log(header) // TODO Impl. Banner if (!name || !abbv || !header) {