@m5kdev/backend 0.1.1 → 0.1.3
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +18 -0
- package/dist/src/lib/posthog.js +7 -0
- package/dist/src/lib/sentry.js +9 -0
- package/dist/src/modules/access/access.repository.js +32 -0
- package/dist/src/modules/access/access.service.js +51 -0
- package/dist/src/modules/access/access.test.js +182 -0
- package/dist/src/modules/access/access.utils.js +20 -0
- package/dist/src/modules/ai/ai.db.js +39 -0
- package/dist/src/modules/ai/ai.prompt.js +30 -0
- package/dist/src/modules/ai/ai.repository.js +26 -0
- package/dist/src/modules/ai/ai.router.js +132 -0
- package/dist/src/modules/ai/ai.service.js +207 -0
- package/dist/src/modules/ai/ai.trpc.d.ts +5 -5
- package/dist/src/modules/ai/ai.trpc.js +20 -0
- package/dist/src/modules/ai/ideogram/ideogram.constants.js +167 -0
- package/dist/src/modules/ai/ideogram/ideogram.dto.js +49 -0
- package/dist/src/modules/ai/ideogram/ideogram.prompt.js +860 -0
- package/dist/src/modules/ai/ideogram/ideogram.repository.js +46 -0
- package/dist/src/modules/ai/ideogram/ideogram.service.js +11 -0
- package/dist/src/modules/auth/auth.db.js +215 -0
- package/dist/src/modules/auth/auth.dto.js +38 -0
- package/dist/src/modules/auth/auth.lib.d.ts +4 -4
- package/dist/src/modules/auth/auth.lib.js +284 -0
- package/dist/src/modules/auth/auth.middleware.js +52 -0
- package/dist/src/modules/auth/auth.repository.js +541 -0
- package/dist/src/modules/auth/auth.service.js +201 -0
- package/dist/src/modules/auth/auth.trpc.d.ts +18 -18
- package/dist/src/modules/auth/auth.trpc.js +157 -0
- package/dist/src/modules/auth/auth.utils.js +97 -0
- package/dist/src/modules/base/base.abstract.js +53 -0
- package/dist/src/modules/base/base.dto.js +112 -0
- package/dist/src/modules/base/base.grants.js +123 -0
- package/dist/src/modules/base/base.grants.test.js +668 -0
- package/dist/src/modules/base/base.repository.js +307 -0
- package/dist/src/modules/base/base.service.js +109 -0
- package/dist/src/modules/base/base.types.js +2 -0
- package/dist/src/modules/billing/billing.db.js +29 -0
- package/dist/src/modules/billing/billing.repository.js +235 -0
- package/dist/src/modules/billing/billing.router.js +56 -0
- package/dist/src/modules/billing/billing.service.js +147 -0
- package/dist/src/modules/billing/billing.trpc.d.ts +5 -5
- package/dist/src/modules/billing/billing.trpc.js +17 -0
- package/dist/src/modules/clay/clay.repository.js +26 -0
- package/dist/src/modules/clay/clay.service.js +24 -0
- package/dist/src/modules/connect/connect.db.js +30 -0
- package/dist/src/modules/connect/connect.dto.js +36 -0
- package/dist/src/modules/connect/connect.linkedin.js +53 -0
- package/dist/src/modules/connect/connect.oauth.js +198 -0
- package/dist/src/modules/connect/connect.repository.d.ts +7 -7
- package/dist/src/modules/connect/connect.repository.js +54 -0
- package/dist/src/modules/connect/connect.router.js +54 -0
- package/dist/src/modules/connect/connect.service.d.ts +14 -14
- package/dist/src/modules/connect/connect.service.js +114 -0
- package/dist/src/modules/connect/connect.trpc.d.ts +10 -10
- package/dist/src/modules/connect/connect.trpc.js +21 -0
- package/dist/src/modules/connect/connect.types.js +2 -0
- package/dist/src/modules/crypto/crypto.db.js +17 -0
- package/dist/src/modules/crypto/crypto.repository.js +10 -0
- package/dist/src/modules/crypto/crypto.service.js +52 -0
- package/dist/src/modules/email/email.service.js +107 -0
- package/dist/src/modules/file/file.repository.js +79 -0
- package/dist/src/modules/file/file.router.js +99 -0
- package/dist/src/modules/file/file.service.js +150 -0
- package/dist/src/modules/recurrence/recurrence.db.js +66 -0
- package/dist/src/modules/recurrence/recurrence.repository.js +39 -0
- package/dist/src/modules/recurrence/recurrence.service.js +70 -0
- package/dist/src/modules/recurrence/recurrence.trpc.d.ts +15 -15
- package/dist/src/modules/recurrence/recurrence.trpc.js +65 -0
- package/dist/src/modules/social/social.dto.js +18 -0
- package/dist/src/modules/social/social.linkedin.js +427 -0
- package/dist/src/modules/social/social.linkedin.test.js +235 -0
- package/dist/src/modules/social/social.service.js +76 -0
- package/dist/src/modules/social/social.types.js +2 -0
- package/dist/src/modules/tag/tag.db.js +42 -0
- package/dist/src/modules/tag/tag.dto.js +9 -0
- package/dist/src/modules/tag/tag.repository.js +154 -0
- package/dist/src/modules/tag/tag.service.js +31 -0
- package/dist/src/modules/tag/tag.trpc.d.ts +5 -5
- package/dist/src/modules/tag/tag.trpc.js +47 -0
- package/dist/src/modules/utils/applyPagination.js +16 -0
- package/dist/src/modules/utils/applySorting.js +18 -0
- package/dist/src/modules/utils/getConditionsFromFilters.js +200 -0
- package/dist/src/modules/video/video.service.js +84 -0
- package/dist/src/modules/webhook/webhook.constants.js +10 -0
- package/dist/src/modules/webhook/webhook.db.js +17 -0
- package/dist/src/modules/webhook/webhook.dto.js +7 -0
- package/dist/src/modules/webhook/webhook.repository.js +56 -0
- package/dist/src/modules/webhook/webhook.router.js +30 -0
- package/dist/src/modules/webhook/webhook.service.js +68 -0
- package/dist/src/modules/workflow/workflow.db.js +30 -0
- package/dist/src/modules/workflow/workflow.repository.js +105 -0
- package/dist/src/modules/workflow/workflow.service.js +37 -0
- package/dist/src/modules/workflow/workflow.trpc.d.ts +5 -5
- package/dist/src/modules/workflow/workflow.trpc.js +21 -0
- package/dist/src/modules/workflow/workflow.types.js +2 -0
- package/dist/src/modules/workflow/workflow.utils.js +173 -0
- package/dist/src/test/stubs/utils.js +5 -0
- package/dist/src/trpc/context.d.ts +5 -5
- package/dist/src/trpc/context.js +17 -0
- package/dist/src/trpc/index.js +6 -0
- package/dist/src/trpc/procedures.d.ts +56 -56
- package/dist/src/trpc/procedures.js +32 -0
- package/dist/src/trpc/utils.js +20 -0
- package/dist/src/types.d.ts +33 -33
- package/dist/src/types.js +13 -0
- package/dist/src/utils/errors.js +104 -0
- package/dist/src/utils/logger.js +11 -0
- package/dist/src/utils/posthog.js +31 -0
- package/dist/src/utils/types.js +2 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/tsconfig.json +2 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SocialService = void 0;
|
|
4
|
+
const neverthrow_1 = require("neverthrow");
|
|
5
|
+
const base_service_1 = require("#modules/base/base.service");
|
|
6
|
+
class SocialService extends base_service_1.BaseService {
|
|
7
|
+
providers = new Map();
|
|
8
|
+
constructor(repositories, services, providers) {
|
|
9
|
+
super(repositories, services);
|
|
10
|
+
this.providers = new Map(providers.map((provider) => [provider.id, provider]));
|
|
11
|
+
}
|
|
12
|
+
getProvider(id) {
|
|
13
|
+
return this.providers.get(id) ?? null;
|
|
14
|
+
}
|
|
15
|
+
async postToProvider(providerId, input, { user }) {
|
|
16
|
+
return this.throwableAsync(async () => {
|
|
17
|
+
const provider = this.getProvider(providerId);
|
|
18
|
+
if (!provider) {
|
|
19
|
+
return this.error("BAD_REQUEST", `Unknown provider: ${providerId}`);
|
|
20
|
+
}
|
|
21
|
+
const connectionResult = await this.repository.connect.list({
|
|
22
|
+
userId: user.id,
|
|
23
|
+
providers: [providerId],
|
|
24
|
+
});
|
|
25
|
+
if (connectionResult.isErr()) {
|
|
26
|
+
return this.error("INTERNAL_SERVER_ERROR", "Failed to load connection", {
|
|
27
|
+
cause: connectionResult.error,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const activeConnection = connectionResult.value.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime())[0];
|
|
31
|
+
const connection = await this.ensureFreshConnection(activeConnection);
|
|
32
|
+
if (connection.isErr()) {
|
|
33
|
+
return this.error("INTERNAL_SERVER_ERROR", "Failed to refresh connection", {
|
|
34
|
+
cause: connection.error,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const payload = {
|
|
38
|
+
text: input.text,
|
|
39
|
+
media: input.media,
|
|
40
|
+
visibility: input.visibility ?? "PUBLIC",
|
|
41
|
+
};
|
|
42
|
+
const accessToken = connection.value.accessToken;
|
|
43
|
+
if (!accessToken) {
|
|
44
|
+
return this.error("BAD_REQUEST", "Missing access token for connection");
|
|
45
|
+
}
|
|
46
|
+
const result = await provider.post({
|
|
47
|
+
deps: { fileService: this.service.file },
|
|
48
|
+
context: {
|
|
49
|
+
userId: user.id,
|
|
50
|
+
connection: connection.value,
|
|
51
|
+
accessToken,
|
|
52
|
+
},
|
|
53
|
+
payload,
|
|
54
|
+
});
|
|
55
|
+
return (0, neverthrow_1.ok)(result);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
async ensureFreshConnection(connection) {
|
|
59
|
+
if (!connection.expiresAt || !connection.refreshToken) {
|
|
60
|
+
return (0, neverthrow_1.ok)(connection);
|
|
61
|
+
}
|
|
62
|
+
const expiresAt = new Date(connection.expiresAt);
|
|
63
|
+
const bufferMs = 60 * 1000; // Refresh 1 minute before expiry
|
|
64
|
+
if (Date.now() < expiresAt.getTime() - bufferMs) {
|
|
65
|
+
return (0, neverthrow_1.ok)(connection);
|
|
66
|
+
}
|
|
67
|
+
const refreshed = await this.service.connect.refreshToken(connection.id);
|
|
68
|
+
if (refreshed.isErr()) {
|
|
69
|
+
return this.error("INTERNAL_SERVER_ERROR", "Failed to refresh access token", {
|
|
70
|
+
cause: refreshed.error,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return (0, neverthrow_1.ok)(refreshed.value);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
exports.SocialService = SocialService;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.taggings = exports.tags = void 0;
|
|
4
|
+
const sqlite_core_1 = require("drizzle-orm/sqlite-core");
|
|
5
|
+
const uuid_1 = require("uuid");
|
|
6
|
+
const auth_db_1 = require("#modules/auth/auth.db");
|
|
7
|
+
exports.tags = (0, sqlite_core_1.sqliteTable)("tags", {
|
|
8
|
+
id: (0, sqlite_core_1.text)("id").primaryKey().$default(uuid_1.v4),
|
|
9
|
+
createdAt: (0, sqlite_core_1.integer)("created_at", { mode: "timestamp" })
|
|
10
|
+
.notNull()
|
|
11
|
+
.$default(() => new Date()),
|
|
12
|
+
updatedAt: (0, sqlite_core_1.integer)("updated_at", { mode: "timestamp" }),
|
|
13
|
+
deletedAt: (0, sqlite_core_1.integer)("deleted_at", { mode: "timestamp" }),
|
|
14
|
+
userId: (0, sqlite_core_1.text)("user_id")
|
|
15
|
+
.notNull()
|
|
16
|
+
.references(() => auth_db_1.users.id, { onDelete: "cascade" }),
|
|
17
|
+
organizationId: (0, sqlite_core_1.text)("organization_id").references(() => auth_db_1.organizations.id, {
|
|
18
|
+
onDelete: "cascade",
|
|
19
|
+
}),
|
|
20
|
+
teamId: (0, sqlite_core_1.text)("team_id").references(() => auth_db_1.teams.id, { onDelete: "cascade" }),
|
|
21
|
+
name: (0, sqlite_core_1.text)("name").notNull(),
|
|
22
|
+
color: (0, sqlite_core_1.text)("color"),
|
|
23
|
+
type: (0, sqlite_core_1.text)("type"),
|
|
24
|
+
isEnabled: (0, sqlite_core_1.integer)("is_enabled", { mode: "boolean" }).notNull().default(true),
|
|
25
|
+
parentId: (0, sqlite_core_1.text)("parent_id").references(() => exports.tags.id, { onDelete: "set null" }),
|
|
26
|
+
assignableTo: (0, sqlite_core_1.text)("assignable_to", {
|
|
27
|
+
mode: "json",
|
|
28
|
+
})
|
|
29
|
+
.notNull()
|
|
30
|
+
.$type(),
|
|
31
|
+
});
|
|
32
|
+
exports.taggings = (0, sqlite_core_1.sqliteTable)("taggings", {
|
|
33
|
+
id: (0, sqlite_core_1.text)("id").primaryKey().$default(uuid_1.v4),
|
|
34
|
+
createdAt: (0, sqlite_core_1.integer)("created_at", { mode: "timestamp" })
|
|
35
|
+
.notNull()
|
|
36
|
+
.$default(() => new Date()),
|
|
37
|
+
tagId: (0, sqlite_core_1.text)("tag_id")
|
|
38
|
+
.notNull()
|
|
39
|
+
.references(() => exports.tags.id),
|
|
40
|
+
resourceType: (0, sqlite_core_1.text)("resource_type").notNull(), // e.g., "post", "image"
|
|
41
|
+
resourceId: (0, sqlite_core_1.text)("resource_id").notNull(), // id in the resource table
|
|
42
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.taggingsSelectOutput = exports.tagsSelectOutput = exports.taggingsSelectDTO = exports.tagsSelectDTO = void 0;
|
|
4
|
+
const base_dto_1 = require("#modules/base/base.dto");
|
|
5
|
+
const tag_db_1 = require("#modules/tag/tag.db");
|
|
6
|
+
exports.tagsSelectDTO = (0, base_dto_1.createSelectDTO)(tag_db_1.tags);
|
|
7
|
+
exports.taggingsSelectDTO = (0, base_dto_1.createSelectDTO)(tag_db_1.taggings);
|
|
8
|
+
exports.tagsSelectOutput = exports.tagsSelectDTO.schema;
|
|
9
|
+
exports.taggingsSelectOutput = exports.taggingsSelectDTO.schema;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TagRepository = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
6
|
+
const neverthrow_1 = require("neverthrow");
|
|
7
|
+
const base_repository_1 = require("#modules/base/base.repository");
|
|
8
|
+
const tag = tslib_1.__importStar(require("#modules/tag/tag.db"));
|
|
9
|
+
const schema = { ...tag };
|
|
10
|
+
class TagRepository extends base_repository_1.BaseTableRepository {
|
|
11
|
+
async link({ userId, ...data }, tx) {
|
|
12
|
+
return this.throwableAsync(async () => {
|
|
13
|
+
const db = tx ?? this.orm;
|
|
14
|
+
const [foundTag] = await db
|
|
15
|
+
.select({ id: this.schema.tags.id })
|
|
16
|
+
.from(this.schema.tags)
|
|
17
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.schema.tags.id, data.tagId), (0, drizzle_orm_1.eq)(this.schema.tags.userId, userId)))
|
|
18
|
+
.limit(1);
|
|
19
|
+
if (!foundTag)
|
|
20
|
+
return this.error("FORBIDDEN");
|
|
21
|
+
const [tagging] = await db
|
|
22
|
+
.insert(this.schema.taggings)
|
|
23
|
+
.values({ ...data, tagId: foundTag.id })
|
|
24
|
+
.returning();
|
|
25
|
+
if (!tagging)
|
|
26
|
+
return this.error("NOT_FOUND");
|
|
27
|
+
return (0, neverthrow_1.ok)(tagging);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async linkBulk(data, tx) {
|
|
31
|
+
return this.throwableAsync(async () => {
|
|
32
|
+
const db = tx ?? this.orm;
|
|
33
|
+
await db.insert(this.schema.taggings).values(data);
|
|
34
|
+
const tags = await db
|
|
35
|
+
.select()
|
|
36
|
+
.from(this.schema.tags)
|
|
37
|
+
.where((0, drizzle_orm_1.inArray)(this.schema.tags.id, data.map((tag) => tag.tagId)));
|
|
38
|
+
return (0, neverthrow_1.ok)(tags);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
async set(data, tx) {
|
|
42
|
+
return this.throwableAsync(async () => {
|
|
43
|
+
const db = tx ?? this.orm;
|
|
44
|
+
const result = await db.transaction(async (trx) => {
|
|
45
|
+
// FIXME: We are assuming that all resourceIds are the same, this is not a good assumption.
|
|
46
|
+
await trx
|
|
47
|
+
.delete(this.schema.taggings)
|
|
48
|
+
.where((0, drizzle_orm_1.eq)(this.schema.taggings.resourceId, data[0].resourceId));
|
|
49
|
+
await db.insert(this.schema.taggings).values(data);
|
|
50
|
+
const tags = await db
|
|
51
|
+
.select()
|
|
52
|
+
.from(this.schema.tags)
|
|
53
|
+
.where((0, drizzle_orm_1.inArray)(this.schema.tags.id, data.map((tag) => tag.tagId)));
|
|
54
|
+
return tags;
|
|
55
|
+
});
|
|
56
|
+
return (0, neverthrow_1.ok)(result);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async unlink({ userId, ...data }, tx) {
|
|
60
|
+
return this.throwableAsync(async () => {
|
|
61
|
+
const db = tx ?? this.orm;
|
|
62
|
+
const [foundTag] = await db
|
|
63
|
+
.select()
|
|
64
|
+
.from(this.schema.tags)
|
|
65
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.schema.tags.id, data.tagId), (0, drizzle_orm_1.eq)(this.schema.tags.userId, userId)))
|
|
66
|
+
.limit(1);
|
|
67
|
+
if (!foundTag)
|
|
68
|
+
return this.error("FORBIDDEN");
|
|
69
|
+
await db
|
|
70
|
+
.delete(this.schema.taggings)
|
|
71
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.schema.taggings.tagId, data.tagId), (0, drizzle_orm_1.eq)(this.schema.taggings.resourceId, data.resourceId), (0, drizzle_orm_1.eq)(this.schema.taggings.resourceType, data.resourceType)));
|
|
72
|
+
return (0, neverthrow_1.ok)(foundTag);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
async findTagsForResources(data, tx) {
|
|
76
|
+
return this.throwableAsync(async () => {
|
|
77
|
+
const db = tx ?? this.orm;
|
|
78
|
+
if (data.resourceIds.length === 0)
|
|
79
|
+
return (0, neverthrow_1.ok)({});
|
|
80
|
+
const taggings = await db
|
|
81
|
+
.select({
|
|
82
|
+
resourceId: this.schema.taggings.resourceId,
|
|
83
|
+
tagId: this.schema.taggings.tagId,
|
|
84
|
+
})
|
|
85
|
+
.from(this.schema.taggings)
|
|
86
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.schema.taggings.resourceType, data.resourceType), (0, drizzle_orm_1.inArray)(this.schema.taggings.resourceId, data.resourceIds)));
|
|
87
|
+
if (taggings.length === 0)
|
|
88
|
+
return (0, neverthrow_1.ok)({});
|
|
89
|
+
const tagIds = Array.from(new Set(taggings.map((tagging) => tagging.tagId)));
|
|
90
|
+
const tags = await db
|
|
91
|
+
.select()
|
|
92
|
+
.from(this.schema.tags)
|
|
93
|
+
.where((0, drizzle_orm_1.inArray)(this.schema.tags.id, tagIds));
|
|
94
|
+
const tagById = tags.reduce((acc, tagRow) => {
|
|
95
|
+
acc[tagRow.id] = tagRow;
|
|
96
|
+
return acc;
|
|
97
|
+
}, {});
|
|
98
|
+
const grouped = taggings.reduce((acc, tagging) => {
|
|
99
|
+
const tagRow = tagById[tagging.tagId];
|
|
100
|
+
if (!tagRow)
|
|
101
|
+
return acc;
|
|
102
|
+
const existing = acc[tagging.resourceId] ?? [];
|
|
103
|
+
acc[tagging.resourceId] = [...existing, tagRow];
|
|
104
|
+
return acc;
|
|
105
|
+
}, {});
|
|
106
|
+
return (0, neverthrow_1.ok)(grouped);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
async attachTagsToResources(resourceType, rows, tx) {
|
|
110
|
+
return this.throwableAsync(async () => {
|
|
111
|
+
if (rows.length === 0)
|
|
112
|
+
return (0, neverthrow_1.ok)([]);
|
|
113
|
+
const tagsResult = await this.findTagsForResources({ resourceType, resourceIds: rows.map((row) => row.id) }, tx);
|
|
114
|
+
if (tagsResult.isErr())
|
|
115
|
+
return (0, neverthrow_1.err)(tagsResult.error);
|
|
116
|
+
const tagsByResource = tagsResult.value;
|
|
117
|
+
const withTags = rows.map((row) => ({
|
|
118
|
+
...row,
|
|
119
|
+
tags: tagsByResource[row.id] ?? [],
|
|
120
|
+
}));
|
|
121
|
+
return (0, neverthrow_1.ok)(withTags);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
async listTaggings(input, tx) {
|
|
125
|
+
return this.throwableAsync(async () => {
|
|
126
|
+
const db = tx ?? this.orm;
|
|
127
|
+
const filters = [(0, drizzle_orm_1.eq)(this.schema.taggings.resourceType, input.resourceType)];
|
|
128
|
+
if (input.resourceIds?.length) {
|
|
129
|
+
filters.push((0, drizzle_orm_1.inArray)(this.schema.taggings.resourceId, input.resourceIds));
|
|
130
|
+
}
|
|
131
|
+
const rows = await db
|
|
132
|
+
.select()
|
|
133
|
+
.from(this.schema.taggings)
|
|
134
|
+
.where((0, drizzle_orm_1.and)(...filters));
|
|
135
|
+
return (0, neverthrow_1.ok)(rows);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
async list(input, tx) {
|
|
139
|
+
return this.throwableAsync(async () => {
|
|
140
|
+
const db = tx ?? this.orm;
|
|
141
|
+
const conditions = this.getConditionBuilder(this.table);
|
|
142
|
+
conditions.applyFilters(input);
|
|
143
|
+
if (input?.assignableTo) {
|
|
144
|
+
conditions.push(this.helpers.arrayContains(this.table.assignableTo, [input.assignableTo]));
|
|
145
|
+
}
|
|
146
|
+
const whereClause = conditions.join();
|
|
147
|
+
const rowsQuery = this.withSortingAndPagination(db.select().from(this.table).where(whereClause), input || {});
|
|
148
|
+
const countQuery = db.select({ count: (0, drizzle_orm_1.count)() }).from(this.table).where(whereClause);
|
|
149
|
+
const [rows, [totalResult]] = await Promise.all([rowsQuery, countQuery]);
|
|
150
|
+
return (0, neverthrow_1.ok)({ rows, total: totalResult?.count ?? 0 });
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
exports.TagRepository = TagRepository;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TagService = void 0;
|
|
4
|
+
const base_service_1 = require("#modules/base/base.service");
|
|
5
|
+
class TagService extends base_service_1.BaseService {
|
|
6
|
+
async list(input) {
|
|
7
|
+
return this.repository.tag.list(input);
|
|
8
|
+
}
|
|
9
|
+
async listTaggings(input) {
|
|
10
|
+
return this.repository.tag.listTaggings(input);
|
|
11
|
+
}
|
|
12
|
+
async create(data, { user }) {
|
|
13
|
+
return this.repository.tag.create({ ...data, userId: user.id });
|
|
14
|
+
}
|
|
15
|
+
async update(data, { user }) {
|
|
16
|
+
return this.repository.tag.update({ ...data, userId: user.id });
|
|
17
|
+
}
|
|
18
|
+
async link(data, { user }) {
|
|
19
|
+
return this.repository.tag.link({ ...data, userId: user.id });
|
|
20
|
+
}
|
|
21
|
+
async linkBulk(data) {
|
|
22
|
+
return this.repository.tag.linkBulk(data);
|
|
23
|
+
}
|
|
24
|
+
async set(data) {
|
|
25
|
+
return this.repository.tag.set(data);
|
|
26
|
+
}
|
|
27
|
+
async unlink(data, { user }) {
|
|
28
|
+
return this.repository.tag.unlink({ ...data, userId: user.id });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.TagService = TagService;
|
|
@@ -4,9 +4,9 @@ export declare function createTagTRPC(tagService: TagService): import("@trpc/ser
|
|
|
4
4
|
session: {
|
|
5
5
|
id: string;
|
|
6
6
|
userId: string;
|
|
7
|
-
expiresAt: Date;
|
|
8
|
-
createdAt: Date;
|
|
9
7
|
updatedAt: Date;
|
|
8
|
+
createdAt: Date;
|
|
9
|
+
expiresAt: Date;
|
|
10
10
|
token: string;
|
|
11
11
|
ipAddress: string | null;
|
|
12
12
|
userAgent: string | null;
|
|
@@ -18,13 +18,12 @@ export declare function createTagTRPC(tagService: TagService): import("@trpc/ser
|
|
|
18
18
|
};
|
|
19
19
|
user: {
|
|
20
20
|
name: string;
|
|
21
|
-
image: string | null;
|
|
22
21
|
id: string;
|
|
23
|
-
createdAt: Date;
|
|
24
22
|
updatedAt: Date;
|
|
25
23
|
email: string;
|
|
26
|
-
metadata: Record<string, unknown>;
|
|
27
24
|
emailVerified: boolean;
|
|
25
|
+
image: string | null;
|
|
26
|
+
createdAt: Date;
|
|
28
27
|
role: string | null;
|
|
29
28
|
banned: boolean | null;
|
|
30
29
|
banReason: string | null;
|
|
@@ -34,6 +33,7 @@ export declare function createTagTRPC(tagService: TagService): import("@trpc/ser
|
|
|
34
33
|
paymentPlanTier: string | null;
|
|
35
34
|
paymentPlanExpiresAt: Date | null;
|
|
36
35
|
preferences: string | null;
|
|
36
|
+
metadata: Record<string, unknown>;
|
|
37
37
|
onboarding: number | null;
|
|
38
38
|
flags: string | null;
|
|
39
39
|
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createTagTRPC = createTagTRPC;
|
|
4
|
+
const tag_schema_1 = require("@m5kdev/commons/modules/tag/tag.schema");
|
|
5
|
+
const tag_dto_1 = require("#modules/tag/tag.dto");
|
|
6
|
+
const _trpc_1 = require("#trpc");
|
|
7
|
+
function createTagTRPC(tagService) {
|
|
8
|
+
const tagListInput = tag_schema_1.tagListInputSchema.extend({
|
|
9
|
+
assignableTo: tag_schema_1.tagListSchema.shape.assignableTo,
|
|
10
|
+
});
|
|
11
|
+
return (0, _trpc_1.router)({
|
|
12
|
+
list: _trpc_1.procedure
|
|
13
|
+
.input(tagListInput)
|
|
14
|
+
.output(tag_schema_1.tagListOutputSchema)
|
|
15
|
+
.query(async ({ input }) => (0, _trpc_1.handleTRPCResult)(await tagService.list(input))),
|
|
16
|
+
listTaggings: _trpc_1.procedure
|
|
17
|
+
.input(tag_schema_1.taggingSchema
|
|
18
|
+
.pick({ resourceType: true })
|
|
19
|
+
.extend({ resourceIds: tag_schema_1.tagListInputSchema.shape.filters.optional() }))
|
|
20
|
+
.output(tag_dto_1.taggingsSelectOutput.array())
|
|
21
|
+
.query(async ({ input }) => (0, _trpc_1.handleTRPCResult)(await tagService.listTaggings(input))),
|
|
22
|
+
create: _trpc_1.procedure
|
|
23
|
+
.input(tag_schema_1.tagCreateSchema)
|
|
24
|
+
.output(tag_dto_1.tagsSelectOutput)
|
|
25
|
+
.mutation(async ({ ctx, input }) => {
|
|
26
|
+
return (0, _trpc_1.handleTRPCResult)(await tagService.create(input, ctx));
|
|
27
|
+
}),
|
|
28
|
+
update: _trpc_1.procedure
|
|
29
|
+
.input(tag_schema_1.tagUpdateSchema)
|
|
30
|
+
.output(tag_dto_1.tagsSelectOutput)
|
|
31
|
+
.mutation(async ({ ctx, input }) => {
|
|
32
|
+
return (0, _trpc_1.handleTRPCResult)(await tagService.update(input, ctx));
|
|
33
|
+
}),
|
|
34
|
+
link: _trpc_1.procedure
|
|
35
|
+
.input(tag_schema_1.tagLinkSchema)
|
|
36
|
+
.output(tag_dto_1.taggingsSelectOutput)
|
|
37
|
+
.mutation(async ({ ctx, input }) => {
|
|
38
|
+
return (0, _trpc_1.handleTRPCResult)(await tagService.link(input, ctx));
|
|
39
|
+
}),
|
|
40
|
+
unlink: _trpc_1.procedure
|
|
41
|
+
.input(tag_schema_1.tagLinkSchema)
|
|
42
|
+
.output(tag_dto_1.tagsSelectOutput)
|
|
43
|
+
.mutation(async ({ ctx, input }) => {
|
|
44
|
+
return (0, _trpc_1.handleTRPCResult)(await tagService.unlink(input, ctx));
|
|
45
|
+
}),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Applies pagination (limit and offset) to a drizzle query builder.
|
|
4
|
+
* Returns the query builder with pagination applied, or the original query if no limit is specified.
|
|
5
|
+
* Page is 1-based and only applied if limit is also provided.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.applyPagination = void 0;
|
|
9
|
+
const applyPagination = (query, limit, page) => {
|
|
10
|
+
if (limit)
|
|
11
|
+
query.limit(limit);
|
|
12
|
+
if (page && page > 1 && limit)
|
|
13
|
+
query.offset((page - 1) * limit);
|
|
14
|
+
return query;
|
|
15
|
+
};
|
|
16
|
+
exports.applyPagination = applyPagination;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.applySorting = void 0;
|
|
4
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
5
|
+
/**
|
|
6
|
+
* Applies sorting to a drizzle query builder.
|
|
7
|
+
* Returns the query builder with sorting applied.
|
|
8
|
+
* If no sort or order is specified, defaults to createdAt descending.
|
|
9
|
+
* If createdAt column doesn't exist, returns the query unchanged.
|
|
10
|
+
*/
|
|
11
|
+
const applySorting = (query, table, sort, order) => {
|
|
12
|
+
const column = sort ? table[sort] : table.createdAt || table.id;
|
|
13
|
+
if (!column)
|
|
14
|
+
throw new Error(`Column ${sort} not found in table ${table.name}`);
|
|
15
|
+
query.orderBy(order === "asc" ? (0, drizzle_orm_1.asc)(column) : (0, drizzle_orm_1.desc)(column));
|
|
16
|
+
return query;
|
|
17
|
+
};
|
|
18
|
+
exports.applySorting = applySorting;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getConditionsFromFilters = void 0;
|
|
4
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
5
|
+
const luxon_1 = require("luxon");
|
|
6
|
+
// Helper: Create UTC date boundaries from ISO string
|
|
7
|
+
const getUTCDateBoundaries = (isoString) => {
|
|
8
|
+
const dateTime = luxon_1.DateTime.fromISO(isoString, { zone: "utc" });
|
|
9
|
+
return {
|
|
10
|
+
start: dateTime.startOf("day").toJSDate(),
|
|
11
|
+
end: dateTime.endOf("day").toJSDate(),
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
const getConditionsFromFilters = (conditions, filters, table) => {
|
|
15
|
+
if (!filters || filters.length === 0) {
|
|
16
|
+
return conditions;
|
|
17
|
+
}
|
|
18
|
+
// Process each filter (maximum one filter per column)
|
|
19
|
+
for (const filter of filters) {
|
|
20
|
+
const { columnId, type, method, value, valueTo } = filter;
|
|
21
|
+
// Get the column from the table using columnId
|
|
22
|
+
const column = table[columnId];
|
|
23
|
+
if (!column) {
|
|
24
|
+
continue; // Skip if column doesn't exist
|
|
25
|
+
}
|
|
26
|
+
// Handle isEmpty/isNotEmpty methods (work across types, ignore value)
|
|
27
|
+
if (method === "isEmpty" || method === "isNotEmpty") {
|
|
28
|
+
switch (type) {
|
|
29
|
+
case "string":
|
|
30
|
+
case "enum":
|
|
31
|
+
// isEmpty: IS NULL OR = ''
|
|
32
|
+
// isNotEmpty: IS NOT NULL AND != ''
|
|
33
|
+
if (method === "isEmpty") {
|
|
34
|
+
conditions.push((0, drizzle_orm_1.or)((0, drizzle_orm_1.isNull)(column), (0, drizzle_orm_1.eq)(column, "")));
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
conditions.push((0, drizzle_orm_1.and)((0, drizzle_orm_1.isNotNull)(column), (0, drizzle_orm_1.ne)(column, "")));
|
|
38
|
+
}
|
|
39
|
+
continue;
|
|
40
|
+
case "number":
|
|
41
|
+
// isEmpty: IS NULL OR = 0
|
|
42
|
+
// isNotEmpty: IS NOT NULL AND != 0
|
|
43
|
+
if (method === "isEmpty") {
|
|
44
|
+
conditions.push((0, drizzle_orm_1.or)((0, drizzle_orm_1.isNull)(column), (0, drizzle_orm_1.eq)(column, 0)));
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
conditions.push((0, drizzle_orm_1.and)((0, drizzle_orm_1.isNotNull)(column), (0, drizzle_orm_1.ne)(column, 0)));
|
|
48
|
+
}
|
|
49
|
+
continue;
|
|
50
|
+
case "boolean":
|
|
51
|
+
// Should not happen per plan, but handle gracefully
|
|
52
|
+
continue;
|
|
53
|
+
default:
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Apply filter based on type and method
|
|
58
|
+
switch (type) {
|
|
59
|
+
case "string":
|
|
60
|
+
switch (method) {
|
|
61
|
+
case "contains":
|
|
62
|
+
if (typeof value === "string") {
|
|
63
|
+
conditions.push((0, drizzle_orm_1.like)(column, `%${value}%`));
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
case "equals":
|
|
67
|
+
if (typeof value === "string") {
|
|
68
|
+
conditions.push((0, drizzle_orm_1.eq)(column, value));
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
case "starts_with":
|
|
72
|
+
if (typeof value === "string") {
|
|
73
|
+
conditions.push((0, drizzle_orm_1.like)(column, `${value}%`));
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
case "ends_with":
|
|
77
|
+
if (typeof value === "string") {
|
|
78
|
+
conditions.push((0, drizzle_orm_1.like)(column, `%${value}`));
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
case "is_null":
|
|
82
|
+
conditions.push((0, drizzle_orm_1.isNull)(column));
|
|
83
|
+
break;
|
|
84
|
+
case "is_not_null":
|
|
85
|
+
conditions.push((0, drizzle_orm_1.isNotNull)(column));
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
case "number":
|
|
90
|
+
switch (method) {
|
|
91
|
+
case "equals":
|
|
92
|
+
if (typeof value === "number") {
|
|
93
|
+
conditions.push((0, drizzle_orm_1.eq)(column, value));
|
|
94
|
+
}
|
|
95
|
+
break;
|
|
96
|
+
case "greater_than":
|
|
97
|
+
if (typeof value === "number") {
|
|
98
|
+
conditions.push((0, drizzle_orm_1.gte)(column, value));
|
|
99
|
+
}
|
|
100
|
+
break;
|
|
101
|
+
case "less_than":
|
|
102
|
+
if (typeof value === "number") {
|
|
103
|
+
conditions.push((0, drizzle_orm_1.lte)(column, value));
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
case "is_null":
|
|
107
|
+
conditions.push((0, drizzle_orm_1.isNull)(column));
|
|
108
|
+
break;
|
|
109
|
+
case "is_not_null":
|
|
110
|
+
conditions.push((0, drizzle_orm_1.isNotNull)(column));
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
case "date":
|
|
115
|
+
if (typeof value !== "string")
|
|
116
|
+
break;
|
|
117
|
+
switch (method) {
|
|
118
|
+
case "on": {
|
|
119
|
+
const { start, end } = getUTCDateBoundaries(value);
|
|
120
|
+
conditions.push((0, drizzle_orm_1.and)((0, drizzle_orm_1.gte)(column, start), (0, drizzle_orm_1.lte)(column, end)));
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case "between":
|
|
124
|
+
if (valueTo) {
|
|
125
|
+
const { start } = getUTCDateBoundaries(value);
|
|
126
|
+
const { end } = getUTCDateBoundaries(valueTo);
|
|
127
|
+
conditions.push((0, drizzle_orm_1.between)(column, start, end));
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
case "before": {
|
|
131
|
+
const { end } = getUTCDateBoundaries(value);
|
|
132
|
+
conditions.push((0, drizzle_orm_1.lte)(column, end));
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
case "after": {
|
|
136
|
+
const { start } = getUTCDateBoundaries(value);
|
|
137
|
+
conditions.push((0, drizzle_orm_1.gte)(column, start));
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
case "intersect": {
|
|
141
|
+
// Interval overlap: [columnId, endColumnId] intersects with [value, valueTo]
|
|
142
|
+
// Logic: columnId <= valueTo AND (endColumnId IS NULL OR endColumnId >= value)
|
|
143
|
+
if (!valueTo || !filter.endColumnId)
|
|
144
|
+
break;
|
|
145
|
+
const endColumn = table[filter.endColumnId];
|
|
146
|
+
if (!endColumn)
|
|
147
|
+
break;
|
|
148
|
+
const { start } = getUTCDateBoundaries(value);
|
|
149
|
+
const { end } = getUTCDateBoundaries(valueTo);
|
|
150
|
+
conditions.push((0, drizzle_orm_1.and)((0, drizzle_orm_1.lte)(column, end), (0, drizzle_orm_1.or)((0, drizzle_orm_1.isNull)(endColumn), (0, drizzle_orm_1.gte)(endColumn, start))));
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
case "is_null":
|
|
154
|
+
conditions.push((0, drizzle_orm_1.isNull)(column));
|
|
155
|
+
break;
|
|
156
|
+
case "is_not_null":
|
|
157
|
+
conditions.push((0, drizzle_orm_1.isNotNull)(column));
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
break;
|
|
161
|
+
case "boolean":
|
|
162
|
+
switch (method) {
|
|
163
|
+
case "equals":
|
|
164
|
+
if (typeof value === "boolean") {
|
|
165
|
+
conditions.push((0, drizzle_orm_1.eq)(column, value));
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
case "is_null":
|
|
169
|
+
conditions.push((0, drizzle_orm_1.isNull)(column));
|
|
170
|
+
break;
|
|
171
|
+
case "is_not_null":
|
|
172
|
+
conditions.push((0, drizzle_orm_1.isNotNull)(column));
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
break;
|
|
176
|
+
case "enum":
|
|
177
|
+
switch (method) {
|
|
178
|
+
case "oneOf":
|
|
179
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
180
|
+
conditions.push((0, drizzle_orm_1.inArray)(column, value));
|
|
181
|
+
}
|
|
182
|
+
break;
|
|
183
|
+
case "equals":
|
|
184
|
+
if (typeof value === "string") {
|
|
185
|
+
conditions.push((0, drizzle_orm_1.eq)(column, value));
|
|
186
|
+
}
|
|
187
|
+
break;
|
|
188
|
+
case "is_null":
|
|
189
|
+
conditions.push((0, drizzle_orm_1.isNull)(column));
|
|
190
|
+
break;
|
|
191
|
+
case "is_not_null":
|
|
192
|
+
conditions.push((0, drizzle_orm_1.isNotNull)(column));
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return conditions;
|
|
199
|
+
};
|
|
200
|
+
exports.getConditionsFromFilters = getConditionsFromFilters;
|