This commit is contained in:
2026-01-13 23:02:07 +08:00
parent a1e42127e6
commit 804baa64b2
71 changed files with 658 additions and 925 deletions

View File

@@ -0,0 +1,30 @@
import { ValidateError } from "@/lib/errors";
import { TSharedItem } from "@/shared";
import z from "zod";
const DictionaryActionInputDtoSchema = z.object({
text: z.string().min(1, 'Empty text.').max(30, 'Text too long.'),
queryLang: z.string().min(1, 'Query lang too short.').max(20, 'Query lang too long.'),
forceRelook: z.boolean(),
definitionLang: z.string().min(1, 'Definition lang too short.').max(20, 'Definition lang too long.'),
userId: z.string().optional()
});
export type DictionaryActionInputDto = z.infer<typeof DictionaryActionInputDtoSchema>;
export const validateDictionaryActionInput = (dto: DictionaryActionInputDto): DictionaryActionInputDto => {
const result = DictionaryActionInputDtoSchema.safeParse(dto);
if (result.success) return result.data;
const errorMessages = result.error.issues.map((issue) =>
`${issue.path.join('.')}: ${issue.message}`
).join('; ');
throw new ValidateError(`Validation failed: ${errorMessages}`);
};
export type DictionaryActionOutputDto = {
message: string,
success: boolean;
data?: TSharedItem;
};

View File

@@ -0,0 +1,27 @@
"use server";
import { DictionaryActionInputDto, DictionaryActionOutputDto, validateDictionaryActionInput } from "./dictionary-action-dto";
import { ValidateError } from "@/lib/errors";
import { lookUpService } from "./dictionary-service";
export const lookUpDictionaryAction = async (dto: DictionaryActionInputDto): Promise<DictionaryActionOutputDto> => {
try {
return {
message: 'success',
success: true,
data: await lookUpService(validateDictionaryActionInput(dto))
};
} catch (e) {
if (e instanceof ValidateError) {
return {
success: false,
message: e.message
};
}
console.log(e);
return {
success: false,
message: 'Unknown error occured.'
};
}
};

View File

@@ -0,0 +1,38 @@
import { TSharedItem } from "@/shared";
export type CreateDictionaryLookUpInputDto = {
userId?: string;
text: string;
queryLang: string;
definitionLang: string;
dictionaryItemId?: number;
};
export type SelectLastLookUpResultOutputDto = TSharedItem & {id: number} | null;
export type CreateDictionaryItemInputDto = {
standardForm: string;
queryLang: string;
definitionLang: string;
};
export type CreateDictionaryEntryInputDto = {
itemId: number;
ipa?: string;
definition: string;
partOfSpeech?: string;
example: string;
};
export type CreateDictionaryEntryWithoutItemIdInputDto = {
ipa?: string;
definition: string;
partOfSpeech?: string;
example: string;
};
export type SelectLastLookUpResultInputDto = {
text: string,
queryLang: string,
definitionLang: string;
};

View File

@@ -0,0 +1,86 @@
import { stringNormalize } from "@/utils/string";
import {
CreateDictionaryEntryInputDto,
CreateDictionaryEntryWithoutItemIdInputDto,
CreateDictionaryItemInputDto,
CreateDictionaryLookUpInputDto,
SelectLastLookUpResultInputDto,
SelectLastLookUpResultOutputDto,
} from "./dictionary-repository-dto";
import prisma from "@/lib/db";
export async function selectLastLookUpResult(dto: SelectLastLookUpResultInputDto): Promise<SelectLastLookUpResultOutputDto> {
const result = await prisma.dictionaryLookUp.findFirst({
where: {
normalizedText: stringNormalize(dto.text),
queryLang: dto.queryLang,
definitionLang: dto.definitionLang,
dictionaryItemId: {
not: null
}
},
include: {
dictionaryItem: {
include: {
entries: true
}
}
},
orderBy: {
createdAt: 'desc'
}
});
if (result && result.dictionaryItem) {
const item = result.dictionaryItem;
return {
id: item.id,
standardForm: item.standardForm,
entries: item.entries.filter(v => !!v).map(v => {
return {
ipa: v.ipa || undefined,
definition: v.definition,
partOfSpeech: v.partOfSpeech || undefined,
example: v.example
};
})
};
}
return null;
}
export async function createLookUp(content: CreateDictionaryLookUpInputDto) {
return (await prisma.dictionaryLookUp.create({
data: { ...content, normalizedText: stringNormalize(content.text) }
})).id;
}
export async function createLookUpWithItemAndEntries(
itemData: CreateDictionaryItemInputDto,
lookUpData: CreateDictionaryLookUpInputDto,
entries: CreateDictionaryEntryWithoutItemIdInputDto[]
) {
return await prisma.$transaction(async (tx) => {
const item = await tx.dictionaryItem.create({
data: itemData
});
await tx.dictionaryLookUp.create({
data: {
...lookUpData,
normalizedText: stringNormalize(lookUpData.text),
dictionaryItemId: item.id
}
});
for (const entry of entries) {
await tx.dictionaryEntry.create({
data: {
...entry,
itemId: item.id
}
});
}
return item.id;
});
}

View File

@@ -0,0 +1,11 @@
import { TSharedItem } from "@/shared";
export type LookUpServiceInputDto = {
text: string,
queryLang: string,
definitionLang: string,
forceRelook: boolean,
userId?: string;
};
export type LookUpServiceOutputDto = TSharedItem;

View File

@@ -0,0 +1,61 @@
import { executeDictionaryLookup } from "@/lib/bigmodel/dictionary";
import { createLookUp, createLookUpWithItemAndEntries, selectLastLookUpResult } from "./dictionary-repository";
import { LookUpServiceInputDto } from "./dictionary-service-dto";
export const lookUpService = async (dto: LookUpServiceInputDto) => {
const {
text,
queryLang,
userId,
definitionLang,
forceRelook
} = dto;
const lastLookUpResult = await selectLastLookUpResult({
text,
queryLang,
definitionLang,
});
if (forceRelook || !lastLookUpResult) {
const response = await executeDictionaryLookup(
text,
queryLang,
definitionLang
);
// 使用事务确保数据一致性
createLookUpWithItemAndEntries(
{
standardForm: response.standardForm,
queryLang,
definitionLang
},
{
userId,
text,
queryLang,
definitionLang,
},
response.entries
).catch(error => {
console.error('Failed to save dictionary data:', error);
});
return response;
} else {
createLookUp({
userId: userId,
text: text,
queryLang: queryLang,
definitionLang: definitionLang,
dictionaryItemId: lastLookUpResult.id
}).catch(error => {
console.error('Failed to save dictionary data:', error);
});
return {
standardForm: lastLookUpResult.standardForm,
entries: lastLookUpResult.entries
};
}
};

View File

@@ -0,0 +1,2 @@
export * from "./dictionary-action";
export * from "./dictionary-action-dto";