refactor: remove obsolete project mode

This commit is contained in:
saturn
2026-03-25 15:39:16 +08:00
parent fd8f5f8635
commit ca5d8a58f7
47 changed files with 40 additions and 157 deletions

View File

@@ -0,0 +1,2 @@
ALTER TABLE `projects`
DROP COLUMN `mode`;

View File

@@ -353,7 +353,6 @@ model Project {
id String @id @default(uuid())
name String
description String? @db.Text
mode String @default("novel-promotion")
userId String
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt

View File

@@ -347,7 +347,6 @@ model Project {
id String @id @default(uuid())
name String
description String?
mode String @default("novel-promotion")
userId String
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt

View File

@@ -155,10 +155,7 @@ export default function WorkspacePage() {
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
...formData,
mode: 'novel-promotion' // 固定为 novel-promotion
})
body: JSON.stringify(formData)
})
if (response.ok) {

View File

@@ -11,7 +11,7 @@ export const POST = apiHandler(async (
const { projectId } = await context.params
const authResult = await requireProjectAuth(projectId)
if (isErrorResponse(authResult)) return authResult
const { session, project } = authResult
const { session } = authResult
const body = await request.json().catch(() => ({}))
const currentPrompt = typeof body?.currentPrompt === 'string' ? body.currentPrompt.trim() : ''
@@ -19,10 +19,6 @@ export const POST = apiHandler(async (
if (!currentPrompt || !modifyInstruction) {
throw new ApiError('INVALID_PARAMS')
}
if (project.mode !== 'novel-promotion') {
throw new ApiError('INVALID_PARAMS')
}
const panelId = typeof body?.panelId === 'string' ? body.panelId.trim() : ''
const episodeId = typeof body?.episodeId === 'string' ? body.episodeId.trim() : ''

View File

@@ -14,13 +14,9 @@ export const POST = apiHandler(async (
const { projectId } = await context.params
const authResult = await requireProjectAuth(projectId)
if (isErrorResponse(authResult)) return authResult
const { session, project } = authResult
const { session } = authResult
const body = await request.json().catch(() => ({}))
if (project.mode !== 'novel-promotion') {
throw new ApiError('INVALID_PARAMS')
}
const asyncTaskResponse = await maybeSubmitLLMTask({
request,
userId: session.user.id,

View File

@@ -16,11 +16,7 @@ export const POST = apiHandler(async (
include: { characters: true, locations: true },
})
if (isErrorResponse(authResult)) return authResult
const { session, project } = authResult
if (project.mode !== 'novel-promotion') {
throw new ApiError('INVALID_PARAMS')
}
const { session } = authResult
const asyncTaskResponse = await maybeSubmitLLMTask({
request,

View File

@@ -24,11 +24,7 @@ export const POST = apiHandler(async (
include: { characters: true, locations: true },
})
if (isErrorResponse(authResult)) return authResult
const { session, project } = authResult
if (project.mode !== 'novel-promotion') {
throw new ApiError('INVALID_PARAMS')
}
const { session } = authResult
const asyncTaskResponse = await maybeSubmitLLMTask({
request,

View File

@@ -14,11 +14,7 @@ export const POST = apiHandler(async (
const { projectId } = await params
const authResult = await requireProjectAuthLight(projectId)
if (isErrorResponse(authResult)) return authResult
const { session, project } = authResult
if (project.mode !== 'novel-promotion') {
throw new ApiError('INVALID_PARAMS')
}
const { session } = authResult
const body = await request.json().catch(() => ({}))
const content = typeof body?.content === 'string' ? body.content : ''

View File

@@ -276,10 +276,6 @@ export const PATCH = apiHandler(async (
const body = await request.json()
if (project.mode !== 'novel-promotion') {
throw new ApiError('INVALID_PARAMS')
}
const currentProjectConfig = await prisma.novelPromotionProject.findUnique({
where: { projectId },
select: {

View File

@@ -22,11 +22,7 @@ export const POST = apiHandler(async (
const authResult = await requireProjectAuth(projectId)
if (isErrorResponse(authResult)) return authResult
const { session, project } = authResult
if (project.mode !== 'novel-promotion') {
throw new ApiError('INVALID_PARAMS')
}
const { session } = authResult
const asyncTaskResponse = await maybeSubmitLLMTask({
request,

View File

@@ -22,11 +22,7 @@ export const POST = apiHandler(async (
include: { characters: true, locations: true },
})
if (isErrorResponse(authResult)) return authResult
const { session, project } = authResult
if (project.mode !== 'novel-promotion') {
throw new ApiError('INVALID_PARAMS')
}
const { session } = authResult
const asyncTaskResponse = await maybeSubmitLLMTask({
request,

View File

@@ -26,11 +26,7 @@ export const POST = apiHandler(async (
include: { characters: true, locations: true },
})
if (isErrorResponse(authResult)) return authResult
const { session, project } = authResult
if (project.mode !== 'novel-promotion') {
throw new ApiError('INVALID_PARAMS')
}
const { session } = authResult
const asyncTaskResponse = await maybeSubmitLLMTask({
request,

View File

@@ -22,11 +22,7 @@ export const POST = apiHandler(async (
const authResult = await requireProjectAuthLight(projectId)
if (isErrorResponse(authResult)) return authResult
const { session, project } = authResult
if (project.mode !== 'novel-promotion') {
throw new ApiError('INVALID_PARAMS')
}
const { session } = authResult
const asyncTaskResponse = await maybeSubmitLLMTask({
request,

View File

@@ -44,8 +44,8 @@ export const GET = apiHandler(async (
data: { lastAccessedAt: new Date() }
}).catch(err => _ulogError('更新访问时间失败:', err))
// 这个API只返回基础项目信息
// 模式特定的数据应该通过各自的API获取如 /api/novel-promotion/[projectId]
// 这个 API 只返回基础项目信息
// 项目附属业务数据通过各自的 API 获取(如 /api/novel-promotion/[projectId]
const projectWithSignedUrls = addSignedUrlsToProject(project)
return NextResponse.json({ project: projectWithSignedUrls })

View File

@@ -188,12 +188,11 @@ export const POST = apiHandler(async (request: NextRequest) => {
where: { userId: session.user.id }
})
// 创建基础项目mode 固定为 novel-promotion
// 创建基础项目
const project = await prisma.project.create({
data: {
name: name.trim(),
description: description?.trim() || null,
mode: 'novel-promotion',
userId: session.user.id
}
})

View File

@@ -113,7 +113,6 @@ export async function createHomeProjectLaunch({
body: JSON.stringify({
name: projectName,
description: storyText,
mode: 'novel-promotion',
}),
})

View File

@@ -1,29 +0,0 @@
import { ProjectMode } from '@/types/project'
// 重新导出 ProjectMode 类型,方便其他文件使用
export type { ProjectMode }
export interface ModeConfig {
id: ProjectMode
name: string
description: string
icon: string
color: string
available: boolean
}
export const PROJECT_MODE: ModeConfig = {
id: 'novel-promotion',
name: '小说推文',
description: '从小说生成推广短视频',
icon: 'N',
color: 'purple',
available: true
}
// 为了兼容性保留
export const PROJECT_MODES: ModeConfig[] = [PROJECT_MODE]
export function getModeConfig(mode: ProjectMode): ModeConfig | undefined {
return mode === 'novel-promotion' ? PROJECT_MODE : undefined
}

View File

@@ -30,15 +30,11 @@ export async function handleAnalyzeGlobalTask(job: Job<TaskJobData>) {
where: { id: projectId },
select: {
id: true,
mode: true,
},
})
if (!project) {
throw new Error('Project not found')
}
if (project.mode !== 'novel-promotion') {
throw new Error('Not a novel promotion project')
}
const novelData = await prisma.novelPromotionProject.findUnique({
where: { projectId },

View File

@@ -49,15 +49,11 @@ export async function handleAnalyzeNovelTask(job: Job<TaskJobData>) {
where: { id: projectId },
select: {
id: true,
mode: true,
},
})
if (!project) {
throw new Error('Project not found')
}
if (project.mode !== 'novel-promotion') {
throw new Error('Not a novel promotion project')
}
const novelData = await prisma.novelPromotionProject.findUnique({
where: { projectId },

View File

@@ -48,14 +48,11 @@ export async function handleClipsBuildTask(job: Job<TaskJobData>) {
const project = await prisma.project.findUnique({
where: { id: projectId },
select: { id: true, mode: true },
select: { id: true },
})
if (!project) {
throw new Error('Project not found')
}
if (project.mode !== 'novel-promotion') {
throw new Error('Not a novel promotion project')
}
const novelData = await prisma.novelPromotionProject.findUnique({
where: { projectId },

View File

@@ -67,15 +67,11 @@ export async function handleEpisodeSplitTask(job: Job<TaskJobData>) {
where: { id: projectId },
select: {
id: true,
mode: true,
},
})
if (!project) {
throw new Error('Project not found')
}
if (project.mode !== 'novel-promotion') {
throw new Error('Not a novel promotion project')
}
const novelProject = await prisma.novelPromotionProject.findFirst({
where: { projectId },

View File

@@ -33,15 +33,11 @@ export async function handleScreenplayConvertTask(job: Job<TaskJobData>) {
select: {
id: true,
name: true,
mode: true,
},
})
if (!project) {
throw new Error('Project not found')
}
if (project.mode !== 'novel-promotion') {
throw new Error('Not a novel promotion project')
}
const novelData = await prisma.novelPromotionProject.findUnique({
where: { projectId },

View File

@@ -82,15 +82,11 @@ export async function handleScriptToStoryboardTask(job: Job<TaskJobData>) {
select: {
id: true,
name: true,
mode: true,
},
})
if (!project) {
throw new Error('Project not found')
}
if (project.mode !== 'novel-promotion') {
throw new Error('Not a novel promotion project')
}
// Register project name for per-project log file routing
onProjectNameAvailable(projectId, project.name)

View File

@@ -77,15 +77,11 @@ export async function handleStoryToScriptTask(job: Job<TaskJobData>) {
select: {
id: true,
name: true,
mode: true,
},
})
if (!project) {
throw new Error('Project not found')
}
if (project.mode !== 'novel-promotion') {
throw new Error('Not a novel promotion project')
}
// Register project name for per-project log file routing
onProjectNameAvailable(projectId, project.name)

View File

@@ -36,15 +36,11 @@ export async function handleVoiceAnalyzeTask(job: Job<TaskJobData>) {
where: { id: projectId },
select: {
id: true,
mode: true,
},
})
if (!project) {
throw new Error('Project not found')
}
if (project.mode !== 'novel-promotion') {
throw new Error('Not a novel promotion project')
}
const novelPromotionData = await prisma.novelPromotionProject.findUnique({
where: { projectId },

View File

@@ -1,10 +1,5 @@
import type { CapabilitySelections } from '@/lib/model-config-contract'
// ============================================
// 项目模式类型
// ============================================
export type ProjectMode = 'novel-promotion'
// ============================================
// 基础项目类型
// ============================================
@@ -12,7 +7,6 @@ export interface BaseProject {
id: string
name: string
description: string | null
mode: ProjectMode
userId: string
createdAt: Date
updatedAt: Date

View File

@@ -81,7 +81,7 @@ export function installAuthMocks() {
if (state.projectAuthMode === 'not_found') return notFoundResponse()
return {
session: state.session,
project: { id: projectId, userId: state.session.user.id, name: 'project', mode: 'novel-promotion' },
project: { id: projectId, userId: state.session.user.id, name: 'project' },
novelData: { id: 'novel-data-id' },
}
},

View File

@@ -15,12 +15,11 @@ export async function createFixtureUser() {
})
}
export async function createFixtureProject(userId: string, mode: 'novel-promotion' | 'general' = 'novel-promotion') {
export async function createFixtureProject(userId: string) {
const id = suffix()
return await prisma.project.create({
data: {
userId,
mode,
name: `project_${id}`,
},
})

View File

@@ -68,14 +68,14 @@ vi.mock('@/lib/api-auth', () => {
if (!authState.authenticated) return unauthorized()
return {
session: { user: { id: 'user-1' } },
project: { id: projectId, userId: 'user-1', mode: 'novel-promotion' },
project: { id: projectId, userId: 'user-1' },
}
},
requireProjectAuthLight: async (projectId: string) => {
if (!authState.authenticated) return unauthorized()
return {
session: { user: { id: 'user-1' } },
project: { id: projectId, userId: 'user-1', mode: 'novel-promotion' },
project: { id: projectId, userId: 'user-1' },
}
},
}

View File

@@ -4,7 +4,6 @@ import { buildMockRequest } from '../../../helpers/request'
type AuthState = {
authenticated: boolean
projectMode: 'novel-promotion' | 'other'
}
type SubmitResult = {
@@ -28,7 +27,6 @@ type DirectRouteCase = {
const authState = vi.hoisted<AuthState>(() => ({
authenticated: true,
projectMode: 'novel-promotion',
}))
const submitTaskMock = vi.hoisted(() => vi.fn<(...args: unknown[]) => Promise<SubmitResult>>())
@@ -218,14 +216,14 @@ vi.mock('@/lib/api-auth', () => {
if (!authState.authenticated) return unauthorized()
return {
session: { user: { id: 'user-1' } },
project: { id: projectId, userId: 'user-1', mode: authState.projectMode },
project: { id: projectId, userId: 'user-1' },
}
},
requireProjectAuthLight: async (projectId: string) => {
if (!authState.authenticated) return unauthorized()
return {
session: { user: { id: 'user-1' } },
project: { id: projectId, userId: 'user-1', mode: authState.projectMode },
project: { id: projectId, userId: 'user-1' },
}
},
}
@@ -534,7 +532,6 @@ describe('api contract - direct submit routes (behavior)', () => {
beforeEach(() => {
vi.clearAllMocks()
authState.authenticated = true
authState.projectMode = 'novel-promotion'
let seq = 0
submitTaskMock.mockImplementation(async () => ({
taskId: `task-${++seq}`,

View File

@@ -5,7 +5,6 @@ import { buildMockRequest } from '../../../helpers/request'
type AuthState = {
authenticated: boolean
projectMode: 'novel-promotion' | 'other'
}
type LLMRouteCase = {
@@ -23,7 +22,6 @@ type RouteContext = {
const authState = vi.hoisted<AuthState>(() => ({
authenticated: true,
projectMode: 'novel-promotion',
}))
const maybeSubmitLLMTaskMock = vi.hoisted(() =>
@@ -77,14 +75,14 @@ vi.mock('@/lib/api-auth', () => {
if (!authState.authenticated) return unauthorized()
return {
session: { user: { id: 'user-1' } },
project: { id: projectId, userId: 'user-1', mode: authState.projectMode },
project: { id: projectId, userId: 'user-1' },
}
},
requireProjectAuthLight: async (projectId: string) => {
if (!authState.authenticated) return unauthorized()
return {
session: { user: { id: 'user-1' } },
project: { id: projectId, userId: 'user-1', mode: authState.projectMode },
project: { id: projectId, userId: 'user-1' },
}
},
}
@@ -329,7 +327,6 @@ describe('api contract - llm observe routes (behavior)', () => {
beforeEach(() => {
vi.clearAllMocks()
authState.authenticated = true
authState.projectMode = 'novel-promotion'
maybeSubmitLLMTaskMock.mockResolvedValue(
NextResponse.json({
success: true,

View File

@@ -4,7 +4,7 @@ import { buildMockRequest } from '../../../helpers/request'
const authMock = vi.hoisted(() => ({
requireProjectAuthLight: vi.fn(async () => ({
session: { user: { id: 'user-1', name: 'User 1' } },
project: { id: 'project-1', userId: 'user-1', mode: 'novel-promotion', name: 'Project 1' },
project: { id: 'project-1', userId: 'user-1', name: 'Project 1' },
})),
isErrorResponse: vi.fn((value: unknown) => value instanceof Response),
}))

View File

@@ -27,7 +27,7 @@ type StoryboardRecord = {
const authMock = vi.hoisted(() => ({
requireProjectAuthLight: vi.fn(async () => ({
session: { user: { id: 'user-1' } },
project: { id: 'project-1', userId: 'user-1', mode: 'novel-promotion' },
project: { id: 'project-1', userId: 'user-1' },
})),
isErrorResponse: vi.fn((value: unknown) => value instanceof Response),
}))

View File

@@ -28,7 +28,6 @@ const prismaMock = vi.hoisted(() => ({
id: 'project-1',
name: 'Test Project',
description: null,
mode: 'novel-promotion',
userId: 'user-1',
})),
},
@@ -60,6 +59,13 @@ describe('api specific - project create default audio model', () => {
const res = await mod.POST(req, routeContext)
expect(res.status).toBe(201)
expect(prismaMock.project.create).toHaveBeenCalledWith({
data: {
name: 'Test Project',
description: null,
userId: 'user-1',
},
})
expect(prismaMock.novelPromotionProject.create).toHaveBeenCalledWith({
data: expect.objectContaining({
projectId: 'project-1',

View File

@@ -4,7 +4,7 @@ import { buildMockRequest } from '../../../helpers/request'
const authMock = vi.hoisted(() => ({
requireProjectAuthLight: vi.fn(async () => ({
session: { user: { id: 'user-1' } },
project: { id: 'project-1', userId: 'user-1', mode: 'novel-promotion' },
project: { id: 'project-1', userId: 'user-1' },
})),
isErrorResponse: vi.fn((value: unknown) => value instanceof Response),
}))

View File

@@ -4,7 +4,7 @@ import { buildMockRequest } from '../../../helpers/request'
const authMock = vi.hoisted(() => ({
requireProjectAuthLight: vi.fn(async () => ({
session: { user: { id: 'user-1' } },
project: { id: 'project-1', userId: 'user-1', mode: 'novel-promotion' },
project: { id: 'project-1', userId: 'user-1' },
})),
isErrorResponse: vi.fn((value: unknown) => value instanceof Response),
}))

View File

@@ -14,7 +14,7 @@ const queueState = vi.hoisted(() => ({
const prismaMock = vi.hoisted(() => ({
project: {
findUnique: vi.fn(async () => ({ id: 'project-1', mode: 'novel-promotion' })),
findUnique: vi.fn(async () => ({ id: 'project-1' })),
},
novelPromotionProject: {
findFirst: vi.fn(async () => ({ id: 'np-project-1' })),

View File

@@ -45,7 +45,6 @@ describe('createHomeProjectLaunch', () => {
body: JSON.stringify({
name: '开场白',
description: '第一章内容',
mode: 'novel-promotion',
}),
})
expect(apiFetch).toHaveBeenNthCalledWith(2, '/api/novel-promotion/project-1', {

View File

@@ -104,7 +104,7 @@ describe('worker analyze-global behavior', () => {
beforeEach(() => {
vi.clearAllMocks()
prismaMock.project.findUnique.mockResolvedValue({ id: 'project-1', mode: 'novel-promotion' })
prismaMock.project.findUnique.mockResolvedValue({ id: 'project-1' })
prismaMock.novelPromotionProject.findUnique.mockResolvedValue({
id: 'np-project-1',

View File

@@ -85,7 +85,6 @@ describe('worker analyze-novel behavior', () => {
prismaMock.project.findUnique.mockResolvedValue({
id: 'project-1',
mode: 'novel-promotion',
})
prismaMock.novelPromotionProject.findUnique.mockResolvedValue({

View File

@@ -85,7 +85,7 @@ describe('worker clips-build behavior', () => {
beforeEach(() => {
vi.clearAllMocks()
prismaMock.project.findUnique.mockResolvedValue({ id: 'project-1', mode: 'novel-promotion' })
prismaMock.project.findUnique.mockResolvedValue({ id: 'project-1' })
prismaMock.novelPromotionProject.findUnique.mockResolvedValue({
id: 'np-project-1',

View File

@@ -4,7 +4,7 @@ import { TASK_TYPE, type TaskJobData } from '@/lib/task/types'
const prismaMock = vi.hoisted(() => ({
project: {
findUnique: vi.fn(async () => ({ id: 'project-1', mode: 'novel-promotion' })),
findUnique: vi.fn(async () => ({ id: 'project-1' })),
},
novelPromotionProject: {
findFirst: vi.fn(async () => ({ id: 'np-project-1' })),

View File

@@ -79,7 +79,6 @@ describe('worker screenplay-convert behavior', () => {
prismaMock.project.findUnique.mockResolvedValue({
id: 'project-1',
name: 'Project One',
mode: 'novel-promotion',
})
prismaMock.novelPromotionProject.findUnique.mockResolvedValue({

View File

@@ -231,7 +231,6 @@ describe('worker script-to-storyboard behavior', () => {
prismaMock.project.findUnique.mockResolvedValue({
id: 'project-1',
name: 'Project One',
mode: 'novel-promotion',
})
prismaMock.novelPromotionProject.findUnique.mockResolvedValue({

View File

@@ -124,7 +124,6 @@ describe('worker story-to-script behavior', () => {
prismaMock.project.findUnique.mockResolvedValue({
id: 'project-1',
name: 'Project One',
mode: 'novel-promotion',
})
prismaMock.novelPromotionProject.findUnique.mockResolvedValue({

View File

@@ -82,7 +82,7 @@ describe('worker voice-analyze behavior', () => {
txState.createdRows = []
txState.deletedWhereClauses = []
prismaMock.project.findUnique.mockResolvedValue({ id: 'project-1', mode: 'novel-promotion' })
prismaMock.project.findUnique.mockResolvedValue({ id: 'project-1' })
prismaMock.novelPromotionProject.findUnique.mockResolvedValue({
id: 'np-project-1',
analysisModel: 'llm::analysis-1',