@goscribe/server 1.0.11 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ANALYSIS_PROGRESS_SPEC.md +463 -0
- package/PROGRESS_QUICK_REFERENCE.md +239 -0
- package/dist/lib/ai-session.d.ts +20 -9
- package/dist/lib/ai-session.js +316 -80
- package/dist/lib/auth.d.ts +35 -2
- package/dist/lib/auth.js +88 -15
- package/dist/lib/env.d.ts +32 -0
- package/dist/lib/env.js +46 -0
- package/dist/lib/errors.d.ts +33 -0
- package/dist/lib/errors.js +78 -0
- package/dist/lib/inference.d.ts +4 -1
- package/dist/lib/inference.js +9 -11
- package/dist/lib/logger.d.ts +62 -0
- package/dist/lib/logger.js +342 -0
- package/dist/lib/podcast-prompts.d.ts +43 -0
- package/dist/lib/podcast-prompts.js +135 -0
- package/dist/lib/pusher.d.ts +1 -0
- package/dist/lib/pusher.js +14 -2
- package/dist/lib/storage.d.ts +3 -3
- package/dist/lib/storage.js +51 -47
- package/dist/lib/validation.d.ts +51 -0
- package/dist/lib/validation.js +64 -0
- package/dist/routers/_app.d.ts +697 -111
- package/dist/routers/_app.js +5 -0
- package/dist/routers/auth.d.ts +11 -1
- package/dist/routers/chat.d.ts +11 -1
- package/dist/routers/flashcards.d.ts +205 -6
- package/dist/routers/flashcards.js +144 -66
- package/dist/routers/members.d.ts +165 -0
- package/dist/routers/members.js +531 -0
- package/dist/routers/podcast.d.ts +78 -63
- package/dist/routers/podcast.js +330 -393
- package/dist/routers/studyguide.d.ts +11 -1
- package/dist/routers/worksheets.d.ts +124 -13
- package/dist/routers/worksheets.js +123 -50
- package/dist/routers/workspace.d.ts +213 -26
- package/dist/routers/workspace.js +303 -181
- package/dist/server.js +12 -4
- package/dist/services/flashcard-progress.service.d.ts +183 -0
- package/dist/services/flashcard-progress.service.js +383 -0
- package/dist/services/flashcard.service.d.ts +183 -0
- package/dist/services/flashcard.service.js +224 -0
- package/dist/services/podcast-segment-reorder.d.ts +0 -0
- package/dist/services/podcast-segment-reorder.js +107 -0
- package/dist/services/podcast.service.d.ts +0 -0
- package/dist/services/podcast.service.js +326 -0
- package/dist/services/worksheet.service.d.ts +0 -0
- package/dist/services/worksheet.service.js +295 -0
- package/dist/trpc.d.ts +13 -2
- package/dist/trpc.js +55 -6
- package/dist/types/index.d.ts +126 -0
- package/dist/types/index.js +1 -0
- package/package.json +3 -2
- package/prisma/schema.prisma +142 -4
- package/src/lib/ai-session.ts +356 -85
- package/src/lib/auth.ts +113 -19
- package/src/lib/env.ts +59 -0
- package/src/lib/errors.ts +92 -0
- package/src/lib/inference.ts +11 -11
- package/src/lib/logger.ts +405 -0
- package/src/lib/pusher.ts +15 -3
- package/src/lib/storage.ts +56 -51
- package/src/lib/validation.ts +75 -0
- package/src/routers/_app.ts +5 -0
- package/src/routers/chat.ts +2 -23
- package/src/routers/flashcards.ts +108 -24
- package/src/routers/members.ts +586 -0
- package/src/routers/podcast.ts +385 -420
- package/src/routers/worksheets.ts +117 -35
- package/src/routers/workspace.ts +328 -195
- package/src/server.ts +13 -4
- package/src/services/flashcard-progress.service.ts +541 -0
- package/src/trpc.ts +59 -6
- package/src/types/index.ts +165 -0
- package/AUTH_FRONTEND_SPEC.md +0 -21
- package/CHAT_FRONTEND_SPEC.md +0 -474
- package/DATABASE_SETUP.md +0 -165
- package/MEETINGSUMMARY_FRONTEND_SPEC.md +0 -28
- package/PODCAST_FRONTEND_SPEC.md +0 -595
- package/STUDYGUIDE_FRONTEND_SPEC.md +0 -18
- package/WORKSHEETS_FRONTEND_SPEC.md +0 -26
- package/WORKSPACE_FRONTEND_SPEC.md +0 -47
- package/test-ai-integration.js +0 -134
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import type { PrismaClient } from '@prisma/client';
|
|
2
|
+
import type { CreateFlashcardInput } from '../types/index.js';
|
|
3
|
+
export declare class FlashcardService {
|
|
4
|
+
private db;
|
|
5
|
+
constructor(db: PrismaClient);
|
|
6
|
+
jsonToFlashcards(json: string): Promise<any>;
|
|
7
|
+
/**
|
|
8
|
+
* List all flashcard sets for a workspace
|
|
9
|
+
*/
|
|
10
|
+
listFlashcardSets(workspaceId: string, userId: string): Promise<({
|
|
11
|
+
flashcards: {
|
|
12
|
+
id: string;
|
|
13
|
+
createdAt: Date;
|
|
14
|
+
artifactId: string;
|
|
15
|
+
order: number;
|
|
16
|
+
front: string;
|
|
17
|
+
back: string;
|
|
18
|
+
tags: string[];
|
|
19
|
+
}[];
|
|
20
|
+
} & {
|
|
21
|
+
id: string;
|
|
22
|
+
createdAt: Date;
|
|
23
|
+
updatedAt: Date;
|
|
24
|
+
title: string;
|
|
25
|
+
description: string | null;
|
|
26
|
+
workspaceId: string;
|
|
27
|
+
type: import("@prisma/client").$Enums.ArtifactType;
|
|
28
|
+
isArchived: boolean;
|
|
29
|
+
generating: boolean;
|
|
30
|
+
generatingMetadata: import("@prisma/client/runtime/library").JsonValue | null;
|
|
31
|
+
difficulty: import("@prisma/client").$Enums.Difficulty | null;
|
|
32
|
+
estimatedTime: string | null;
|
|
33
|
+
imageObjectKey: string | null;
|
|
34
|
+
createdById: string | null;
|
|
35
|
+
})[]>;
|
|
36
|
+
/**
|
|
37
|
+
* Get a single flashcard set
|
|
38
|
+
*/
|
|
39
|
+
getFlashcardSet(setId: string, userId: string): Promise<{
|
|
40
|
+
flashcards: {
|
|
41
|
+
id: string;
|
|
42
|
+
createdAt: Date;
|
|
43
|
+
artifactId: string;
|
|
44
|
+
order: number;
|
|
45
|
+
front: string;
|
|
46
|
+
back: string;
|
|
47
|
+
tags: string[];
|
|
48
|
+
}[];
|
|
49
|
+
} & {
|
|
50
|
+
id: string;
|
|
51
|
+
createdAt: Date;
|
|
52
|
+
updatedAt: Date;
|
|
53
|
+
title: string;
|
|
54
|
+
description: string | null;
|
|
55
|
+
workspaceId: string;
|
|
56
|
+
type: import("@prisma/client").$Enums.ArtifactType;
|
|
57
|
+
isArchived: boolean;
|
|
58
|
+
generating: boolean;
|
|
59
|
+
generatingMetadata: import("@prisma/client/runtime/library").JsonValue | null;
|
|
60
|
+
difficulty: import("@prisma/client").$Enums.Difficulty | null;
|
|
61
|
+
estimatedTime: string | null;
|
|
62
|
+
imageObjectKey: string | null;
|
|
63
|
+
createdById: string | null;
|
|
64
|
+
}>;
|
|
65
|
+
/**
|
|
66
|
+
* Create a new flashcard set
|
|
67
|
+
*/
|
|
68
|
+
createFlashcardSet(data: {
|
|
69
|
+
workspaceId: string;
|
|
70
|
+
title: string;
|
|
71
|
+
userId: string;
|
|
72
|
+
flashcards?: CreateFlashcardInput[];
|
|
73
|
+
}): Promise<{
|
|
74
|
+
flashcards: {
|
|
75
|
+
id: string;
|
|
76
|
+
createdAt: Date;
|
|
77
|
+
artifactId: string;
|
|
78
|
+
order: number;
|
|
79
|
+
front: string;
|
|
80
|
+
back: string;
|
|
81
|
+
tags: string[];
|
|
82
|
+
}[];
|
|
83
|
+
} & {
|
|
84
|
+
id: string;
|
|
85
|
+
createdAt: Date;
|
|
86
|
+
updatedAt: Date;
|
|
87
|
+
title: string;
|
|
88
|
+
description: string | null;
|
|
89
|
+
workspaceId: string;
|
|
90
|
+
type: import("@prisma/client").$Enums.ArtifactType;
|
|
91
|
+
isArchived: boolean;
|
|
92
|
+
generating: boolean;
|
|
93
|
+
generatingMetadata: import("@prisma/client/runtime/library").JsonValue | null;
|
|
94
|
+
difficulty: import("@prisma/client").$Enums.Difficulty | null;
|
|
95
|
+
estimatedTime: string | null;
|
|
96
|
+
imageObjectKey: string | null;
|
|
97
|
+
createdById: string | null;
|
|
98
|
+
}>;
|
|
99
|
+
/**
|
|
100
|
+
* Update a flashcard set
|
|
101
|
+
*/
|
|
102
|
+
updateFlashcardSet(data: {
|
|
103
|
+
id: string;
|
|
104
|
+
title?: string;
|
|
105
|
+
userId: string;
|
|
106
|
+
flashcards?: (CreateFlashcardInput & {
|
|
107
|
+
id?: string;
|
|
108
|
+
})[];
|
|
109
|
+
}): Promise<{
|
|
110
|
+
flashcards: {
|
|
111
|
+
id: string;
|
|
112
|
+
createdAt: Date;
|
|
113
|
+
artifactId: string;
|
|
114
|
+
order: number;
|
|
115
|
+
front: string;
|
|
116
|
+
back: string;
|
|
117
|
+
tags: string[];
|
|
118
|
+
}[];
|
|
119
|
+
} & {
|
|
120
|
+
id: string;
|
|
121
|
+
createdAt: Date;
|
|
122
|
+
updatedAt: Date;
|
|
123
|
+
title: string;
|
|
124
|
+
description: string | null;
|
|
125
|
+
workspaceId: string;
|
|
126
|
+
type: import("@prisma/client").$Enums.ArtifactType;
|
|
127
|
+
isArchived: boolean;
|
|
128
|
+
generating: boolean;
|
|
129
|
+
generatingMetadata: import("@prisma/client/runtime/library").JsonValue | null;
|
|
130
|
+
difficulty: import("@prisma/client").$Enums.Difficulty | null;
|
|
131
|
+
estimatedTime: string | null;
|
|
132
|
+
imageObjectKey: string | null;
|
|
133
|
+
createdById: string | null;
|
|
134
|
+
}>;
|
|
135
|
+
/**
|
|
136
|
+
* Delete a flashcard set
|
|
137
|
+
*/
|
|
138
|
+
deleteFlashcardSet(setId: string, userId: string): Promise<{
|
|
139
|
+
success: boolean;
|
|
140
|
+
}>;
|
|
141
|
+
/**
|
|
142
|
+
* Add a flashcard to a set
|
|
143
|
+
*/
|
|
144
|
+
addFlashcard(data: {
|
|
145
|
+
setId: string;
|
|
146
|
+
userId: string;
|
|
147
|
+
flashcard: CreateFlashcardInput;
|
|
148
|
+
}): Promise<{
|
|
149
|
+
id: string;
|
|
150
|
+
createdAt: Date;
|
|
151
|
+
artifactId: string;
|
|
152
|
+
order: number;
|
|
153
|
+
front: string;
|
|
154
|
+
back: string;
|
|
155
|
+
tags: string[];
|
|
156
|
+
}>;
|
|
157
|
+
/**
|
|
158
|
+
* Update a flashcard
|
|
159
|
+
*/
|
|
160
|
+
updateFlashcard(data: {
|
|
161
|
+
flashcardId: string;
|
|
162
|
+
userId: string;
|
|
163
|
+
updates: Partial<CreateFlashcardInput>;
|
|
164
|
+
}): Promise<{
|
|
165
|
+
id: string;
|
|
166
|
+
createdAt: Date;
|
|
167
|
+
artifactId: string;
|
|
168
|
+
order: number;
|
|
169
|
+
front: string;
|
|
170
|
+
back: string;
|
|
171
|
+
tags: string[];
|
|
172
|
+
}>;
|
|
173
|
+
/**
|
|
174
|
+
* Delete a flashcard
|
|
175
|
+
*/
|
|
176
|
+
deleteFlashcard(flashcardId: string, userId: string): Promise<{
|
|
177
|
+
success: boolean;
|
|
178
|
+
}>;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Factory function to create flashcard service
|
|
182
|
+
*/
|
|
183
|
+
export declare function createFlashcardService(db: PrismaClient): FlashcardService;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { NotFoundError } from '../lib/errors.js';
|
|
2
|
+
export class FlashcardService {
|
|
3
|
+
constructor(db) {
|
|
4
|
+
this.db = db;
|
|
5
|
+
}
|
|
6
|
+
async jsonToFlashcards(json) {
|
|
7
|
+
const flashcards = JSON.parse(json);
|
|
8
|
+
return flashcards.map((card) => ({
|
|
9
|
+
front: card.front,
|
|
10
|
+
back: card.back,
|
|
11
|
+
}));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* List all flashcard sets for a workspace
|
|
15
|
+
*/
|
|
16
|
+
async listFlashcardSets(workspaceId, userId) {
|
|
17
|
+
return this.db.artifact.findMany({
|
|
18
|
+
where: {
|
|
19
|
+
workspaceId,
|
|
20
|
+
type: 'FLASHCARD_SET',
|
|
21
|
+
workspace: { ownerId: userId },
|
|
22
|
+
},
|
|
23
|
+
include: {
|
|
24
|
+
flashcards: {
|
|
25
|
+
orderBy: { order: 'asc' },
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
orderBy: { updatedAt: 'desc' },
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get a single flashcard set
|
|
33
|
+
*/
|
|
34
|
+
async getFlashcardSet(setId, userId) {
|
|
35
|
+
const flashcardSet = await this.db.artifact.findFirst({
|
|
36
|
+
where: {
|
|
37
|
+
id: setId,
|
|
38
|
+
type: 'FLASHCARD_SET',
|
|
39
|
+
workspace: { ownerId: userId },
|
|
40
|
+
},
|
|
41
|
+
include: {
|
|
42
|
+
flashcards: {
|
|
43
|
+
orderBy: { order: 'asc' },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
if (!flashcardSet) {
|
|
48
|
+
throw new NotFoundError('Flashcard set');
|
|
49
|
+
}
|
|
50
|
+
return flashcardSet;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Create a new flashcard set
|
|
54
|
+
*/
|
|
55
|
+
async createFlashcardSet(data) {
|
|
56
|
+
// Verify workspace ownership
|
|
57
|
+
const workspace = await this.db.workspace.findFirst({
|
|
58
|
+
where: {
|
|
59
|
+
id: data.workspaceId,
|
|
60
|
+
ownerId: data.userId,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
if (!workspace) {
|
|
64
|
+
throw new NotFoundError('Workspace');
|
|
65
|
+
}
|
|
66
|
+
const { flashcards, ...setData } = data;
|
|
67
|
+
return this.db.artifact.create({
|
|
68
|
+
data: {
|
|
69
|
+
workspaceId: data.workspaceId,
|
|
70
|
+
type: 'FLASHCARD_SET',
|
|
71
|
+
title: data.title,
|
|
72
|
+
createdById: data.userId,
|
|
73
|
+
flashcards: flashcards
|
|
74
|
+
? {
|
|
75
|
+
create: flashcards.map((card, index) => ({
|
|
76
|
+
...card,
|
|
77
|
+
order: card.order ?? index,
|
|
78
|
+
})),
|
|
79
|
+
}
|
|
80
|
+
: undefined,
|
|
81
|
+
},
|
|
82
|
+
include: {
|
|
83
|
+
flashcards: {
|
|
84
|
+
orderBy: { order: 'asc' },
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Update a flashcard set
|
|
91
|
+
*/
|
|
92
|
+
async updateFlashcardSet(data) {
|
|
93
|
+
const { id, flashcards, userId, ...updateData } = data;
|
|
94
|
+
// Verify ownership
|
|
95
|
+
const existingSet = await this.db.artifact.findFirst({
|
|
96
|
+
where: {
|
|
97
|
+
id,
|
|
98
|
+
type: 'FLASHCARD_SET',
|
|
99
|
+
workspace: { ownerId: userId },
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
if (!existingSet) {
|
|
103
|
+
throw new NotFoundError('Flashcard set');
|
|
104
|
+
}
|
|
105
|
+
// Handle flashcards update if provided
|
|
106
|
+
if (flashcards) {
|
|
107
|
+
// Delete existing flashcards
|
|
108
|
+
await this.db.flashcard.deleteMany({
|
|
109
|
+
where: { artifactId: id },
|
|
110
|
+
});
|
|
111
|
+
// Create new flashcards
|
|
112
|
+
await this.db.flashcard.createMany({
|
|
113
|
+
data: flashcards.map((card, index) => ({
|
|
114
|
+
artifactId: id,
|
|
115
|
+
front: card.front,
|
|
116
|
+
back: card.back,
|
|
117
|
+
tags: card.tags || [],
|
|
118
|
+
order: card.order ?? index,
|
|
119
|
+
})),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return this.db.artifact.update({
|
|
123
|
+
where: { id },
|
|
124
|
+
data: updateData,
|
|
125
|
+
include: {
|
|
126
|
+
flashcards: {
|
|
127
|
+
orderBy: { order: 'asc' },
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Delete a flashcard set
|
|
134
|
+
*/
|
|
135
|
+
async deleteFlashcardSet(setId, userId) {
|
|
136
|
+
const deleted = await this.db.artifact.deleteMany({
|
|
137
|
+
where: {
|
|
138
|
+
id: setId,
|
|
139
|
+
type: 'FLASHCARD_SET',
|
|
140
|
+
workspace: { ownerId: userId },
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
if (deleted.count === 0) {
|
|
144
|
+
throw new NotFoundError('Flashcard set');
|
|
145
|
+
}
|
|
146
|
+
return { success: true };
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Add a flashcard to a set
|
|
150
|
+
*/
|
|
151
|
+
async addFlashcard(data) {
|
|
152
|
+
// Verify ownership
|
|
153
|
+
const set = await this.db.artifact.findFirst({
|
|
154
|
+
where: {
|
|
155
|
+
id: data.setId,
|
|
156
|
+
type: 'FLASHCARD_SET',
|
|
157
|
+
workspace: { ownerId: data.userId },
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
if (!set) {
|
|
161
|
+
throw new NotFoundError('Flashcard set');
|
|
162
|
+
}
|
|
163
|
+
// Get the next order number
|
|
164
|
+
const maxOrder = await this.db.flashcard.aggregate({
|
|
165
|
+
where: { artifactId: data.setId },
|
|
166
|
+
_max: { order: true },
|
|
167
|
+
});
|
|
168
|
+
return this.db.flashcard.create({
|
|
169
|
+
data: {
|
|
170
|
+
artifactId: data.setId,
|
|
171
|
+
front: data.flashcard.front,
|
|
172
|
+
back: data.flashcard.back,
|
|
173
|
+
tags: data.flashcard.tags || [],
|
|
174
|
+
order: data.flashcard.order ?? (maxOrder._max.order ?? 0) + 1,
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Update a flashcard
|
|
180
|
+
*/
|
|
181
|
+
async updateFlashcard(data) {
|
|
182
|
+
// Verify ownership
|
|
183
|
+
const flashcard = await this.db.flashcard.findFirst({
|
|
184
|
+
where: {
|
|
185
|
+
id: data.flashcardId,
|
|
186
|
+
artifact: {
|
|
187
|
+
type: 'FLASHCARD_SET',
|
|
188
|
+
workspace: { ownerId: data.userId },
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
if (!flashcard) {
|
|
193
|
+
throw new NotFoundError('Flashcard');
|
|
194
|
+
}
|
|
195
|
+
return this.db.flashcard.update({
|
|
196
|
+
where: { id: data.flashcardId },
|
|
197
|
+
data: data.updates,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Delete a flashcard
|
|
202
|
+
*/
|
|
203
|
+
async deleteFlashcard(flashcardId, userId) {
|
|
204
|
+
const flashcard = await this.db.flashcard.findFirst({
|
|
205
|
+
where: {
|
|
206
|
+
id: flashcardId,
|
|
207
|
+
artifact: { workspace: { ownerId: userId } },
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
if (!flashcard) {
|
|
211
|
+
throw new NotFoundError('Flashcard');
|
|
212
|
+
}
|
|
213
|
+
await this.db.flashcard.delete({
|
|
214
|
+
where: { id: flashcardId },
|
|
215
|
+
});
|
|
216
|
+
return { success: true };
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Factory function to create flashcard service
|
|
221
|
+
*/
|
|
222
|
+
export function createFlashcardService(db) {
|
|
223
|
+
return new FlashcardService(db);
|
|
224
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// import type { PrismaClient } from '@prisma/client';
|
|
3
|
+
// import { NotFoundError } from '../lib/errors.js';
|
|
4
|
+
// export interface ReorderSegmentData {
|
|
5
|
+
// id: string;
|
|
6
|
+
// newOrder: number;
|
|
7
|
+
// }
|
|
8
|
+
// export class PodcastSegmentReorderService {
|
|
9
|
+
// constructor(private db: PrismaClient) {}
|
|
10
|
+
// /**
|
|
11
|
+
// * Reorder podcast segments and recalculate start times
|
|
12
|
+
// */
|
|
13
|
+
// async reorderSegments(data: {
|
|
14
|
+
// episodeId: string;
|
|
15
|
+
// userId: string;
|
|
16
|
+
// newOrder: ReorderSegmentData[];
|
|
17
|
+
// }) {
|
|
18
|
+
// const { episodeId, userId, newOrder } = data;
|
|
19
|
+
// // Verify ownership
|
|
20
|
+
// const episode = await this.db.artifact.findFirst({
|
|
21
|
+
// where: {
|
|
22
|
+
// id: episodeId,
|
|
23
|
+
// type: 'PODCAST_EPISODE',
|
|
24
|
+
// workspace: { ownerId: userId },
|
|
25
|
+
// },
|
|
26
|
+
// include: {
|
|
27
|
+
// podcastSegments: {
|
|
28
|
+
// orderBy: { order: 'asc' },
|
|
29
|
+
// },
|
|
30
|
+
// },
|
|
31
|
+
// });
|
|
32
|
+
// if (!episode) {
|
|
33
|
+
// throw new NotFoundError('Podcast episode');
|
|
34
|
+
// }
|
|
35
|
+
// // Validate all segment IDs exist
|
|
36
|
+
// const segmentIds = episode.podcastSegments.map((s) => s.id);
|
|
37
|
+
// const invalidIds = newOrder.filter((item) => !segmentIds.includes(item.id));
|
|
38
|
+
// if (invalidIds.length > 0) {
|
|
39
|
+
// throw new Error(`Invalid segment IDs: ${invalidIds.map((i) => i.id).join(', ')}`);
|
|
40
|
+
// }
|
|
41
|
+
// // Validate order values are sequential
|
|
42
|
+
// const orderValues = newOrder.map((item) => item.newOrder).sort((a, b) => a - b);
|
|
43
|
+
// const expectedOrder = Array.from({ length: newOrder.length }, (_, i) => i + 1);
|
|
44
|
+
// if (JSON.stringify(orderValues) !== JSON.stringify(expectedOrder)) {
|
|
45
|
+
// throw new Error('Order values must be sequential starting from 1');
|
|
46
|
+
// }
|
|
47
|
+
// return this.db.$transaction(async (tx) => {
|
|
48
|
+
// // Update each segment's order
|
|
49
|
+
// for (const item of newOrder) {
|
|
50
|
+
// await tx.podcastSegment.update({
|
|
51
|
+
// where: { id: item.id },
|
|
52
|
+
// data: { order: item.newOrder },
|
|
53
|
+
// });
|
|
54
|
+
// }
|
|
55
|
+
// // Get all segments in new order
|
|
56
|
+
// const reorderedSegments = await tx.podcastSegment.findMany({
|
|
57
|
+
// where: { artifactId: episodeId },
|
|
58
|
+
// orderBy: { order: 'asc' },
|
|
59
|
+
// });
|
|
60
|
+
// // Recalculate start times
|
|
61
|
+
// let currentTime = 0;
|
|
62
|
+
// for (const segment of reorderedSegments) {
|
|
63
|
+
// await tx.podcastSegment.update({
|
|
64
|
+
// where: { id: segment.id },
|
|
65
|
+
// data: { startTime: currentTime },
|
|
66
|
+
// });
|
|
67
|
+
// currentTime += segment.duration;
|
|
68
|
+
// }
|
|
69
|
+
// // Update total duration in latest version
|
|
70
|
+
// const latestVersion = await tx.artifactVersion.findFirst({
|
|
71
|
+
// where: { artifactId: episodeId },
|
|
72
|
+
// orderBy: { version: 'desc' },
|
|
73
|
+
// });
|
|
74
|
+
// if (latestVersion) {
|
|
75
|
+
// const metadata = latestVersion.data as any;
|
|
76
|
+
// if (metadata) {
|
|
77
|
+
// metadata.totalDuration = currentTime;
|
|
78
|
+
// // Create new version with updated metadata
|
|
79
|
+
// await tx.artifactVersion.create({
|
|
80
|
+
// data: {
|
|
81
|
+
// artifactId: episodeId,
|
|
82
|
+
// version: latestVersion.version + 1,
|
|
83
|
+
// content: latestVersion.content,
|
|
84
|
+
// data: metadata,
|
|
85
|
+
// createdById: userId,
|
|
86
|
+
// },
|
|
87
|
+
// });
|
|
88
|
+
// }
|
|
89
|
+
// }
|
|
90
|
+
// // Update artifact timestamp
|
|
91
|
+
// await tx.artifact.update({
|
|
92
|
+
// where: { id: episodeId },
|
|
93
|
+
// data: { updatedAt: new Date() },
|
|
94
|
+
// });
|
|
95
|
+
// return {
|
|
96
|
+
// totalDuration: currentTime,
|
|
97
|
+
// segmentsReordered: reorderedSegments.length,
|
|
98
|
+
// };
|
|
99
|
+
// });
|
|
100
|
+
// }
|
|
101
|
+
// }
|
|
102
|
+
// /**
|
|
103
|
+
// * Factory function
|
|
104
|
+
// */
|
|
105
|
+
// export function createPodcastSegmentReorderService(db: PrismaClient) {
|
|
106
|
+
// return new PodcastSegmentReorderService(db);
|
|
107
|
+
// }
|
|
File without changes
|