From 0aec69e1753497d0c543d9bb5b0364c4e1a99ade Mon Sep 17 00:00:00 2001 From: blinko Date: Thu, 12 Dec 2024 12:05:57 +0800 Subject: [PATCH] fix: improve mutli user logic #289 --- .../20241212033352_0_23_3/migration.sql | 5 ++ prisma/schema.prisma | 3 + prisma/seed.ts | 18 ++--- .../BlinkoSettings/BasicSetting.tsx | 36 +++++----- src/pages/api/auth/[...nextauth].ts | 12 +++- src/pages/signup.tsx | 2 +- src/server/plugins/ai.ts | 7 +- src/server/plugins/ai/aiModelFactory.ts | 4 +- src/server/plugins/files.ts | 10 +-- src/server/routers/ai.ts | 4 +- src/server/routers/config.ts | 16 +++-- src/server/routers/helper/index.ts | 2 +- src/server/routers/note.ts | 66 +++++++++---------- src/server/routers/tag.ts | 41 +++++++----- src/server/routers/task.ts | 10 +-- src/server/routers/user.ts | 11 ++-- src/store/blinkoStore.tsx | 9 ++- 17 files changed, 146 insertions(+), 110 deletions(-) create mode 100644 prisma/migrations/20241212033352_0_23_3/migration.sql diff --git a/prisma/migrations/20241212033352_0_23_3/migration.sql b/prisma/migrations/20241212033352_0_23_3/migration.sql new file mode 100644 index 00000000..574a02c6 --- /dev/null +++ b/prisma/migrations/20241212033352_0_23_3/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "tag" ADD COLUMN "accountId" INTEGER; + +-- AddForeignKey +ALTER TABLE "tag" ADD CONSTRAINT "tag_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "accounts"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 122ff005..a708c675 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -24,6 +24,7 @@ model accounts { updatedAt DateTime @updatedAt @db.Timestamptz(6) notes notes[] configs config[] + tags tag[] } model attachments { @@ -75,9 +76,11 @@ model tag { name String @default("") @db.VarChar icon String @default("") @db.VarChar parent Int @default(0) + accountId Int? createdAt DateTime @default(now()) @db.Timestamptz(6) updatedAt DateTime @updatedAt @db.Timestamptz(6) tagsToNote tagsToNote[] + account accounts? @relation(fields: [accountId], references: [id]) } model tagsToNote { diff --git a/prisma/seed.ts b/prisma/seed.ts index 8c3a66dc..363ab3f8 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -277,19 +277,11 @@ async function main() { } }) - //v0.12.11 config add user columns - // const configs = await prisma.config.findMany(); - // const admin = await prisma.accounts.findFirst({ where: { role: 'superadmin' } }) - // if (admin) { - // for (const config of configs) { - // if (!config.userId) { - // await prisma.config.update({ - // where: { id: config.id }, - // data: { userId: admin.id } - // }); - // } - // } - // } + //v0.23.3 + const tagsWithoutAccount = await prisma.tag.findMany({ where: { accountId: null } }) + for (const tag of tagsWithoutAccount) { + await prisma.tag.update({ where: { id: tag.id }, data: { accountId: accounts[0]?.id } }) + } } main() diff --git a/src/components/BlinkoSettings/BasicSetting.tsx b/src/components/BlinkoSettings/BasicSetting.tsx index 97c475d8..8af9cd2a 100644 --- a/src/components/BlinkoSettings/BasicSetting.tsx +++ b/src/components/BlinkoSettings/BasicSetting.tsx @@ -176,24 +176,26 @@ export const BasicSetting = observer(() => { user.canRegister.call() }} />} /> + { + user.role == 'superadmin' && + Webhook} + rightContent={<> + store.webhookEndpoint = e.target.value} + onBlur={async () => { + await PromiseCall(api.config.update.mutate({ + key: 'webhookEndpoint', + value: store.webhookEndpoint + })) + }} + className="w-[150px] md:w-[300px]" + /> + } /> - Webhook} - rightContent={<> - store.webhookEndpoint = e.target.value} - onBlur={async () => { - await PromiseCall(api.config.update.mutate({ - key: 'webhookEndpoint', - value: store.webhookEndpoint - })) - }} - className="w-[150px] md:w-[300px]" - /> - } /> - + } {t('two-factor-authentication')}} rightContent={ diff --git a/src/pages/api/auth/[...nextauth].ts b/src/pages/api/auth/[...nextauth].ts index ebb6d18d..bcd8dc3e 100644 --- a/src/pages/api/auth/[...nextauth].ts +++ b/src/pages/api/auth/[...nextauth].ts @@ -43,8 +43,16 @@ export default NextAuth({ } const user = correctUsers![0]!; - const config = await getGlobalConfig({ id: user.id.toString() }) - + const config = await getGlobalConfig({ + ctx: { + id: user.id.toString(), + role: user.role as 'superadmin' | 'user', + name: user.name, + sub: user.id.toString(), + exp: 0, + iat: 0 + } + }) // 2fa verification if (credentials?.isSecondStep === 'true') { diff --git a/src/pages/signup.tsx b/src/pages/signup.tsx index 82a8d90b..2248e0ca 100644 --- a/src/pages/signup.tsx +++ b/src/pages/signup.tsx @@ -100,7 +100,7 @@ export default function Component() { return RootStore.Get(ToastPlugin).error(t('the-two-passwords-are-inconsistent')) } try { - await api.users.createAdmin.mutate({ name: user, password }) + await api.users.register.mutate({ name: user, password }) RootStore.Get(ToastPlugin).success(t('create-successfully-is-about-to-jump-to-the-login')) setTimeout(() => { router.push('/signin') diff --git a/src/server/plugins/ai.ts b/src/server/plugins/ai.ts index 54b6722d..0a61bae6 100644 --- a/src/server/plugins/ai.ts +++ b/src/server/plugins/ai.ts @@ -21,6 +21,7 @@ import { FaissStore } from '@langchain/community/vectorstores/faiss'; import { BaseDocumentLoader } from '@langchain/core/document_loaders/base'; import { FileService } from './files'; import { AiPrompt } from './ai/aiPrompt'; +import { Context } from '../context'; //https://js.langchain.com/docs/introduction/ //https://smith.langchain.com/onboarding @@ -303,7 +304,7 @@ export class AiService { return conversationMessage } - static async enhanceQuery({ query }: { query: string }) { + static async enhanceQuery({ query, ctx }: { query: string, ctx: Context }) { const { VectorStore } = await AiModelFactory.GetProvider() const config = await AiModelFactory.globalConfig() const results = await VectorStore.similaritySearchWithScore(query, 20); @@ -319,6 +320,7 @@ export class AiService { const notes = await prisma.notes.findMany({ where: { id: { in: filteredResultsWithScore.map(i => i.doc.metadata?.noteId).filter(i => !!i) }, + accountId: Number(ctx.id) }, include: { tags: { include: { tag: true } }, attachments: true } }) @@ -331,7 +333,7 @@ export class AiService { return sortedNotes; } - static async completions({ question, conversations }: { question: string, conversations: { role: string, content: string }[] }) { + static async completions({ question, conversations, ctx }: { question: string, conversations: { role: string, content: string }[], ctx: Context }) { try { const { LLM } = await AiModelFactory.GetProvider() let searchRes = await AiService.similaritySearch({ question }) @@ -340,6 +342,7 @@ export class AiService { if (searchRes && searchRes.length != 0) { notes = await prisma.notes.findMany({ where: { + accountId: Number(ctx.id), id: { in: _.uniqWith(searchRes.map(i => i.metadata?.noteId)).filter(i => !!i) as number[] } diff --git a/src/server/plugins/ai/aiModelFactory.ts b/src/server/plugins/ai/aiModelFactory.ts index 7bfa250f..12bc0dbd 100644 --- a/src/server/plugins/ai/aiModelFactory.ts +++ b/src/server/plugins/ai/aiModelFactory.ts @@ -10,7 +10,7 @@ import { OllamaModelProvider } from "./ollamaModelProvider" export class AiModelFactory { static async globalConfig() { return cache.wrap('globalConfig', async () => { - return await getGlobalConfig() + return await getGlobalConfig({ useAdmin: true }) }, { ttl: 1000 }) } @@ -34,7 +34,7 @@ export class AiModelFactory { TokenTextSplitter: provider.TokenTextSplitter() } } - + if (globalConfig.aiModelProvider == 'Ollama') { const provider = new OllamaModelProvider({ globalConfig }) return { diff --git a/src/server/plugins/files.ts b/src/server/plugins/files.ts index 06cda2a0..48b9bbed 100644 --- a/src/server/plugins/files.ts +++ b/src/server/plugins/files.ts @@ -10,7 +10,7 @@ import { existsSync } from "fs"; export class FileService { public static async getS3Client() { - const config = await getGlobalConfig(); + const config = await getGlobalConfig({ useAdmin: true }); return cache.wrap(`${config.s3Endpoint}-${config.s3Region}-${config.s3Bucket}-${config.s3AccessKeyId}-${config.s3AccessKeySecret}`, async () => { const s3ClientInstance = new S3Client({ endpoint: config.s3Endpoint, @@ -70,8 +70,8 @@ export class FileService { const extension = path.extname(originalName); const baseName = path.basename(originalName, extension); const timestamp = Date.now(); - const config = await getGlobalConfig(); - + const config = await getGlobalConfig({ useAdmin: true }); + if (config.objectStorage === 's3') { const { s3ClientInstance } = await this.getS3Client(); @@ -100,7 +100,7 @@ export class FileService { } static async deleteFile(api_attachment_path: string) { - const config = await getGlobalConfig(); + const config = await getGlobalConfig({ useAdmin: true }); if (api_attachment_path.includes('/api/s3file/')) { const { s3ClientInstance } = await this.getS3Client(); const fileName = api_attachment_path.replace('/api/s3file/', ""); @@ -130,7 +130,7 @@ export class FileService { } static async getFile(filePath: string) { - const config = await getGlobalConfig(); + const config = await getGlobalConfig({ useAdmin: true }); const fileName = filePath.replace('/api/file/', '').replace('/api/s3file/', ''); const tempPath = path.join(UPLOAD_FILE_PATH, path.basename(fileName)); diff --git a/src/server/routers/ai.ts b/src/server/routers/ai.ts index e152817b..426201b0 100644 --- a/src/server/routers/ai.ts +++ b/src/server/routers/ai.ts @@ -56,10 +56,10 @@ export const aiRouter = router({ question: z.string(), conversations: z.array(z.object({ role: z.string(), content: z.string() })) })) - .mutation(async function* ({ input }) { + .mutation(async function* ({ input, ctx }) { try { const { question, conversations } = input - const { result: responseStream, notes } = await AiService.completions({ question, conversations }) + const { result: responseStream, notes } = await AiService.completions({ question, conversations, ctx }) yield { notes } for await (const chunk of responseStream) { yield { context: chunk } diff --git a/src/server/routers/config.ts b/src/server/routers/config.ts index 9c95b1ec..3b2e5bfe 100644 --- a/src/server/routers/config.ts +++ b/src/server/routers/config.ts @@ -3,20 +3,25 @@ import { z } from 'zod'; import { prisma } from '../prisma'; import { GlobalConfig, ZConfigKey, ZConfigSchema, ZUserPerferConfigKey } from '../types'; import { configSchema } from '@/lib/prismaZodType'; +import { Context } from '../context'; -export const getGlobalConfig = async (ctx?: { id?: string }) => { +export const getGlobalConfig = async ({ ctx, useAdmin = false }: { ctx?: Context, useAdmin?: boolean }) => { const userId = Number(ctx?.id ?? 1); const configs = await prisma.config.findMany(); - + const isSuperAdmin = useAdmin ? true : ctx?.role === 'superadmin'; + const globalConfig = configs.reduce((acc, item) => { const config = item.config as { type: string, value: any }; + if (!isSuperAdmin && !item.userId) { + return acc; + } const isUserPreferConfig = ZUserPerferConfigKey.safeParse(item.key).success; if ((isUserPreferConfig && item.userId === userId) || (!isUserPreferConfig)) { acc[item.key] = config.value; } return acc; }, {} as Record); - + return globalConfig as GlobalConfig; }; @@ -26,7 +31,7 @@ export const configRouter = router({ .input(z.void()) .output(ZConfigSchema) .query(async function ({ ctx }) { - return await getGlobalConfig(ctx) + return await getGlobalConfig({ ctx }) }), update: authProcedure .meta({ openapi: { method: 'POST', path: '/v1/config/update', summary: 'Update user config', protect: true, tags: ['Config'] } }) @@ -46,6 +51,9 @@ export const configRouter = router({ } return await prisma.config.create({ data: { userId, key, config: { type: typeof value, value } } }) } else { + if (ctx.role !== 'superadmin') { + throw new Error('You are not allowed to update global config') + } // global config const hasKey = await prisma.config.findFirst({ where: { key } }) if (hasKey) { diff --git a/src/server/routers/helper/index.ts b/src/server/routers/helper/index.ts index 6a974cc8..d2c22cd8 100644 --- a/src/server/routers/helper/index.ts +++ b/src/server/routers/helper/index.ts @@ -5,7 +5,7 @@ import { getGlobalConfig } from "../config" export const SendWebhook = async (data: any, webhookType: string, ctx: { id: string }) => { try { - const globalConfig = await getGlobalConfig(ctx) + const globalConfig = await getGlobalConfig({ useAdmin: true }) if (globalConfig.webhookEndpoint) { await axios.post(globalConfig.webhookEndpoint, { data, webhookType, activityType: `blinko.note.${webhookType}` }) } diff --git a/src/server/routers/note.ts b/src/server/routers/note.ts index 15ada19b..63658cc9 100644 --- a/src/server/routers/note.ts +++ b/src/server/routers/note.ts @@ -5,7 +5,7 @@ import { Prisma } from '@prisma/client'; import { helper, TagTreeNode } from '@/lib/helper'; import { _ } from '@/lib/lodash'; import { NoteType } from '../types'; -import { attachmentsSchema, notesSchema, tagSchema, tagsToNoteSchema } from '@/lib/prismaZodType'; +import { attachmentsSchema, notesSchema, tagSchema, tagsToNoteSchema } from '@/lib/prismaZodType'; import { getGlobalConfig } from './config'; import { FileService } from '../plugins/files'; import { AiService } from '../plugins/ai'; @@ -51,16 +51,13 @@ export const noteRouter = router({ const { tagId, type, isArchived, isRecycle, searchText, page, size, orderBy, withFile, withoutTag, withLink, isUseAiQuery } = input if (isUseAiQuery && searchText?.trim() != '') { if (page == 1) { - return await AiService.enhanceQuery({ query: searchText! }) + return await AiService.enhanceQuery({ query: searchText!, ctx }) } else { return [] } } let where: Prisma.notesWhereInput = { - OR: [ - { accountId: Number(ctx.id) }, - { accountId: null } - ] + accountId: Number(ctx.id) } if (searchText != '') { @@ -97,7 +94,7 @@ export const noteRouter = router({ { content: { contains: 'https://', mode: 'insensitive' } } ]; } - const config = await getGlobalConfig() + const config = await getGlobalConfig({ ctx }) let timeOrderBy = config?.isOrderByCreateTime ? { createdAt: orderBy } : { updatedAt: orderBy } return await prisma.notes.findMany({ where, @@ -156,10 +153,10 @@ export const noteRouter = router({ referencedBy: z.array(z.object({ fromNoteId: z.number() })).optional() })) )) - .mutation(async function ({ input }) { + .mutation(async function ({ input, ctx }) { const { ids } = input return await prisma.notes.findMany({ - where: { id: { in: ids } }, + where: { id: { in: ids }, accountId: Number(ctx.id) }, include: { tags: { include: { tag: true } }, attachments: true, @@ -198,9 +195,9 @@ export const noteRouter = router({ z.object({ attachments: z.array(attachmentsSchema) }))])) - .mutation(async function ({ input }) { + .mutation(async function ({ input, ctx }) { const { id } = input - return await prisma.notes.findFirst({ where: { id }, include: { tags: true, attachments: true }, }) + return await prisma.notes.findFirst({ where: { id, accountId: Number(ctx.id) }, include: { tags: true, attachments: true }, }) }), dailyReviewNoteList: authProcedure .meta({ openapi: { method: 'GET', path: '/v1/note/daily-review-list', summary: 'Query daily review note list', protect: true, tags: ['Note'] } }) @@ -221,8 +218,8 @@ export const noteRouter = router({ .meta({ openapi: { method: 'POST', path: '/v1/note/review', summary: 'Review a note', protect: true, tags: ['Note'] } }) .input(z.object({ id: z.number() })) .output(z.union([z.null(), notesSchema])) - .mutation(async function ({ input }) { - return await prisma.notes.update({ where: { id: input.id }, data: { isReviewed: true } }) + .mutation(async function ({ input, ctx }) { + return await prisma.notes.update({ where: { id: input.id, accountId: Number(ctx.id) }, data: { isReviewed: true } }) }), upsert: authProcedure .meta({ openapi: { method: 'POST', path: '/v1/note/upsert', summary: 'Update or create note', protect: true, tags: ['Note'] } }) @@ -239,7 +236,6 @@ export const noteRouter = router({ })) .output(z.any()) .mutation(async function ({ input, ctx }) { - const globalConfig = await getGlobalConfig(ctx) let { id, isArchived, isRecycle, type, attachments, content, isTop, isShare, references } = input if (content != null) { content = content?.replace(/ /g, ' ')?.replace(/ \\/g, '')?.replace(/\\([#<>{}[\]|`*-_.])/g, '$1'); @@ -248,9 +244,9 @@ export const noteRouter = router({ let newTags: Prisma.tagCreateManyInput[] = [] const handleAddTags = async (tagTree: TagTreeNode[], parentTag: Prisma.tagCreateManyInput | undefined, noteId?: number) => { for (const i of tagTree) { - let hasTag = await prisma.tag.findFirst({ where: { name: i.name, parent: parentTag?.id ?? 0 } }) + let hasTag = await prisma.tag.findFirst({ where: { name: i.name, parent: parentTag?.id ?? 0, accountId: Number(ctx.id) } }) if (!hasTag) { - hasTag = await prisma.tag.create({ data: { name: i.name, parent: parentTag?.id ?? 0 } }) + hasTag = await prisma.tag.create({ data: { name: i.name, parent: parentTag?.id ?? 0, accountId: Number(ctx.id) } }) } if (noteId) { const hasRelation = await prisma.tagsToNote.findFirst({ where: { tag: hasTag, noteId } }) @@ -273,10 +269,10 @@ export const noteRouter = router({ } if (id) { - const note = await prisma.notes.update({ where: { id }, data: update }) + const note = await prisma.notes.update({ where: { id, accountId: Number(ctx.id) }, data: update }) if (content == null) return const oldTagsInThisNote = await prisma.tagsToNote.findMany({ where: { noteId: note.id }, include: { tag: true } }) - await handleAddTags(tagTree, undefined) + await handleAddTags(tagTree, undefined, note.id) const oldTags = oldTagsInThisNote.map(i => i.tag).filter(i => !!i) const oldTagsString = oldTags.map(i => `${i?.name}${i?.parent}`) const newTagsString = newTags.map(i => `${i?.name}${i?.parent}`) @@ -338,7 +334,7 @@ export const noteRouter = router({ const usingTags = (await prisma.tagsToNote.findMany({ where: { tagId: { in: allTagsIds } } })).map(i => i.tagId).filter(i => !!i) const needTobeDeledTags = _.difference(allTagsIds, usingTags); if (needTobeDeledTags) { - await prisma.tag.deleteMany({ where: { id: { in: needTobeDeledTags } } }) + await prisma.tag.deleteMany({ where: { id: { in: needTobeDeledTags }, accountId: Number(ctx.id) } }) } // insert not repeat attachments @@ -388,22 +384,22 @@ export const noteRouter = router({ ids: z.array(z.number()) })) .output(z.any()) - .mutation(async function ({ input }) { + .mutation(async function ({ input, ctx }) { const { type, isArchived, isRecycle, ids } = input const update: Prisma.notesUpdateInput = { ...(type !== -1 && { type }), ...(isArchived !== null && { isArchived }), ...(isRecycle !== null && { isRecycle }), } - return await prisma.notes.updateMany({ where: { id: { in: ids } }, data: update }) + return await prisma.notes.updateMany({ where: { id: { in: ids }, accountId: Number(ctx.id) }, data: update }) }), trashMany: authProcedure .meta({ openapi: { method: 'POST', path: '/v1/note/batch-trash', summary: 'Batch trash note', protect: true, tags: ['Note'] } }) .input(z.object({ ids: z.array(z.number()) })) .output(z.any()) - .mutation(async function ({ input }) { + .mutation(async function ({ input, ctx }) { const { ids } = input - return await prisma.notes.updateMany({ where: { id: { in: ids } }, data: { isRecycle: true } }) + return await prisma.notes.updateMany({ where: { id: { in: ids }, accountId: Number(ctx.id) }, data: { isRecycle: true } }) }), deleteMany: authProcedure.use(demoAuthMiddleware) .meta({ openapi: { method: 'POST', path: '/v1/note/batch-delete', summary: 'Batch delete note', protect: true, tags: ['Note'] } }) @@ -415,7 +411,7 @@ export const noteRouter = router({ .mutation(async function ({ input, ctx }) { const { ids } = input const notes = await prisma.notes.findMany({ - where: { id: { in: ids } }, + where: { id: { in: ids }, accountId: Number(ctx.id) }, include: { tags: { include: { tag: true } }, attachments: true, @@ -446,7 +442,7 @@ export const noteRouter = router({ })).map(i => i.tag?.id).filter(i => !!i) const needTobeDeledTags = _.difference(allTagsIds, usingTags); if (needTobeDeledTags?.length) { - await prisma.tag.deleteMany({ where: { id: { in: needTobeDeledTags } } }) + await prisma.tag.deleteMany({ where: { id: { in: needTobeDeledTags }, accountId: Number(ctx.id) } }) } if (note.attachments?.length) { @@ -465,7 +461,7 @@ export const noteRouter = router({ } await handleDeleteRelation() - await prisma.notes.deleteMany({ where: { id: { in: ids } } }) + await prisma.notes.deleteMany({ where: { id: { in: ids }, accountId: Number(ctx.id) } }) return { ok: true } }), addReference: authProcedure @@ -475,8 +471,8 @@ export const noteRouter = router({ toNoteId: z.number(), })) .output(z.any()) - .mutation(async function ({ input }) { - return await insertNoteReference(input) + .mutation(async function ({ input, ctx }) { + return await insertNoteReference({ ...input, accountId: Number(ctx.id) }) }), noteReferenceList: authProcedure .meta({ openapi: { method: 'POST', path: '/v1/note/reference-list', summary: 'Query note references', protect: true, tags: ['Note'] } }) @@ -490,7 +486,7 @@ export const noteRouter = router({ referenceCreatedAt: z.date() }) ))) - .mutation(async function ({ input }) { + .mutation(async function ({ input, ctx }) { const { noteId, type } = input; if (type === 'references') { @@ -545,10 +541,10 @@ export const noteRouter = router({ }), }) -let insertNoteReference = async ({ fromNoteId, toNoteId }) => { +let insertNoteReference = async ({ fromNoteId, toNoteId, accountId }) => { const [fromNote, toNote] = await Promise.all([ - prisma.notes.findUnique({ where: { id: fromNoteId } }), - prisma.notes.findUnique({ where: { id: toNoteId } }) + prisma.notes.findUnique({ where: { id: fromNoteId, accountId } }), + prisma.notes.findUnique({ where: { id: toNoteId, accountId } }) ]); if (!fromNote || !toNote) { @@ -563,6 +559,6 @@ let insertNoteReference = async ({ fromNoteId, toNoteId }) => { }); } -let deleteNoteReference = async ({ fromNoteId, toNoteId }) => { - return await prisma.noteReference.deleteMany({ where: { fromNoteId, toNoteId } }) -} +// let deleteNoteReference = async ({ fromNoteId, toNoteId, accountId }) => { +// return await prisma.noteReference.deleteMany({ where: { fromNoteId, toNoteId, accountId } }) +// } diff --git a/src/server/routers/tag.ts b/src/server/routers/tag.ts index 10a929d0..3bc4188c 100644 --- a/src/server/routers/tag.ts +++ b/src/server/routers/tag.ts @@ -12,16 +12,7 @@ export const tagRouter = router({ .query(async function ({ ctx }) { const tags = await prisma.tag.findMany({ where: { - tagsToNote: { - some: { - note: { - OR: [ - { accountId: Number(ctx.id) }, - { accountId: null } - ] - } - } - } + accountId: Number(ctx.id) }, distinct: ['id'] }); @@ -96,16 +87,32 @@ export const tagRouter = router({ id: z.number() })) .output(z.boolean()) - .mutation(async function ({ input }) { + .mutation(async function ({ input, ctx }) { const { id } = input - const tag = await prisma.tag.findFirst({ where: { id }, include: { tagsToNote: true } }) - const allNotesId = tag?.tagsToNote.map(i => i.noteId) ?? [] + const tag = await prisma.tag.findFirst({ + where: { + id, + accountId: Number(ctx.id) + }, + include: { tagsToNote: true } + }) + + if (!tag) return true + + const allNotesId = tag.tagsToNote.map(i => i.noteId) + for (const noteId of allNotesId) { const note = await prisma.notes.findFirst({ where: { id: noteId } }) - await prisma.notes.update({ where: { id: note!.id }, data: { content: note!.content.replace(new RegExp(`#${tag!.name}`, 'g'), '') } }) - await prisma.tagsToNote.deleteMany({ where: { tagId: tag!.id } }) + if (!note) continue + await prisma.notes.update({ + where: { id: note.id }, + data: { content: note.content.replace(new RegExp(`#${tag.name}`, 'g'), '') } + }) + await prisma.tagsToNote.deleteMany({ where: { tagId: tag.id } }) } + await prisma.tag.delete({ where: { id } }) + return true }), deleteTagWithAllNote: authProcedure.use(demoAuthMiddleware) @@ -121,8 +128,10 @@ export const tagRouter = router({ .output(z.boolean()) .mutation(async function ({ input, ctx }) { const { id } = input - const tag = await prisma.tag.findFirst({ where: { id }, include: { tagsToNote: true } }) + const tag = await prisma.tag.findFirst({ where: { id, accountId: Number(ctx.id) }, include: { tagsToNote: true } }) + console.log({ tag }) const allNotesId = tag?.tagsToNote.map(i => i.noteId) ?? [] + console.log(allNotesId) await userCaller(ctx).notes.deleteMany({ ids: allNotesId }) return true }), diff --git a/src/server/routers/task.ts b/src/server/routers/task.ts index 9915da9f..c1201591 100644 --- a/src/server/routers/task.ts +++ b/src/server/routers/task.ts @@ -1,4 +1,4 @@ -import { router, authProcedure, demoAuthMiddleware } from '../trpc'; +import { router, authProcedure, demoAuthMiddleware, superAdminAuthMiddleware } from '../trpc'; import { z } from 'zod'; import { prisma } from '../prisma'; import { DBJob } from '../plugins/dbjob'; @@ -13,14 +13,14 @@ import fs from 'fs'; export const taskRouter = router({ - list: authProcedure + list: authProcedure.use(superAdminAuthMiddleware) .meta({ openapi: { method: 'GET', path: '/v1/tasks/list', summary: 'Query user task list', protect: true, tags: ['Task'] } }) .input(z.void()) .output(z.array(scheduledTaskSchema)) .query(async () => { return await prisma.scheduledTask.findMany() }), - upsertTask: authProcedure + upsertTask: authProcedure.use(superAdminAuthMiddleware) .meta({ openapi: { method: 'GET', path: '/v1/tasks/upsert', summary: 'Upsert Task', protect: true, tags: ['Task'] } }) .input(z.object({ time: z.string().optional(), @@ -40,7 +40,7 @@ export const taskRouter = router({ return task == DBBAK_TASK_NAME ? await DBJob.SetCornTime(time) : await ArchiveJob.SetCornTime(time) } }), - importFromBlinko: authProcedure.use(demoAuthMiddleware) + importFromBlinko: authProcedure.use(demoAuthMiddleware).use(superAdminAuthMiddleware) .input(z.object({ filePath: z.string() })) @@ -62,7 +62,7 @@ export const taskRouter = router({ } }), - importFromMemos: authProcedure.use(demoAuthMiddleware) + importFromMemos: authProcedure.use(demoAuthMiddleware).use(superAdminAuthMiddleware) .input(z.object({ filePath: z.string() //xxxx.db })) diff --git a/src/server/routers/user.ts b/src/server/routers/user.ts index fe3c181f..63d3cb27 100644 --- a/src/server/routers/user.ts +++ b/src/server/routers/user.ts @@ -46,8 +46,11 @@ export const userRouter = router({ nickName: z.string(), token: z.string() })) - .query(async ({ input }) => { + .query(async ({ input, ctx }) => { const user = await prisma.accounts.findFirst({ where: { id: input.id } }) + if (user?.id !== Number(ctx.id) && user?.role !== 'superadmin') { + throw new TRPCError({ code: 'UNAUTHORIZED', message: 'You are not allowed to access this user' }) + } return { id: input.id, name: user?.name ?? '', @@ -69,11 +72,11 @@ export const userRouter = router({ return res?.config.value === true } }), - createAdmin: publicProcedure + register: publicProcedure .meta({ openapi: { - method: 'POST', path: '/v1/user/create-admin', summary: 'Create admin user', - description: 'Create admin user in first time', tags: ['User'] + method: 'POST', path: '/v1/user/register', summary: 'Register user or admin', + description: 'Register user or admin', tags: ['User'] } }) .input(z.object({ diff --git a/src/store/blinkoStore.tsx b/src/store/blinkoStore.tsx index b0ffd6c7..fddad933 100644 --- a/src/store/blinkoStore.tsx +++ b/src/store/blinkoStore.tsx @@ -134,7 +134,14 @@ export class BlinkoStore implements Store { task = new PromiseState({ function: async () => { - return await api.task.list.query() ?? [] + try { + if (RootStore.Get(UserStore).role == 'superadmin') { + return await api.task.list.query() ?? [] + } + return [] + } catch (error) { + return [] + } } })