@m5kdev/backend 0.4.0 → 0.6.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/dist/src/modules/ai/ai.prompts.d.ts +5 -0
- package/dist/src/modules/ai/ai.prompts.js +16 -0
- package/dist/src/modules/ai/ai.service.d.ts +26 -12
- package/dist/src/modules/ai/ai.service.js +105 -15
- package/dist/src/modules/auth/auth.dto.d.ts +2 -2
- package/dist/src/modules/auth/auth.lib.d.ts +3 -3
- package/dist/src/modules/auth/auth.repository.d.ts +2 -2
- package/dist/src/modules/auth/auth.repository.js +1 -1
- package/dist/src/modules/auth/auth.service.d.ts +3 -3
- package/dist/src/modules/auth/auth.service.js +1 -1
- package/dist/src/modules/auth/auth.trpc.d.ts +6 -6
- package/dist/src/modules/auth/auth.trpc.js +1 -1
- package/dist/src/modules/base/base.abstract.d.ts +3 -2
- package/dist/src/modules/base/base.abstract.js +10 -1
- package/dist/src/modules/base/base.procedure.d.ts +112 -0
- package/dist/src/modules/base/base.procedure.js +289 -0
- package/dist/src/modules/base/base.repository.d.ts +1 -0
- package/dist/src/modules/base/base.repository.js +12 -2
- package/dist/src/modules/base/base.service.d.ts +17 -5
- package/dist/src/modules/base/base.service.js +7 -0
- package/dist/src/modules/base/base.service.test.d.ts +1 -0
- package/dist/src/modules/base/base.service.test.js +415 -0
- package/dist/src/modules/connect/connect.repository.d.ts +3 -3
- package/dist/src/modules/connect/connect.service.d.ts +4 -4
- package/dist/src/modules/connect/connect.trpc.d.ts +2 -2
- package/dist/src/modules/recurrence/recurrence.service.d.ts +29 -8
- package/dist/src/modules/recurrence/recurrence.service.js +3 -4
- package/dist/src/modules/recurrence/recurrence.trpc.d.ts +3 -3
- package/dist/src/modules/recurrence/recurrence.trpc.js +1 -1
- package/dist/src/modules/tag/tag.db.js +1 -1
- package/dist/src/modules/tag/tag.repository.js +27 -26
- package/dist/src/modules/tag/tag.service.d.ts +86 -15
- package/dist/src/modules/tag/tag.service.js +20 -12
- package/dist/src/modules/tag/tag.trpc.d.ts +3 -3
- package/dist/src/types.d.ts +5 -5
- package/dist/src/utils/trpc.d.ts +6 -6
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -5
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const neverthrow_1 = require("neverthrow");
|
|
4
|
+
const errors_1 = require("../../utils/errors");
|
|
5
|
+
const base_service_1 = require("./base.service");
|
|
6
|
+
function createUser(overrides = {}) {
|
|
7
|
+
return {
|
|
8
|
+
id: "user-1",
|
|
9
|
+
role: "member",
|
|
10
|
+
email: "user@example.com",
|
|
11
|
+
name: "User",
|
|
12
|
+
createdAt: new Date(),
|
|
13
|
+
updatedAt: new Date(),
|
|
14
|
+
banned: false,
|
|
15
|
+
banReason: null,
|
|
16
|
+
banExpires: null,
|
|
17
|
+
emailVerified: true,
|
|
18
|
+
image: null,
|
|
19
|
+
onboarding: null,
|
|
20
|
+
preferences: null,
|
|
21
|
+
flags: null,
|
|
22
|
+
stripeCustomerId: null,
|
|
23
|
+
paymentCustomerId: null,
|
|
24
|
+
paymentPlanTier: null,
|
|
25
|
+
paymentPlanExpiresAt: null,
|
|
26
|
+
...overrides,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function createSession(overrides = {}) {
|
|
30
|
+
return {
|
|
31
|
+
id: "session-1",
|
|
32
|
+
createdAt: new Date(),
|
|
33
|
+
updatedAt: new Date(),
|
|
34
|
+
expiresAt: new Date(Date.now() + 1000 * 60 * 60),
|
|
35
|
+
token: "token",
|
|
36
|
+
ipAddress: null,
|
|
37
|
+
userAgent: null,
|
|
38
|
+
userId: "user-1",
|
|
39
|
+
activeOrganizationId: null,
|
|
40
|
+
activeOrganizationRole: null,
|
|
41
|
+
activeTeamId: null,
|
|
42
|
+
activeTeamRole: null,
|
|
43
|
+
impersonatedBy: null,
|
|
44
|
+
...overrides,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
describe("BaseService procedure builder", () => {
|
|
48
|
+
it("runs chained steps in order and exposes state by step name", async () => {
|
|
49
|
+
const events = [];
|
|
50
|
+
class PipelineService extends base_service_1.BaseService {
|
|
51
|
+
run = this.procedure("run")
|
|
52
|
+
.use("trimmed", ({ input }) => {
|
|
53
|
+
events.push("trimmed");
|
|
54
|
+
return input.value.trim();
|
|
55
|
+
})
|
|
56
|
+
.use("upper", ({ state }) => {
|
|
57
|
+
events.push("upper");
|
|
58
|
+
return state.trimmed.toUpperCase();
|
|
59
|
+
})
|
|
60
|
+
.handle(({ state }) => {
|
|
61
|
+
events.push("handler");
|
|
62
|
+
return (0, neverthrow_1.ok)({
|
|
63
|
+
trimmed: state.trimmed,
|
|
64
|
+
upper: state.upper,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const service = new PipelineService();
|
|
69
|
+
const result = await service.run({ value: " hello " }, {});
|
|
70
|
+
expect(result.isOk()).toBe(true);
|
|
71
|
+
if (result.isOk()) {
|
|
72
|
+
expect(result.value).toEqual({
|
|
73
|
+
trimmed: "hello",
|
|
74
|
+
upper: "HELLO",
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
expect(events).toEqual(["trimmed", "upper", "handler"]);
|
|
78
|
+
});
|
|
79
|
+
it("addContextFilter wraps auth and maps query input with the default step name", async () => {
|
|
80
|
+
class QueryService extends base_service_1.BaseService {
|
|
81
|
+
run = this.procedure("run")
|
|
82
|
+
.addContextFilter(["user", "organization"])
|
|
83
|
+
.handle(({ input, state }) => (0, neverthrow_1.ok)({
|
|
84
|
+
input,
|
|
85
|
+
stateMatches: state.contextFilter === input,
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
const service = new QueryService();
|
|
89
|
+
const unauthorized = await service.run({ search: "hello" }, {});
|
|
90
|
+
expect(unauthorized.isErr()).toBe(true);
|
|
91
|
+
if (unauthorized.isErr()) {
|
|
92
|
+
expect(unauthorized.error.code).toBe("UNAUTHORIZED");
|
|
93
|
+
}
|
|
94
|
+
const authorized = await service.run({ search: "hello" }, {
|
|
95
|
+
user: createUser(),
|
|
96
|
+
session: createSession({
|
|
97
|
+
activeOrganizationId: "org-1",
|
|
98
|
+
}),
|
|
99
|
+
});
|
|
100
|
+
expect(authorized.isOk()).toBe(true);
|
|
101
|
+
if (authorized.isOk()) {
|
|
102
|
+
expect(authorized.value.input.search).toBe("hello");
|
|
103
|
+
expect(authorized.value.stateMatches).toBe(true);
|
|
104
|
+
expect(authorized.value.input.filters).toEqual([
|
|
105
|
+
{
|
|
106
|
+
columnId: "userId",
|
|
107
|
+
type: "string",
|
|
108
|
+
method: "equals",
|
|
109
|
+
value: "user-1",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
columnId: "organizationId",
|
|
113
|
+
type: "string",
|
|
114
|
+
method: "equals",
|
|
115
|
+
value: "org-1",
|
|
116
|
+
},
|
|
117
|
+
]);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
it("mapInput updates the input seen by later steps and the handler", async () => {
|
|
121
|
+
class QueryService extends base_service_1.BaseService {
|
|
122
|
+
run = this.procedure("run")
|
|
123
|
+
.requireAuth()
|
|
124
|
+
.mapInput("scopedQuery", ({ input, ctx }) => this.addContextFilter(ctx, { user: true, organization: true, team: true }, input, {
|
|
125
|
+
userId: {
|
|
126
|
+
columnId: "authorUserId",
|
|
127
|
+
method: "equals",
|
|
128
|
+
},
|
|
129
|
+
organizationId: {
|
|
130
|
+
columnId: "organizationId",
|
|
131
|
+
method: "equals",
|
|
132
|
+
},
|
|
133
|
+
teamId: {
|
|
134
|
+
columnId: "teamId",
|
|
135
|
+
method: "equals",
|
|
136
|
+
},
|
|
137
|
+
}))
|
|
138
|
+
.use("filterCount", ({ input }) => input.filters?.length ?? 0)
|
|
139
|
+
.handle(({ input, state }) => (0, neverthrow_1.ok)({
|
|
140
|
+
input,
|
|
141
|
+
filterCount: state.filterCount,
|
|
142
|
+
scopedInputMatches: state.scopedQuery === input,
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
const service = new QueryService();
|
|
146
|
+
const result = await service.run({ search: "hello" }, {
|
|
147
|
+
user: createUser(),
|
|
148
|
+
session: createSession({
|
|
149
|
+
activeOrganizationId: "org-1",
|
|
150
|
+
activeTeamId: "team-1",
|
|
151
|
+
}),
|
|
152
|
+
});
|
|
153
|
+
expect(result.isOk()).toBe(true);
|
|
154
|
+
if (result.isOk()) {
|
|
155
|
+
expect(result.value.input.search).toBe("hello");
|
|
156
|
+
expect(result.value.filterCount).toBe(3);
|
|
157
|
+
expect(result.value.scopedInputMatches).toBe(true);
|
|
158
|
+
expect(result.value.input.filters).toEqual([
|
|
159
|
+
{
|
|
160
|
+
columnId: "authorUserId",
|
|
161
|
+
type: "string",
|
|
162
|
+
method: "equals",
|
|
163
|
+
value: "user-1",
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
columnId: "organizationId",
|
|
167
|
+
type: "string",
|
|
168
|
+
method: "equals",
|
|
169
|
+
value: "org-1",
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
columnId: "teamId",
|
|
173
|
+
type: "string",
|
|
174
|
+
method: "equals",
|
|
175
|
+
value: "team-1",
|
|
176
|
+
},
|
|
177
|
+
]);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
it("uses the base service default context type for procedures", async () => {
|
|
181
|
+
class ContextService extends base_service_1.BaseService {
|
|
182
|
+
run = this.procedure("run")
|
|
183
|
+
.requireAuth()
|
|
184
|
+
.handle(({ input, ctx }) => (0, neverthrow_1.ok)(`${ctx.requestId}:${ctx.user.id}:${input.value}`));
|
|
185
|
+
}
|
|
186
|
+
const service = new ContextService();
|
|
187
|
+
const result = await service.run({ value: "ok" }, {
|
|
188
|
+
requestId: "req-1",
|
|
189
|
+
user: createUser(),
|
|
190
|
+
session: createSession(),
|
|
191
|
+
});
|
|
192
|
+
expect(result.isOk()).toBe(true);
|
|
193
|
+
if (result.isOk()) {
|
|
194
|
+
expect(result.value).toBe("req-1:user-1:ok");
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
it("requireAuth rejects missing auth context", async () => {
|
|
198
|
+
class ProtectedService extends base_service_1.BaseService {
|
|
199
|
+
run = this.procedure("run")
|
|
200
|
+
.requireAuth()
|
|
201
|
+
.handle(({ input, ctx }) => (0, neverthrow_1.ok)(`${ctx.user.id}:${input.value}`));
|
|
202
|
+
}
|
|
203
|
+
const service = new ProtectedService();
|
|
204
|
+
const result = await service.run({ value: "x" }, {});
|
|
205
|
+
expect(result.isErr()).toBe(true);
|
|
206
|
+
if (result.isErr()) {
|
|
207
|
+
expect(result.error.code).toBe("UNAUTHORIZED");
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
it("requireAuth allows valid context", async () => {
|
|
211
|
+
class ProtectedService extends base_service_1.BaseService {
|
|
212
|
+
run = this.procedure("run")
|
|
213
|
+
.requireAuth()
|
|
214
|
+
.handle(({ input, ctx }) => (0, neverthrow_1.ok)(`${ctx.user.id}:${input.value}`));
|
|
215
|
+
}
|
|
216
|
+
const service = new ProtectedService();
|
|
217
|
+
const result = await service.run({ value: "ok" }, { user: createUser(), session: createSession() });
|
|
218
|
+
expect(result.isOk()).toBe(true);
|
|
219
|
+
if (result.isOk()) {
|
|
220
|
+
expect(result.value).toBe("user-1:ok");
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
it("normalizes thrown async errors through throwableAsync", async () => {
|
|
224
|
+
class ThrowingService extends base_service_1.BaseService {
|
|
225
|
+
run = this.procedure("run").handle(async () => {
|
|
226
|
+
throw new Error("boom");
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
const service = new ThrowingService();
|
|
230
|
+
const result = await service.run(undefined, {});
|
|
231
|
+
expect(result.isErr()).toBe(true);
|
|
232
|
+
if (result.isErr()) {
|
|
233
|
+
expect(result.error.code).toBe("INTERNAL_SERVER_ERROR");
|
|
234
|
+
expect(result.error.cause).toBeInstanceOf(Error);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
it("logs metadata stages without input or context payloads", async () => {
|
|
238
|
+
class LoggedService extends base_service_1.BaseService {
|
|
239
|
+
run = this.procedure("run")
|
|
240
|
+
.requireAuth()
|
|
241
|
+
.handle(({ ctx }) => (0, neverthrow_1.ok)(ctx.user.id));
|
|
242
|
+
}
|
|
243
|
+
const service = new LoggedService();
|
|
244
|
+
const debugSpy = jest.fn();
|
|
245
|
+
service.logger.debug = debugSpy;
|
|
246
|
+
const result = await service.run({ secret: "top-secret" }, {
|
|
247
|
+
user: createUser(),
|
|
248
|
+
session: createSession(),
|
|
249
|
+
token: "super-token",
|
|
250
|
+
});
|
|
251
|
+
expect(result.isOk()).toBe(true);
|
|
252
|
+
expect(debugSpy).toHaveBeenCalledTimes(3);
|
|
253
|
+
const payloads = debugSpy.mock.calls.map(([payload]) => payload);
|
|
254
|
+
expect(payloads.map((payload) => payload.stage)).toEqual(["start", "auth_passed", "success"]);
|
|
255
|
+
const serializedPayloads = JSON.stringify(payloads);
|
|
256
|
+
expect(serializedPayloads).not.toContain("top-secret");
|
|
257
|
+
expect(serializedPayloads).not.toContain("super-token");
|
|
258
|
+
expect(payloads.every((payload) => !("input" in payload))).toBe(true);
|
|
259
|
+
expect(payloads.every((payload) => !("ctx" in payload))).toBe(true);
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
describe("BasePermissionService procedure builder", () => {
|
|
263
|
+
it("passes loaded entities into the handler when access is granted", async () => {
|
|
264
|
+
const grants = [
|
|
265
|
+
{
|
|
266
|
+
action: "read",
|
|
267
|
+
level: "user",
|
|
268
|
+
role: "member",
|
|
269
|
+
access: "own",
|
|
270
|
+
},
|
|
271
|
+
];
|
|
272
|
+
class PermissionService extends base_service_1.BasePermissionService {
|
|
273
|
+
constructor() {
|
|
274
|
+
super({}, {}, grants);
|
|
275
|
+
}
|
|
276
|
+
run = this.procedure("run")
|
|
277
|
+
.access({
|
|
278
|
+
action: "read",
|
|
279
|
+
entities: ({ input }) => ({
|
|
280
|
+
userId: input.ownerId,
|
|
281
|
+
}),
|
|
282
|
+
})
|
|
283
|
+
.handle(({ state }) => (0, neverthrow_1.ok)(state.access?.userId ?? null));
|
|
284
|
+
}
|
|
285
|
+
const service = new PermissionService();
|
|
286
|
+
const result = await service.run({ ownerId: "user-1" }, { user: createUser(), session: createSession() });
|
|
287
|
+
expect(result.isOk()).toBe(true);
|
|
288
|
+
if (result.isOk()) {
|
|
289
|
+
expect(result.value).toBe("user-1");
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
it("reuses a typed state step directly when entityStep is provided", async () => {
|
|
293
|
+
const grants = [
|
|
294
|
+
{
|
|
295
|
+
action: "read",
|
|
296
|
+
level: "user",
|
|
297
|
+
role: "member",
|
|
298
|
+
access: "own",
|
|
299
|
+
},
|
|
300
|
+
];
|
|
301
|
+
class PermissionService extends base_service_1.BasePermissionService {
|
|
302
|
+
constructor() {
|
|
303
|
+
super({}, {}, grants);
|
|
304
|
+
}
|
|
305
|
+
run = this.procedure("run")
|
|
306
|
+
.use("record", ({ input }) => (0, neverthrow_1.ok)({
|
|
307
|
+
id: "resource-1",
|
|
308
|
+
userId: input.ownerId,
|
|
309
|
+
teamId: null,
|
|
310
|
+
organizationId: null,
|
|
311
|
+
}))
|
|
312
|
+
.access({
|
|
313
|
+
action: "read",
|
|
314
|
+
entityStep: "record",
|
|
315
|
+
})
|
|
316
|
+
.handle(({ state }) => (0, neverthrow_1.ok)(state.access === state.record));
|
|
317
|
+
}
|
|
318
|
+
const service = new PermissionService();
|
|
319
|
+
const result = await service.run({ ownerId: "user-1" }, { user: createUser(), session: createSession() });
|
|
320
|
+
expect(result.isOk()).toBe(true);
|
|
321
|
+
if (result.isOk()) {
|
|
322
|
+
expect(result.value).toBe(true);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
it("returns FORBIDDEN when access validation fails", async () => {
|
|
326
|
+
const grants = [
|
|
327
|
+
{
|
|
328
|
+
action: "read",
|
|
329
|
+
level: "user",
|
|
330
|
+
role: "member",
|
|
331
|
+
access: "own",
|
|
332
|
+
},
|
|
333
|
+
];
|
|
334
|
+
class PermissionService extends base_service_1.BasePermissionService {
|
|
335
|
+
constructor() {
|
|
336
|
+
super({}, {}, grants);
|
|
337
|
+
}
|
|
338
|
+
run = this.procedure("run")
|
|
339
|
+
.access({
|
|
340
|
+
action: "read",
|
|
341
|
+
entities: ({ input }) => ({
|
|
342
|
+
userId: input.ownerId,
|
|
343
|
+
}),
|
|
344
|
+
})
|
|
345
|
+
.handle(() => (0, neverthrow_1.ok)("allowed"));
|
|
346
|
+
}
|
|
347
|
+
const service = new PermissionService();
|
|
348
|
+
const result = await service.run({ ownerId: "other-user" }, { user: createUser(), session: createSession() });
|
|
349
|
+
expect(result.isErr()).toBe(true);
|
|
350
|
+
if (result.isErr()) {
|
|
351
|
+
expect(result.error.code).toBe("FORBIDDEN");
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
it("propagates entity loader failures through access", async () => {
|
|
355
|
+
const grants = [
|
|
356
|
+
{
|
|
357
|
+
action: "read",
|
|
358
|
+
level: "user",
|
|
359
|
+
role: "member",
|
|
360
|
+
access: "own",
|
|
361
|
+
},
|
|
362
|
+
];
|
|
363
|
+
class PermissionService extends base_service_1.BasePermissionService {
|
|
364
|
+
constructor() {
|
|
365
|
+
super({}, {}, grants);
|
|
366
|
+
}
|
|
367
|
+
run = this.procedure("run")
|
|
368
|
+
.access({
|
|
369
|
+
action: "read",
|
|
370
|
+
entities: async () => (0, neverthrow_1.err)(new errors_1.ServerError({
|
|
371
|
+
code: "NOT_FOUND",
|
|
372
|
+
layer: "service",
|
|
373
|
+
layerName: "PermissionService",
|
|
374
|
+
message: "Missing entity",
|
|
375
|
+
})),
|
|
376
|
+
})
|
|
377
|
+
.handle(() => (0, neverthrow_1.ok)("allowed"));
|
|
378
|
+
}
|
|
379
|
+
const service = new PermissionService();
|
|
380
|
+
const result = await service.run({ ownerId: "user-1" }, { user: createUser(), session: createSession() });
|
|
381
|
+
expect(result.isErr()).toBe(true);
|
|
382
|
+
if (result.isErr()) {
|
|
383
|
+
expect(result.error.code).toBe("INTERNAL_SERVER_ERROR");
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
it("skips entity loading when grants already allow all access", async () => {
|
|
387
|
+
const grants = [
|
|
388
|
+
{
|
|
389
|
+
action: "read",
|
|
390
|
+
level: "user",
|
|
391
|
+
role: "member",
|
|
392
|
+
access: "all",
|
|
393
|
+
},
|
|
394
|
+
];
|
|
395
|
+
const resolveEntity = jest.fn(async () => ({ userId: "user-1" }));
|
|
396
|
+
class PermissionService extends base_service_1.BasePermissionService {
|
|
397
|
+
constructor() {
|
|
398
|
+
super({}, {}, grants);
|
|
399
|
+
}
|
|
400
|
+
run = this.procedure("run")
|
|
401
|
+
.access({
|
|
402
|
+
action: "read",
|
|
403
|
+
entities: resolveEntity,
|
|
404
|
+
})
|
|
405
|
+
.handle(() => (0, neverthrow_1.ok)("allowed"));
|
|
406
|
+
}
|
|
407
|
+
const service = new PermissionService();
|
|
408
|
+
const result = await service.run({ ownerId: "other-user" }, { user: createUser(), session: createSession() });
|
|
409
|
+
expect(result.isOk()).toBe(true);
|
|
410
|
+
if (result.isOk()) {
|
|
411
|
+
expect(result.value).toBe("allowed");
|
|
412
|
+
}
|
|
413
|
+
expect(resolveEntity).not.toHaveBeenCalled();
|
|
414
|
+
});
|
|
415
|
+
});
|
|
@@ -389,22 +389,22 @@ export declare class ConnectRepository extends BaseTableRepository<Orm, Schema,
|
|
|
389
389
|
updatedAt: Date | null;
|
|
390
390
|
}[]>>;
|
|
391
391
|
upsert(data: ConnectInsert, tx?: Orm): Promise<import("../base/base.dto").ServerResult<{
|
|
392
|
-
provider: string;
|
|
393
392
|
id: string;
|
|
394
393
|
createdAt: Date;
|
|
395
394
|
updatedAt: Date | null;
|
|
396
|
-
userId: string;
|
|
397
395
|
expiresAt: Date | null;
|
|
396
|
+
userId: string;
|
|
397
|
+
parentId: string | null;
|
|
398
398
|
accessToken: string;
|
|
399
399
|
refreshToken: string | null;
|
|
400
400
|
scope: string | null;
|
|
401
|
+
provider: string;
|
|
401
402
|
accountType: string;
|
|
402
403
|
providerAccountId: string;
|
|
403
404
|
handle: string | null;
|
|
404
405
|
displayName: string | null;
|
|
405
406
|
avatarUrl: string | null;
|
|
406
407
|
tokenType: string | null;
|
|
407
|
-
parentId: string | null;
|
|
408
408
|
metadataJson: unknown;
|
|
409
409
|
revokedAt: Date | null;
|
|
410
410
|
lastRefreshedAt: Date | null;
|
|
@@ -16,19 +16,19 @@ export declare class ConnectService extends BaseService<{
|
|
|
16
16
|
url: string;
|
|
17
17
|
}>;
|
|
18
18
|
handleCallback(user: User, sessionId: string, providerId: string, code: string, state: string): Promise<import("../base/base.dto").ServerResult<{
|
|
19
|
-
handle: string | null;
|
|
20
19
|
id: string;
|
|
21
20
|
createdAt: Date;
|
|
22
21
|
updatedAt: Date | null;
|
|
22
|
+
expiresAt: Date | null;
|
|
23
23
|
userId: string;
|
|
24
24
|
parentId: string | null;
|
|
25
|
-
expiresAt: Date | null;
|
|
26
25
|
accessToken: string;
|
|
27
26
|
refreshToken: string | null;
|
|
28
27
|
scope: string | null;
|
|
29
28
|
provider: string;
|
|
30
29
|
accountType: string;
|
|
31
30
|
providerAccountId: string;
|
|
31
|
+
handle: string | null;
|
|
32
32
|
displayName: string | null;
|
|
33
33
|
avatarUrl: string | null;
|
|
34
34
|
tokenType: string | null;
|
|
@@ -37,19 +37,19 @@ export declare class ConnectService extends BaseService<{
|
|
|
37
37
|
lastRefreshedAt: Date | null;
|
|
38
38
|
}>>;
|
|
39
39
|
refreshToken(connectionId: string): Promise<import("../base/base.dto").ServerResult<{
|
|
40
|
-
handle: string | null;
|
|
41
40
|
id: string;
|
|
42
41
|
createdAt: Date;
|
|
43
42
|
updatedAt: Date | null;
|
|
43
|
+
expiresAt: Date | null;
|
|
44
44
|
userId: string;
|
|
45
45
|
parentId: string | null;
|
|
46
|
-
expiresAt: Date | null;
|
|
47
46
|
accessToken: string;
|
|
48
47
|
refreshToken: string | null;
|
|
49
48
|
scope: string | null;
|
|
50
49
|
provider: string;
|
|
51
50
|
accountType: string;
|
|
52
51
|
providerAccountId: string;
|
|
52
|
+
handle: string | null;
|
|
53
53
|
displayName: string | null;
|
|
54
54
|
avatarUrl: string | null;
|
|
55
55
|
tokenType: string | null;
|
|
@@ -18,11 +18,11 @@ export declare function createConnectTRPC({ router, privateProcedure: procedure
|
|
|
18
18
|
provider: string;
|
|
19
19
|
accountType: string;
|
|
20
20
|
providerAccountId: string;
|
|
21
|
-
handle?: string | null | undefined;
|
|
22
21
|
updatedAt?: Date | null | undefined;
|
|
23
|
-
parentId?: string | null | undefined;
|
|
24
22
|
expiresAt?: Date | null | undefined;
|
|
23
|
+
parentId?: string | null | undefined;
|
|
25
24
|
scope?: string | null | undefined;
|
|
25
|
+
handle?: string | null | undefined;
|
|
26
26
|
displayName?: string | null | undefined;
|
|
27
27
|
avatarUrl?: string | null | undefined;
|
|
28
28
|
tokenType?: string | null | undefined;
|
|
@@ -1,20 +1,41 @@
|
|
|
1
1
|
import type { CreateRecurrenceSchema, DeleteRecurrenceRulesSchema, DeleteRecurrenceSchema, UpdateRecurrenceRulesSchema, UpdateRecurrenceSchema } from "@m5kdev/commons/modules/recurrence/recurrence.schema";
|
|
2
|
-
import type {
|
|
3
|
-
import type { Context, User } from "../auth/auth.lib";
|
|
2
|
+
import type { Context } from "../auth/auth.lib";
|
|
4
3
|
import type { ServerResultAsync } from "../base/base.dto";
|
|
5
4
|
import { BaseService } from "../base/base.service";
|
|
6
5
|
import type { CreateWithRulesResult, RecurrenceRepository, RecurrenceRulesRepository } from "./recurrence.repository";
|
|
7
6
|
export declare class RecurrenceService extends BaseService<{
|
|
8
7
|
recurrence: RecurrenceRepository;
|
|
9
8
|
recurrenceRules: RecurrenceRulesRepository;
|
|
10
|
-
}, Record<string, never
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
}, Record<string, never>, Context> {
|
|
10
|
+
readonly list: import("../base/base.procedure").ServiceProcedure<{
|
|
11
|
+
page?: number | undefined;
|
|
12
|
+
limit?: number | undefined;
|
|
13
|
+
sort?: string | undefined;
|
|
14
|
+
order?: "asc" | "desc" | undefined;
|
|
15
|
+
filters?: {
|
|
16
|
+
columnId: string;
|
|
17
|
+
type: "string" | "number" | "boolean" | "date" | "enum";
|
|
18
|
+
method: "on" | "contains" | "equals" | "starts_with" | "ends_with" | "greater_than" | "less_than" | "between" | "before" | "after" | "oneOf" | "intersect" | "isEmpty" | "isNotEmpty" | "is_null" | "is_not_null";
|
|
19
|
+
value: string | number | boolean | string[];
|
|
20
|
+
valueTo?: string | undefined;
|
|
21
|
+
endColumnId?: string | undefined;
|
|
22
|
+
}[] | undefined;
|
|
23
|
+
}, Context, {
|
|
24
|
+
rows: {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string | null;
|
|
27
|
+
createdAt: Date;
|
|
28
|
+
updatedAt: Date;
|
|
29
|
+
metadata: Record<string, any> | null;
|
|
30
|
+
userId: string | null;
|
|
31
|
+
teamId: string | null;
|
|
32
|
+
organizationId: string | null;
|
|
33
|
+
enabled: boolean;
|
|
34
|
+
kind: string | null;
|
|
35
|
+
}[];
|
|
16
36
|
total: number;
|
|
17
37
|
}>;
|
|
38
|
+
create(data: CreateRecurrenceSchema, ctx: Context): ServerResultAsync<CreateWithRulesResult>;
|
|
18
39
|
findById(id: string): ServerResultAsync<CreateWithRulesResult["recurrence"] | null>;
|
|
19
40
|
update(data: UpdateRecurrenceSchema & {
|
|
20
41
|
id: string;
|
|
@@ -31,6 +31,9 @@ function mapRuleToInsert(rule) {
|
|
|
31
31
|
return out;
|
|
32
32
|
}
|
|
33
33
|
class RecurrenceService extends base_service_1.BaseService {
|
|
34
|
+
list = this.procedure("list")
|
|
35
|
+
.addContextFilter(["user"])
|
|
36
|
+
.handle(({ input }) => this.repository.recurrence.queryList(input));
|
|
34
37
|
async create(data, ctx) {
|
|
35
38
|
const recurrenceData = {
|
|
36
39
|
name: data.name,
|
|
@@ -44,10 +47,6 @@ class RecurrenceService extends base_service_1.BaseService {
|
|
|
44
47
|
const rulesData = data.recurrenceRules.map(mapRuleToInsert);
|
|
45
48
|
return this.repository.recurrence.createWithRules(recurrenceData, rulesData);
|
|
46
49
|
}
|
|
47
|
-
async list(query, ctx) {
|
|
48
|
-
const queryWithUser = ctx?.user ? this.addUserFilter(ctx.user.id, query, "userId") : query;
|
|
49
|
-
return this.repository.recurrence.queryList(queryWithUser);
|
|
50
|
-
}
|
|
51
50
|
async findById(id) {
|
|
52
51
|
const result = await this.repository.recurrence.findById(id);
|
|
53
52
|
if (result.isErr())
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { RecurrenceService } from "./recurrence.service";
|
|
2
1
|
import { type TRPCMethods } from "../../utils/trpc";
|
|
2
|
+
import type { RecurrenceService } from "./recurrence.service";
|
|
3
3
|
export declare function createRecurrenceTRPC({ router, privateProcedure: procedure }: TRPCMethods, recurrenceService: RecurrenceService): import("@trpc/server").TRPCBuiltRouter<{
|
|
4
4
|
ctx: import("../auth/auth.lib").Context;
|
|
5
5
|
meta: any;
|
|
@@ -11,11 +11,11 @@ export declare function createRecurrenceTRPC({ router, privateProcedure: procedu
|
|
|
11
11
|
page?: number | undefined;
|
|
12
12
|
limit?: number | undefined;
|
|
13
13
|
sort?: string | undefined;
|
|
14
|
-
order?: "
|
|
14
|
+
order?: "asc" | "desc" | undefined;
|
|
15
15
|
filters?: {
|
|
16
16
|
columnId: string;
|
|
17
17
|
type: "string" | "number" | "boolean" | "date" | "enum";
|
|
18
|
-
method: "
|
|
18
|
+
method: "on" | "contains" | "equals" | "starts_with" | "ends_with" | "greater_than" | "less_than" | "between" | "before" | "after" | "oneOf" | "intersect" | "isEmpty" | "isNotEmpty" | "is_null" | "is_not_null";
|
|
19
19
|
value: string | number | boolean | string[];
|
|
20
20
|
valueTo?: string | undefined;
|
|
21
21
|
endColumnId?: string | undefined;
|
|
@@ -20,7 +20,7 @@ const deleteRecurrenceOutputSchema = zod_1.z.object({ id: zod_1.z.string() });
|
|
|
20
20
|
function createRecurrenceTRPC({ router, privateProcedure: procedure }, recurrenceService) {
|
|
21
21
|
return router({
|
|
22
22
|
list: procedure
|
|
23
|
-
.input(query_schema_1.querySchema.
|
|
23
|
+
.input(query_schema_1.querySchema.default({}))
|
|
24
24
|
.output(listRecurrenceOutputSchema)
|
|
25
25
|
.query(async ({ ctx, input }) => {
|
|
26
26
|
return (0, trpc_1.handleTRPCResult)(await recurrenceService.list(input, ctx));
|
|
@@ -36,7 +36,7 @@ exports.taggings = (0, sqlite_core_1.sqliteTable)("taggings", {
|
|
|
36
36
|
.$default(() => new Date()),
|
|
37
37
|
tagId: (0, sqlite_core_1.text)("tag_id")
|
|
38
38
|
.notNull()
|
|
39
|
-
.references(() => exports.tags.id),
|
|
39
|
+
.references(() => exports.tags.id, { onDelete: "cascade" }),
|
|
40
40
|
resourceType: (0, sqlite_core_1.text)("resource_type").notNull(), // e.g., "post", "image"
|
|
41
41
|
resourceId: (0, sqlite_core_1.text)("resource_id").notNull(), // id in the resource table
|
|
42
42
|
});
|