@studious-lms/server 1.1.8 → 1.1.10

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.
@@ -0,0 +1,134 @@
1
+ import { z } from 'zod';
2
+ export declare const conversationRouter: import("@trpc/server").TRPCBuiltRouter<{
3
+ ctx: import("../trpc.js").Context;
4
+ meta: object;
5
+ errorShape: {
6
+ data: {
7
+ zodError: z.typeToFlattenedError<any, string> | null;
8
+ prismaError: import("../utils/prismaErrorHandler.js").PrismaErrorInfo | null;
9
+ code: import("@trpc/server").TRPC_ERROR_CODE_KEY;
10
+ httpStatus: number;
11
+ path?: string;
12
+ stack?: string;
13
+ };
14
+ message: string;
15
+ code: import("@trpc/server").TRPC_ERROR_CODE_NUMBER;
16
+ };
17
+ transformer: false;
18
+ }, import("@trpc/server").TRPCDecorateCreateRouterOptions<{
19
+ list: import("@trpc/server").TRPCQueryProcedure<{
20
+ input: void;
21
+ output: {
22
+ id: string;
23
+ type: import(".prisma/client").$Enums.ConversationType;
24
+ name: string | null;
25
+ createdAt: Date;
26
+ updatedAt: Date;
27
+ members: ({
28
+ user: {
29
+ id: string;
30
+ username: string;
31
+ profile: {
32
+ displayName: string | null;
33
+ profilePicture: string | null;
34
+ } | null;
35
+ };
36
+ } & {
37
+ id: string;
38
+ role: import(".prisma/client").$Enums.ConversationRole;
39
+ userId: string;
40
+ conversationId: string;
41
+ joinedAt: Date;
42
+ lastViewedAt: Date | null;
43
+ lastViewedMentionAt: Date | null;
44
+ })[];
45
+ lastMessage: {
46
+ sender: {
47
+ id: string;
48
+ username: string;
49
+ profile: {
50
+ displayName: string | null;
51
+ } | null;
52
+ };
53
+ } & {
54
+ id: string;
55
+ content: string;
56
+ createdAt: Date;
57
+ senderId: string;
58
+ conversationId: string;
59
+ };
60
+ unreadCount: number;
61
+ unreadMentionCount: number;
62
+ }[];
63
+ meta: object;
64
+ }>;
65
+ create: import("@trpc/server").TRPCMutationProcedure<{
66
+ input: {
67
+ type: "DM" | "GROUP";
68
+ memberIds: string[];
69
+ name?: string | undefined;
70
+ };
71
+ output: {
72
+ members: ({
73
+ user: {
74
+ id: string;
75
+ username: string;
76
+ profile: {
77
+ displayName: string | null;
78
+ profilePicture: string | null;
79
+ } | null;
80
+ };
81
+ } & {
82
+ id: string;
83
+ role: import(".prisma/client").$Enums.ConversationRole;
84
+ userId: string;
85
+ conversationId: string;
86
+ joinedAt: Date;
87
+ lastViewedAt: Date | null;
88
+ lastViewedMentionAt: Date | null;
89
+ })[];
90
+ } & {
91
+ type: import(".prisma/client").$Enums.ConversationType;
92
+ id: string;
93
+ name: string | null;
94
+ createdAt: Date;
95
+ updatedAt: Date;
96
+ displayInChat: boolean;
97
+ };
98
+ meta: object;
99
+ }>;
100
+ get: import("@trpc/server").TRPCQueryProcedure<{
101
+ input: {
102
+ conversationId: string;
103
+ };
104
+ output: {
105
+ members: ({
106
+ user: {
107
+ id: string;
108
+ username: string;
109
+ profile: {
110
+ displayName: string | null;
111
+ profilePicture: string | null;
112
+ } | null;
113
+ };
114
+ } & {
115
+ id: string;
116
+ role: import(".prisma/client").$Enums.ConversationRole;
117
+ userId: string;
118
+ conversationId: string;
119
+ joinedAt: Date;
120
+ lastViewedAt: Date | null;
121
+ lastViewedMentionAt: Date | null;
122
+ })[];
123
+ } & {
124
+ type: import(".prisma/client").$Enums.ConversationType;
125
+ id: string;
126
+ name: string | null;
127
+ createdAt: Date;
128
+ updatedAt: Date;
129
+ displayInChat: boolean;
130
+ };
131
+ meta: object;
132
+ }>;
133
+ }>>;
134
+ //# sourceMappingURL=conversation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../../src/routers/conversation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8R7B,CAAC"}
@@ -0,0 +1,267 @@
1
+ import { z } from 'zod';
2
+ import { createTRPCRouter, protectedProcedure } from '../trpc.js';
3
+ import { prisma } from '../lib/prisma.js';
4
+ import { TRPCError } from '@trpc/server';
5
+ export const conversationRouter = createTRPCRouter({
6
+ list: protectedProcedure.query(async ({ ctx }) => {
7
+ const userId = ctx.user.id;
8
+ const conversations = await prisma.conversation.findMany({
9
+ where: {
10
+ members: {
11
+ some: {
12
+ userId,
13
+ },
14
+ },
15
+ },
16
+ include: {
17
+ members: {
18
+ include: {
19
+ user: {
20
+ select: {
21
+ id: true,
22
+ username: true,
23
+ profile: {
24
+ select: {
25
+ displayName: true,
26
+ profilePicture: true,
27
+ },
28
+ },
29
+ },
30
+ },
31
+ },
32
+ },
33
+ messages: {
34
+ orderBy: {
35
+ createdAt: 'desc',
36
+ },
37
+ take: 1,
38
+ include: {
39
+ sender: {
40
+ select: {
41
+ id: true,
42
+ username: true,
43
+ profile: {
44
+ select: {
45
+ displayName: true,
46
+ },
47
+ },
48
+ },
49
+ },
50
+ },
51
+ },
52
+ },
53
+ orderBy: {
54
+ updatedAt: 'desc',
55
+ },
56
+ });
57
+ // Calculate unread counts for each conversation
58
+ const conversationsWithUnread = await Promise.all(conversations.map(async (conversation) => {
59
+ const userMembership = conversation.members.find(m => m.userId === userId);
60
+ const lastViewedAt = userMembership?.lastViewedAt;
61
+ const lastViewedMentionAt = userMembership?.lastViewedMentionAt;
62
+ // Count regular unread messages
63
+ const unreadCount = await prisma.message.count({
64
+ where: {
65
+ conversationId: conversation.id,
66
+ senderId: { not: userId },
67
+ ...(lastViewedAt && {
68
+ createdAt: { gt: lastViewedAt }
69
+ }),
70
+ },
71
+ });
72
+ // Count unread mentions
73
+ // Use the later of lastViewedAt or lastViewedMentionAt
74
+ // This means if user viewed conversation after mention, mention is considered read
75
+ const mentionCutoffTime = lastViewedMentionAt && lastViewedAt
76
+ ? (lastViewedMentionAt > lastViewedAt ? lastViewedMentionAt : lastViewedAt)
77
+ : (lastViewedMentionAt || lastViewedAt);
78
+ const unreadMentionCount = await prisma.mention.count({
79
+ where: {
80
+ userId,
81
+ message: {
82
+ conversationId: conversation.id,
83
+ senderId: { not: userId },
84
+ ...(mentionCutoffTime && {
85
+ createdAt: { gt: mentionCutoffTime }
86
+ }),
87
+ },
88
+ },
89
+ });
90
+ return {
91
+ id: conversation.id,
92
+ type: conversation.type,
93
+ name: conversation.name,
94
+ createdAt: conversation.createdAt,
95
+ updatedAt: conversation.updatedAt,
96
+ members: conversation.members,
97
+ lastMessage: conversation.messages[0] || null,
98
+ unreadCount,
99
+ unreadMentionCount,
100
+ };
101
+ }));
102
+ return conversationsWithUnread;
103
+ }),
104
+ create: protectedProcedure
105
+ .input(z.object({
106
+ type: z.enum(['DM', 'GROUP']),
107
+ name: z.string().optional(),
108
+ memberIds: z.array(z.string()),
109
+ }))
110
+ .mutation(async ({ input, ctx }) => {
111
+ const userId = ctx.user.id;
112
+ const { type, name, memberIds } = input;
113
+ // Validate input
114
+ if (type === 'GROUP' && !name) {
115
+ throw new TRPCError({
116
+ code: 'BAD_REQUEST',
117
+ message: 'Group conversations must have a name',
118
+ });
119
+ }
120
+ if (type === 'DM' && memberIds.length !== 1) {
121
+ throw new TRPCError({
122
+ code: 'BAD_REQUEST',
123
+ message: 'DM conversations must have exactly one other member',
124
+ });
125
+ }
126
+ // For DMs, check if conversation already exists
127
+ if (type === 'DM') {
128
+ const existingDM = await prisma.conversation.findFirst({
129
+ where: {
130
+ type: 'DM',
131
+ members: {
132
+ every: {
133
+ userId: {
134
+ in: [userId, memberIds[0]],
135
+ },
136
+ },
137
+ },
138
+ AND: {
139
+ members: {
140
+ some: {
141
+ userId,
142
+ },
143
+ },
144
+ },
145
+ },
146
+ include: {
147
+ members: {
148
+ include: {
149
+ user: {
150
+ select: {
151
+ id: true,
152
+ username: true,
153
+ profile: {
154
+ select: {
155
+ displayName: true,
156
+ profilePicture: true,
157
+ },
158
+ },
159
+ },
160
+ },
161
+ },
162
+ },
163
+ },
164
+ });
165
+ if (existingDM) {
166
+ return existingDM;
167
+ }
168
+ }
169
+ // Verify all members exist
170
+ const members = await prisma.user.findMany({
171
+ where: {
172
+ username: {
173
+ in: memberIds,
174
+ },
175
+ },
176
+ select: {
177
+ id: true,
178
+ username: true,
179
+ },
180
+ });
181
+ if (members.length !== memberIds.length) {
182
+ throw new TRPCError({
183
+ code: 'BAD_REQUEST',
184
+ message: 'One or more members not found',
185
+ });
186
+ }
187
+ // Create conversation with members
188
+ const conversation = await prisma.conversation.create({
189
+ data: {
190
+ type,
191
+ name,
192
+ members: {
193
+ create: [
194
+ {
195
+ userId,
196
+ role: type === 'GROUP' ? 'ADMIN' : 'MEMBER',
197
+ },
198
+ ...memberIds.map((memberId) => ({
199
+ userId: members.find((member) => member.username === memberId).id,
200
+ role: 'MEMBER',
201
+ })),
202
+ ],
203
+ },
204
+ },
205
+ include: {
206
+ members: {
207
+ include: {
208
+ user: {
209
+ select: {
210
+ id: true,
211
+ username: true,
212
+ profile: {
213
+ select: {
214
+ displayName: true,
215
+ profilePicture: true,
216
+ },
217
+ },
218
+ },
219
+ },
220
+ },
221
+ },
222
+ },
223
+ });
224
+ return conversation;
225
+ }),
226
+ get: protectedProcedure
227
+ .input(z.object({ conversationId: z.string() }))
228
+ .query(async ({ input, ctx }) => {
229
+ const userId = ctx.user.id;
230
+ const { conversationId } = input;
231
+ const conversation = await prisma.conversation.findFirst({
232
+ where: {
233
+ id: conversationId,
234
+ members: {
235
+ some: {
236
+ userId,
237
+ },
238
+ },
239
+ },
240
+ include: {
241
+ members: {
242
+ include: {
243
+ user: {
244
+ select: {
245
+ id: true,
246
+ username: true,
247
+ profile: {
248
+ select: {
249
+ displayName: true,
250
+ profilePicture: true,
251
+ },
252
+ },
253
+ },
254
+ },
255
+ },
256
+ },
257
+ },
258
+ });
259
+ if (!conversation) {
260
+ throw new TRPCError({
261
+ code: 'NOT_FOUND',
262
+ message: 'Conversation not found or access denied',
263
+ });
264
+ }
265
+ return conversation;
266
+ }),
267
+ });
@@ -0,0 +1,142 @@
1
+ import { z } from 'zod';
2
+ export declare const messageRouter: import("@trpc/server").TRPCBuiltRouter<{
3
+ ctx: import("../trpc.js").Context;
4
+ meta: object;
5
+ errorShape: {
6
+ data: {
7
+ zodError: z.typeToFlattenedError<any, string> | null;
8
+ prismaError: import("../utils/prismaErrorHandler.js").PrismaErrorInfo | null;
9
+ code: import("@trpc/server").TRPC_ERROR_CODE_KEY;
10
+ httpStatus: number;
11
+ path?: string;
12
+ stack?: string;
13
+ };
14
+ message: string;
15
+ code: import("@trpc/server").TRPC_ERROR_CODE_NUMBER;
16
+ };
17
+ transformer: false;
18
+ }, import("@trpc/server").TRPCDecorateCreateRouterOptions<{
19
+ list: import("@trpc/server").TRPCQueryProcedure<{
20
+ input: {
21
+ conversationId: string;
22
+ cursor?: string | undefined;
23
+ limit?: number | undefined;
24
+ };
25
+ output: {
26
+ messages: {
27
+ id: string;
28
+ content: string;
29
+ senderId: string;
30
+ conversationId: string;
31
+ createdAt: Date;
32
+ sender: {
33
+ id: string;
34
+ username: string;
35
+ profile: {
36
+ displayName: string | null;
37
+ profilePicture: string | null;
38
+ } | null;
39
+ };
40
+ mentions: {
41
+ user: {
42
+ id: string;
43
+ username: string;
44
+ profile: {
45
+ displayName: string | null;
46
+ } | null;
47
+ };
48
+ }[];
49
+ mentionsMe: boolean;
50
+ }[];
51
+ nextCursor: string | undefined;
52
+ };
53
+ meta: object;
54
+ }>;
55
+ send: import("@trpc/server").TRPCMutationProcedure<{
56
+ input: {
57
+ content: string;
58
+ conversationId: string;
59
+ mentionedUserIds?: string[] | undefined;
60
+ };
61
+ output: {
62
+ id: string;
63
+ content: string;
64
+ senderId: string;
65
+ conversationId: string;
66
+ createdAt: Date;
67
+ sender: {
68
+ id: string;
69
+ username: string;
70
+ profile: {
71
+ displayName: string | null;
72
+ profilePicture: string | null;
73
+ } | null;
74
+ };
75
+ mentionedUserIds: string[];
76
+ };
77
+ meta: object;
78
+ }>;
79
+ update: import("@trpc/server").TRPCMutationProcedure<{
80
+ input: {
81
+ content: string;
82
+ messageId: string;
83
+ mentionedUserIds?: string[] | undefined;
84
+ };
85
+ output: {
86
+ id: string;
87
+ content: string;
88
+ senderId: string;
89
+ conversationId: string;
90
+ createdAt: Date;
91
+ sender: {
92
+ id: string;
93
+ username: string;
94
+ profile: {
95
+ displayName: string | null;
96
+ profilePicture: string | null;
97
+ } | null;
98
+ };
99
+ mentionedUserIds: string[];
100
+ };
101
+ meta: object;
102
+ }>;
103
+ delete: import("@trpc/server").TRPCMutationProcedure<{
104
+ input: {
105
+ messageId: string;
106
+ };
107
+ output: {
108
+ success: boolean;
109
+ messageId: string;
110
+ };
111
+ meta: object;
112
+ }>;
113
+ markAsRead: import("@trpc/server").TRPCMutationProcedure<{
114
+ input: {
115
+ conversationId: string;
116
+ };
117
+ output: {
118
+ success: boolean;
119
+ };
120
+ meta: object;
121
+ }>;
122
+ markMentionsAsRead: import("@trpc/server").TRPCMutationProcedure<{
123
+ input: {
124
+ conversationId: string;
125
+ };
126
+ output: {
127
+ success: boolean;
128
+ };
129
+ meta: object;
130
+ }>;
131
+ getUnreadCount: import("@trpc/server").TRPCQueryProcedure<{
132
+ input: {
133
+ conversationId: string;
134
+ };
135
+ output: {
136
+ unreadCount: number;
137
+ unreadMentionCount: number;
138
+ };
139
+ meta: object;
140
+ }>;
141
+ }>>;
142
+ //# sourceMappingURL=message.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../src/routers/message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6kBxB,CAAC"}