@m5kdev/backend 0.1.1 → 0.1.2

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.
Files changed (113) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +9 -0
  3. package/dist/src/lib/posthog.js +7 -0
  4. package/dist/src/lib/sentry.js +9 -0
  5. package/dist/src/modules/access/access.repository.js +32 -0
  6. package/dist/src/modules/access/access.service.js +51 -0
  7. package/dist/src/modules/access/access.test.js +182 -0
  8. package/dist/src/modules/access/access.utils.js +20 -0
  9. package/dist/src/modules/ai/ai.db.js +39 -0
  10. package/dist/src/modules/ai/ai.prompt.js +30 -0
  11. package/dist/src/modules/ai/ai.repository.js +26 -0
  12. package/dist/src/modules/ai/ai.router.js +132 -0
  13. package/dist/src/modules/ai/ai.service.js +207 -0
  14. package/dist/src/modules/ai/ai.trpc.d.ts +5 -5
  15. package/dist/src/modules/ai/ai.trpc.js +20 -0
  16. package/dist/src/modules/ai/ideogram/ideogram.constants.js +167 -0
  17. package/dist/src/modules/ai/ideogram/ideogram.dto.js +49 -0
  18. package/dist/src/modules/ai/ideogram/ideogram.prompt.js +860 -0
  19. package/dist/src/modules/ai/ideogram/ideogram.repository.js +46 -0
  20. package/dist/src/modules/ai/ideogram/ideogram.service.js +11 -0
  21. package/dist/src/modules/auth/auth.db.js +215 -0
  22. package/dist/src/modules/auth/auth.dto.js +38 -0
  23. package/dist/src/modules/auth/auth.lib.d.ts +4 -4
  24. package/dist/src/modules/auth/auth.lib.js +284 -0
  25. package/dist/src/modules/auth/auth.middleware.js +52 -0
  26. package/dist/src/modules/auth/auth.repository.js +541 -0
  27. package/dist/src/modules/auth/auth.service.js +201 -0
  28. package/dist/src/modules/auth/auth.trpc.d.ts +18 -18
  29. package/dist/src/modules/auth/auth.trpc.js +157 -0
  30. package/dist/src/modules/auth/auth.utils.js +97 -0
  31. package/dist/src/modules/base/base.abstract.js +53 -0
  32. package/dist/src/modules/base/base.dto.js +112 -0
  33. package/dist/src/modules/base/base.grants.js +123 -0
  34. package/dist/src/modules/base/base.grants.test.js +668 -0
  35. package/dist/src/modules/base/base.repository.js +307 -0
  36. package/dist/src/modules/base/base.service.js +109 -0
  37. package/dist/src/modules/base/base.types.js +2 -0
  38. package/dist/src/modules/billing/billing.db.js +29 -0
  39. package/dist/src/modules/billing/billing.repository.js +235 -0
  40. package/dist/src/modules/billing/billing.router.js +56 -0
  41. package/dist/src/modules/billing/billing.service.js +147 -0
  42. package/dist/src/modules/billing/billing.trpc.d.ts +5 -5
  43. package/dist/src/modules/billing/billing.trpc.js +17 -0
  44. package/dist/src/modules/clay/clay.repository.js +26 -0
  45. package/dist/src/modules/clay/clay.service.js +24 -0
  46. package/dist/src/modules/connect/connect.db.js +30 -0
  47. package/dist/src/modules/connect/connect.dto.js +36 -0
  48. package/dist/src/modules/connect/connect.linkedin.js +53 -0
  49. package/dist/src/modules/connect/connect.oauth.js +198 -0
  50. package/dist/src/modules/connect/connect.repository.d.ts +7 -7
  51. package/dist/src/modules/connect/connect.repository.js +54 -0
  52. package/dist/src/modules/connect/connect.router.js +54 -0
  53. package/dist/src/modules/connect/connect.service.d.ts +14 -14
  54. package/dist/src/modules/connect/connect.service.js +114 -0
  55. package/dist/src/modules/connect/connect.trpc.d.ts +10 -10
  56. package/dist/src/modules/connect/connect.trpc.js +21 -0
  57. package/dist/src/modules/connect/connect.types.js +2 -0
  58. package/dist/src/modules/crypto/crypto.db.js +17 -0
  59. package/dist/src/modules/crypto/crypto.repository.js +10 -0
  60. package/dist/src/modules/crypto/crypto.service.js +52 -0
  61. package/dist/src/modules/email/email.service.js +107 -0
  62. package/dist/src/modules/file/file.repository.js +79 -0
  63. package/dist/src/modules/file/file.router.js +99 -0
  64. package/dist/src/modules/file/file.service.js +150 -0
  65. package/dist/src/modules/recurrence/recurrence.db.js +66 -0
  66. package/dist/src/modules/recurrence/recurrence.repository.js +39 -0
  67. package/dist/src/modules/recurrence/recurrence.service.js +70 -0
  68. package/dist/src/modules/recurrence/recurrence.trpc.d.ts +15 -15
  69. package/dist/src/modules/recurrence/recurrence.trpc.js +65 -0
  70. package/dist/src/modules/social/social.dto.js +18 -0
  71. package/dist/src/modules/social/social.linkedin.js +427 -0
  72. package/dist/src/modules/social/social.linkedin.test.js +235 -0
  73. package/dist/src/modules/social/social.service.js +76 -0
  74. package/dist/src/modules/social/social.types.js +2 -0
  75. package/dist/src/modules/tag/tag.db.js +42 -0
  76. package/dist/src/modules/tag/tag.dto.js +9 -0
  77. package/dist/src/modules/tag/tag.repository.js +154 -0
  78. package/dist/src/modules/tag/tag.service.js +31 -0
  79. package/dist/src/modules/tag/tag.trpc.d.ts +5 -5
  80. package/dist/src/modules/tag/tag.trpc.js +47 -0
  81. package/dist/src/modules/utils/applyPagination.js +16 -0
  82. package/dist/src/modules/utils/applySorting.js +18 -0
  83. package/dist/src/modules/utils/getConditionsFromFilters.js +200 -0
  84. package/dist/src/modules/video/video.service.js +84 -0
  85. package/dist/src/modules/webhook/webhook.constants.js +10 -0
  86. package/dist/src/modules/webhook/webhook.db.js +17 -0
  87. package/dist/src/modules/webhook/webhook.dto.js +7 -0
  88. package/dist/src/modules/webhook/webhook.repository.js +56 -0
  89. package/dist/src/modules/webhook/webhook.router.js +30 -0
  90. package/dist/src/modules/webhook/webhook.service.js +68 -0
  91. package/dist/src/modules/workflow/workflow.db.js +30 -0
  92. package/dist/src/modules/workflow/workflow.repository.js +105 -0
  93. package/dist/src/modules/workflow/workflow.service.js +37 -0
  94. package/dist/src/modules/workflow/workflow.trpc.d.ts +5 -5
  95. package/dist/src/modules/workflow/workflow.trpc.js +21 -0
  96. package/dist/src/modules/workflow/workflow.types.js +2 -0
  97. package/dist/src/modules/workflow/workflow.utils.js +173 -0
  98. package/dist/src/test/stubs/utils.js +5 -0
  99. package/dist/src/trpc/context.d.ts +5 -5
  100. package/dist/src/trpc/context.js +17 -0
  101. package/dist/src/trpc/index.js +6 -0
  102. package/dist/src/trpc/procedures.d.ts +56 -56
  103. package/dist/src/trpc/procedures.js +32 -0
  104. package/dist/src/trpc/utils.js +20 -0
  105. package/dist/src/types.d.ts +33 -33
  106. package/dist/src/types.js +13 -0
  107. package/dist/src/utils/errors.js +104 -0
  108. package/dist/src/utils/logger.js +11 -0
  109. package/dist/src/utils/posthog.js +31 -0
  110. package/dist/src/utils/types.js +2 -0
  111. package/dist/tsconfig.tsbuildinfo +1 -1
  112. package/package.json +3 -3
  113. package/tsconfig.json +2 -0
@@ -0,0 +1,307 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BaseExternaRepository = exports.BaseTableRepository = exports.BaseRepository = exports.arrayContains = exports.TableConditionBuilder = exports.ConditionBuilder = void 0;
4
+ const drizzle_orm_1 = require("drizzle-orm");
5
+ const neverthrow_1 = require("neverthrow");
6
+ const base_abstract_1 = require("#modules/base/base.abstract");
7
+ const base_dto_1 = require("#modules/base/base.dto");
8
+ const applyPagination_1 = require("#modules/utils/applyPagination");
9
+ const applySorting_1 = require("#modules/utils/applySorting");
10
+ const getConditionsFromFilters_1 = require("#modules/utils/getConditionsFromFilters");
11
+ class ConditionBuilder {
12
+ conditions;
13
+ constructor(conditions = []) {
14
+ this.conditions = conditions;
15
+ this.conditions = conditions;
16
+ }
17
+ push(condition) {
18
+ if (condition)
19
+ this.conditions.push(condition);
20
+ }
21
+ join(type = "and") {
22
+ if (this.conditions.length === 0)
23
+ return undefined;
24
+ if (this.conditions.length === 1)
25
+ return this.conditions[0];
26
+ return type === "and" ? (0, drizzle_orm_1.and)(...this.conditions) : (0, drizzle_orm_1.or)(...this.conditions);
27
+ }
28
+ [Symbol.iterator]() {
29
+ return this.conditions[Symbol.iterator]();
30
+ }
31
+ }
32
+ exports.ConditionBuilder = ConditionBuilder;
33
+ class TableConditionBuilder extends ConditionBuilder {
34
+ table;
35
+ constructor(table) {
36
+ super();
37
+ this.table = table;
38
+ }
39
+ applyFilters({ filters } = {}) {
40
+ if (filters && filters.length > 0)
41
+ (0, getConditionsFromFilters_1.getConditionsFromFilters)(this, filters, this.table);
42
+ }
43
+ }
44
+ exports.TableConditionBuilder = TableConditionBuilder;
45
+ const arrayContains = (table, values) => {
46
+ const arrayContains = [];
47
+ for (const value of values) {
48
+ arrayContains.push((0, drizzle_orm_1.like)(table, `%"${value}%"`));
49
+ }
50
+ return (0, drizzle_orm_1.or)(...arrayContains);
51
+ };
52
+ exports.arrayContains = arrayContains;
53
+ class BaseRepository extends base_abstract_1.Base {
54
+ orm;
55
+ schema;
56
+ repository;
57
+ constructor(options, repository) {
58
+ super("repository");
59
+ this.orm = options.orm;
60
+ this.schema = options.schema;
61
+ this.repository = repository;
62
+ }
63
+ getConditionBuilder(table) {
64
+ if (table === undefined) {
65
+ return new ConditionBuilder();
66
+ }
67
+ return new TableConditionBuilder(table);
68
+ }
69
+ withPagination(query, { page, limit }) {
70
+ return (0, applyPagination_1.applyPagination)(query, limit, page);
71
+ }
72
+ withSorting(query, { sort, order }, table) {
73
+ if (!table)
74
+ throw new Error("No table provided");
75
+ return (0, applySorting_1.applySorting)(query, table, sort, order);
76
+ }
77
+ withSortingAndPagination(query, { sort, order, page, limit }, table) {
78
+ if (!table)
79
+ throw new Error("No table provided");
80
+ return this.withSorting(this.withPagination(query, { page, limit }), { sort, order }, table);
81
+ }
82
+ addUserIdFilter(userId, query) {
83
+ const userIdFilter = {
84
+ columnId: "userId",
85
+ type: "string",
86
+ method: "equals",
87
+ value: userId,
88
+ };
89
+ return query
90
+ ? { ...query, filters: [...(query?.filters ?? []), userIdFilter] }
91
+ : { filters: [userIdFilter] };
92
+ }
93
+ helpers = {
94
+ pickColumns: base_dto_1.pickColumns,
95
+ arrayContains: exports.arrayContains,
96
+ ConditionBuilder,
97
+ };
98
+ }
99
+ exports.BaseRepository = BaseRepository;
100
+ /**
101
+ * Generic table-bound repository with typed CRUD, returning ServerResultAsync via throwableAsync.
102
+ *
103
+ * Example:
104
+ * const userRepo = new UserRepository(db, schema);
105
+ * class UserRepository extends BaseTableRepository<typeof schema.user> {
106
+ * constructor(db: LibSQLDatabase<typeof schema>, schema: typeof schema) {
107
+ * super(db, schema, schema.user);
108
+ * }
109
+ * }
110
+ */
111
+ class BaseTableRepository extends BaseRepository {
112
+ table;
113
+ idKey;
114
+ idColumn;
115
+ constructor(options, repository) {
116
+ super({ orm: options.orm, schema: options.schema }, repository);
117
+ this.table = options.table;
118
+ this.idKey = options.idKey ?? "id";
119
+ this.idColumn = this.table[this.idKey];
120
+ }
121
+ withSorting(query, { sort, order }, table) {
122
+ return super.withSorting(query, { sort, order }, table || this.table);
123
+ }
124
+ withSortingAndPagination(query, { sort, order, page, limit }, table) {
125
+ return super.withSortingAndPagination(query, { sort, order, page, limit }, table || this.table);
126
+ }
127
+ async queryList(query, options, tx) {
128
+ return this.throwableAsync(async () => {
129
+ const db = tx ?? this.orm;
130
+ const conditions = options?.conditions ?? this.getConditionBuilder(this.table);
131
+ conditions.applyFilters(query);
132
+ const whereClause = conditions.join();
133
+ const rowsQuery = this.withSortingAndPagination((options?.select ? db.select(options.select) : db.select())
134
+ .from(this.table)
135
+ .where(whereClause), query || {});
136
+ const countQuery = db
137
+ .select({ count: (0, drizzle_orm_1.count)() })
138
+ .from(this.table)
139
+ .where(whereClause);
140
+ const [rows, [totalResult]] = await Promise.all([rowsQuery, countQuery]);
141
+ return (0, neverthrow_1.ok)({ rows: rows, total: totalResult?.count ?? 0 });
142
+ });
143
+ }
144
+ async findById(id, tx) {
145
+ return this.throwableAsync(async () => {
146
+ const db = tx ?? this.orm;
147
+ const rows = (await db
148
+ .select()
149
+ .from(this.table)
150
+ .where((0, drizzle_orm_1.eq)(this.idColumn, id)));
151
+ return (0, neverthrow_1.ok)(rows[0]);
152
+ });
153
+ }
154
+ async findManyById(ids, tx) {
155
+ return this.throwableAsync(async () => {
156
+ const db = tx ?? this.orm;
157
+ if (ids.length === 0) {
158
+ return (0, neverthrow_1.ok)([]);
159
+ }
160
+ const rows = (await db
161
+ .select()
162
+ .from(this.table)
163
+ .where((0, drizzle_orm_1.inArray)(this.idColumn, ids)));
164
+ return (0, neverthrow_1.ok)(rows);
165
+ });
166
+ }
167
+ async create(data, tx) {
168
+ return this.throwableAsync(async () => {
169
+ const db = tx ?? this.orm;
170
+ const rows = (await db
171
+ .insert(this.table)
172
+ .values(data)
173
+ .returning());
174
+ if (rows.length === 0)
175
+ return this.error("UNPROCESSABLE_CONTENT");
176
+ return (0, neverthrow_1.ok)(rows[0]);
177
+ });
178
+ }
179
+ async createMany(data, tx) {
180
+ return this.throwableAsync(async () => {
181
+ const db = tx ?? this.orm;
182
+ if (data.length === 0) {
183
+ return (0, neverthrow_1.ok)([]);
184
+ }
185
+ const rows = (await db
186
+ .insert(this.table)
187
+ .values(data)
188
+ .returning());
189
+ return (0, neverthrow_1.ok)(rows);
190
+ });
191
+ }
192
+ async update(data, tx) {
193
+ return this.throwableAsync(async () => {
194
+ const db = tx ?? this.orm;
195
+ const single = data;
196
+ const id = String(single[this.idKey]);
197
+ const { [this.idKey]: _removed, ...rest } = single;
198
+ const update = rest;
199
+ if (this.table.updatedAt)
200
+ update.updatedAt = new Date();
201
+ const rows = (await db
202
+ .update(this.table)
203
+ .set(update)
204
+ .where((0, drizzle_orm_1.eq)(this.idColumn, id))
205
+ .returning());
206
+ const [row] = rows;
207
+ if (!row)
208
+ return this.error("NOT_FOUND");
209
+ return (0, neverthrow_1.ok)(row);
210
+ });
211
+ }
212
+ async updateMany(data, tx) {
213
+ return this.throwableAsync(async () => {
214
+ const db = tx ?? this.orm;
215
+ if (data.length === 0) {
216
+ return (0, neverthrow_1.ok)([]);
217
+ }
218
+ const results = [];
219
+ for (const item of data) {
220
+ const record = item;
221
+ const id = String(record[this.idKey]);
222
+ const { [this.idKey]: _removed, ...rest } = record;
223
+ const update = rest;
224
+ if (this.table.updatedAt)
225
+ update.updatedAt = new Date();
226
+ const rows = (await db
227
+ .update(this.table)
228
+ .set(update)
229
+ .where((0, drizzle_orm_1.eq)(this.idColumn, id))
230
+ .returning());
231
+ if (rows[0])
232
+ results.push(rows[0]);
233
+ }
234
+ return (0, neverthrow_1.ok)(results);
235
+ });
236
+ }
237
+ async softDeleteById(id, tx) {
238
+ return this.throwableAsync(async () => {
239
+ const db = tx ?? this.orm;
240
+ if (!this.table.deletedAt)
241
+ return this.error("METHOD_NOT_SUPPORTED");
242
+ const rows = await db
243
+ .update(this.table)
244
+ .set({ deletedAt: new Date() })
245
+ .where((0, drizzle_orm_1.eq)(this.idColumn, id))
246
+ .returning({
247
+ id: this.idColumn,
248
+ });
249
+ if (rows.length === 0)
250
+ return this.error("NOT_FOUND");
251
+ return (0, neverthrow_1.ok)(rows[0]);
252
+ });
253
+ }
254
+ async softDeleteManyById(ids, tx) {
255
+ return this.throwableAsync(async () => {
256
+ const db = tx ?? this.orm;
257
+ if (!this.table.deletedAt)
258
+ return this.error("METHOD_NOT_SUPPORTED");
259
+ const rows = await db
260
+ .update(this.table)
261
+ .set({ deletedAt: new Date() })
262
+ .where((0, drizzle_orm_1.inArray)(this.idColumn, ids))
263
+ .returning({
264
+ id: this.idColumn,
265
+ });
266
+ if (rows.length === 0)
267
+ return this.error("NOT_FOUND");
268
+ return (0, neverthrow_1.ok)(rows);
269
+ });
270
+ }
271
+ async deleteById(id, tx) {
272
+ return this.throwableAsync(async () => {
273
+ const db = tx ?? this.orm;
274
+ const rows = await db
275
+ .delete(this.table)
276
+ .where((0, drizzle_orm_1.eq)(this.idColumn, id))
277
+ .returning({
278
+ id: this.idColumn,
279
+ });
280
+ if (rows.length === 0)
281
+ return this.error("NOT_FOUND");
282
+ return (0, neverthrow_1.ok)(rows[0]);
283
+ });
284
+ }
285
+ async deleteManyById(ids, tx) {
286
+ return this.throwableAsync(async () => {
287
+ const db = tx ?? this.orm;
288
+ if (ids.length === 0) {
289
+ return (0, neverthrow_1.ok)([]);
290
+ }
291
+ const rows = await db
292
+ .delete(this.table)
293
+ .where((0, drizzle_orm_1.inArray)(this.idColumn, ids))
294
+ .returning({
295
+ id: this.idColumn,
296
+ });
297
+ return (0, neverthrow_1.ok)(rows);
298
+ });
299
+ }
300
+ }
301
+ exports.BaseTableRepository = BaseTableRepository;
302
+ class BaseExternaRepository extends base_abstract_1.Base {
303
+ constructor() {
304
+ super("repository");
305
+ }
306
+ }
307
+ exports.BaseExternaRepository = BaseExternaRepository;
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BasePermissionService = exports.BaseService = void 0;
4
+ const neverthrow_1 = require("neverthrow");
5
+ const base_abstract_1 = require("#modules/base/base.abstract");
6
+ const base_grants_1 = require("#modules/base/base.grants");
7
+ class BaseService extends base_abstract_1.Base {
8
+ repository;
9
+ service;
10
+ constructor(repository = {}, service = {}) {
11
+ super("service");
12
+ this.repository = repository;
13
+ this.service = service;
14
+ this.repository = repository;
15
+ this.service = service;
16
+ }
17
+ addUserFilter(value, query, columnId = "userId", method = "equals") {
18
+ const userFilter = {
19
+ columnId,
20
+ type: "string",
21
+ method,
22
+ value,
23
+ };
24
+ return query
25
+ ? { ...query, filters: [...(query?.filters ?? []), userFilter] }
26
+ : { filters: [userFilter] };
27
+ }
28
+ addContextFilter(ctx, include = {
29
+ user: true,
30
+ organization: false,
31
+ team: false,
32
+ }, query, map = {
33
+ userId: {
34
+ columnId: "userId",
35
+ method: "equals",
36
+ },
37
+ organizationId: {
38
+ columnId: "organizationId",
39
+ method: "equals",
40
+ },
41
+ teamId: {
42
+ columnId: "teamId",
43
+ method: "equals",
44
+ },
45
+ }) {
46
+ const filters = [];
47
+ if (include.user) {
48
+ filters.push({
49
+ columnId: map.userId.columnId,
50
+ type: "string",
51
+ method: map.userId.method,
52
+ value: ctx.user.id,
53
+ });
54
+ }
55
+ if (include.organization) {
56
+ filters.push({
57
+ columnId: map.organizationId.columnId,
58
+ type: "string",
59
+ method: map.organizationId.method,
60
+ value: ctx.session.activeOrganizationId ?? "",
61
+ });
62
+ }
63
+ if (include.team) {
64
+ filters.push({
65
+ columnId: map.teamId.columnId,
66
+ type: "string",
67
+ method: map.teamId.method,
68
+ value: ctx.session.activeTeamId ?? "",
69
+ });
70
+ }
71
+ return query ? { ...query, filters: [...(query?.filters ?? []), ...filters] } : { filters };
72
+ }
73
+ }
74
+ exports.BaseService = BaseService;
75
+ class BasePermissionService extends BaseService {
76
+ grants;
77
+ constructor(repository, service, grants = []) {
78
+ super(repository, service);
79
+ this.grants = grants;
80
+ }
81
+ accessGuard(ctx, action, entities, grants) {
82
+ const hasPermission = this.checkPermission(ctx, action, entities, grants);
83
+ if (!hasPermission)
84
+ return this.error("FORBIDDEN");
85
+ return (0, neverthrow_1.ok)(true);
86
+ }
87
+ async accessGuardAsync(ctx, action, getEntities, grants) {
88
+ const hasPermission = await this.checkPermissionAsync(ctx, action, getEntities, grants);
89
+ if (hasPermission.isErr())
90
+ return (0, neverthrow_1.err)(hasPermission.error);
91
+ if (!hasPermission.value)
92
+ return this.error("FORBIDDEN");
93
+ return (0, neverthrow_1.ok)(true);
94
+ }
95
+ checkPermission(ctx, action, entities, grants) {
96
+ const actionGrants = grants ?? this.grants.filter((grant) => grant.action === action);
97
+ return (0, base_grants_1.checkPermissionSync)(ctx, actionGrants, entities);
98
+ }
99
+ async checkPermissionAsync(ctx, action, getEntities, grants) {
100
+ const actionGrants = grants ?? this.grants.filter((grant) => grant.action === action);
101
+ const permission = await (0, base_grants_1.checkPermissionAsync)(ctx, actionGrants, getEntities);
102
+ if (permission.isErr())
103
+ return this.error("INTERNAL_SERVER_ERROR", "Failed to check permission", {
104
+ cause: permission.error,
105
+ });
106
+ return (0, neverthrow_1.ok)(permission.value);
107
+ }
108
+ }
109
+ exports.BasePermissionService = BasePermissionService;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.subscriptions = void 0;
4
+ const sqlite_core_1 = require("drizzle-orm/sqlite-core");
5
+ const uuid_1 = require("uuid");
6
+ exports.subscriptions = (0, sqlite_core_1.sqliteTable)("subscriptions", {
7
+ id: (0, sqlite_core_1.text)("id").primaryKey().$default(uuid_1.v4),
8
+ createdAt: (0, sqlite_core_1.integer)("created_at", { mode: "timestamp" })
9
+ .notNull()
10
+ .$default(() => new Date()),
11
+ updatedAt: (0, sqlite_core_1.integer)("updated_at", { mode: "timestamp" }),
12
+ plan: (0, sqlite_core_1.text)("plan").notNull(),
13
+ referenceId: (0, sqlite_core_1.text)("reference_id").notNull(),
14
+ stripeCustomerId: (0, sqlite_core_1.text)("stripe_customer_id"),
15
+ stripeSubscriptionId: (0, sqlite_core_1.text)("stripe_subscription_id"),
16
+ status: (0, sqlite_core_1.text)("status").notNull(),
17
+ periodStart: (0, sqlite_core_1.integer)("period_start", { mode: "timestamp" }),
18
+ periodEnd: (0, sqlite_core_1.integer)("period_end", { mode: "timestamp" }),
19
+ priceId: (0, sqlite_core_1.text)("price_id"),
20
+ interval: (0, sqlite_core_1.text)("interval"),
21
+ unitAmount: (0, sqlite_core_1.integer)("unit_amount", { mode: "number" }),
22
+ discounts: (0, sqlite_core_1.text)("discounts", { mode: "json" }).$type(),
23
+ cancelAtPeriodEnd: (0, sqlite_core_1.integer)("cancel_at_period_end", { mode: "boolean" }),
24
+ cancelAt: (0, sqlite_core_1.integer)("cancel_at", { mode: "timestamp" }),
25
+ canceledAt: (0, sqlite_core_1.integer)("canceled_at", { mode: "timestamp" }),
26
+ seats: (0, sqlite_core_1.integer)("seats", { mode: "number" }),
27
+ trialStart: (0, sqlite_core_1.integer)("trial_start", { mode: "timestamp" }),
28
+ trialEnd: (0, sqlite_core_1.integer)("trial_end", { mode: "timestamp" }),
29
+ });
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BillingRepository = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const drizzle_orm_1 = require("drizzle-orm");
6
+ const neverthrow_1 = require("neverthrow");
7
+ const auth = tslib_1.__importStar(require("#modules/auth/auth.db"));
8
+ const base_repository_1 = require("#modules/base/base.repository");
9
+ const billing = tslib_1.__importStar(require("#modules/billing/billing.db"));
10
+ const posthog_1 = require("#utils/posthog");
11
+ const schema = { ...auth, ...billing };
12
+ class BillingRepository extends base_repository_1.BaseTableRepository {
13
+ stripe;
14
+ plans;
15
+ trial;
16
+ constructor(options) {
17
+ const { libs, config, ...rest } = options;
18
+ super(rest);
19
+ this.stripe = libs.stripe;
20
+ this.plans = config.plans;
21
+ this.trial = config.trial;
22
+ }
23
+ hasTrial() {
24
+ return !!this.trial;
25
+ }
26
+ getPlanByPriceId(priceId) {
27
+ return this.plans.find((plan) => plan.priceId === priceId || plan.annualDiscountPriceId === priceId);
28
+ }
29
+ getCustomerByEmail(email) {
30
+ return this.throwableAsync(async () => {
31
+ const customers = await this.stripe.customers.list({
32
+ email,
33
+ limit: 1,
34
+ });
35
+ return (0, neverthrow_1.ok)(customers.data[0] ?? null);
36
+ });
37
+ }
38
+ getUserByCustomerId(customerId) {
39
+ return this.throwableAsync(async () => {
40
+ const [user] = await this.orm
41
+ .select()
42
+ .from(this.schema.users)
43
+ .where((0, drizzle_orm_1.eq)(this.schema.users.stripeCustomerId, customerId))
44
+ .limit(1);
45
+ return (0, neverthrow_1.ok)(user ?? null);
46
+ });
47
+ }
48
+ createCustomer({ email, name, userId, }) {
49
+ return this.throwableAsync(async () => {
50
+ const customer = await this.stripe.customers.create({
51
+ email,
52
+ name,
53
+ metadata: {
54
+ userId,
55
+ },
56
+ });
57
+ return (0, neverthrow_1.ok)(customer);
58
+ });
59
+ }
60
+ async createTrialSubscription(customerId) {
61
+ if (!this.trial)
62
+ return this.error("NOT_FOUND", "Trial plan not found");
63
+ const stripeSubscription = await this.createSubscription({
64
+ customerId,
65
+ priceId: this.trial.priceId,
66
+ trialDays: this.trial.freeTrial?.days ?? 7,
67
+ });
68
+ if (stripeSubscription.isErr())
69
+ return (0, neverthrow_1.err)(stripeSubscription.error);
70
+ if (!stripeSubscription.value)
71
+ return this.error("INTERNAL_SERVER_ERROR", "Failed to create trial subscription");
72
+ return (0, neverthrow_1.ok)(stripeSubscription.value);
73
+ }
74
+ createSubscription({ customerId, priceId, quantity = 1, trialDays, }) {
75
+ return this.throwableAsync(async () => {
76
+ const stripeSubscription = await this.stripe.subscriptions.create({
77
+ customer: customerId,
78
+ items: [{ price: priceId, quantity }], // quantity = seats if you want
79
+ ...(trialDays
80
+ ? {
81
+ trial_period_days: trialDays,
82
+ trial_settings: {
83
+ end_behavior: {
84
+ missing_payment_method: "cancel",
85
+ },
86
+ },
87
+ }
88
+ : {}),
89
+ });
90
+ return (0, neverthrow_1.ok)(stripeSubscription);
91
+ });
92
+ }
93
+ updateUserCustomerId({ userId, customerId, }) {
94
+ return this.throwableAsync(async () => {
95
+ const [user] = await this.orm
96
+ .update(this.schema.users)
97
+ .set({ stripeCustomerId: customerId })
98
+ .where((0, drizzle_orm_1.eq)(this.schema.users.id, userId))
99
+ .returning();
100
+ if (!user)
101
+ return this.error("NOT_FOUND", "User not found");
102
+ return (0, neverthrow_1.ok)(user);
103
+ });
104
+ }
105
+ getLatestSubscription(referenceId) {
106
+ return this.throwableAsync(async () => {
107
+ const subscriptions = await this.orm
108
+ .select()
109
+ .from(this.schema.subscriptions)
110
+ .where((0, drizzle_orm_1.eq)(this.schema.subscriptions.referenceId, referenceId))
111
+ .orderBy((0, drizzle_orm_1.desc)(this.schema.subscriptions.createdAt))
112
+ .limit(1);
113
+ return (0, neverthrow_1.ok)(subscriptions[0] ?? null);
114
+ });
115
+ }
116
+ getActiveSubscription(referenceId) {
117
+ return this.throwableAsync(async () => {
118
+ const [subscription] = await this.orm
119
+ .select()
120
+ .from(this.schema.subscriptions)
121
+ .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.schema.subscriptions.referenceId, referenceId), (0, drizzle_orm_1.inArray)(this.schema.subscriptions.status, ["active", "trialing"])))
122
+ .orderBy((0, drizzle_orm_1.desc)(this.schema.subscriptions.createdAt))
123
+ .limit(1);
124
+ return (0, neverthrow_1.ok)(subscription ?? null);
125
+ });
126
+ }
127
+ listInvoices(customerId) {
128
+ return this.throwableAsync(async () => {
129
+ const invoices = await this.stripe.invoices.list({
130
+ customer: customerId,
131
+ });
132
+ return (0, neverthrow_1.ok)(invoices.data);
133
+ });
134
+ }
135
+ createCheckoutSession({ customerId, priceId, userId, }) {
136
+ return this.throwableAsync(async () => {
137
+ const session = await this.stripe.checkout.sessions.create({
138
+ client_reference_id: userId,
139
+ customer: customerId,
140
+ success_url: `${process.env.VITE_SERVER_URL}/stripe/success`,
141
+ cancel_url: `${process.env.VITE_APP_URL}/billing`,
142
+ mode: "subscription",
143
+ line_items: [
144
+ {
145
+ price: priceId,
146
+ quantity: 1,
147
+ },
148
+ ],
149
+ });
150
+ return (0, neverthrow_1.ok)(session);
151
+ });
152
+ }
153
+ createBillingPortalSession(customerId) {
154
+ return this.throwableAsync(async () => {
155
+ const session = await this.stripe.billingPortal.sessions.create({
156
+ customer: customerId,
157
+ return_url: `${process.env.VITE_SERVER_URL}/stripe/success`,
158
+ });
159
+ return (0, neverthrow_1.ok)(session);
160
+ });
161
+ }
162
+ async syncStripeData({ customerId, userId, }) {
163
+ return this.throwableAsync(async () => {
164
+ // Fetch latest subscription data from Stripe
165
+ const stripeSubscriptions = await this.stripe.subscriptions.list({
166
+ customer: customerId,
167
+ limit: 1,
168
+ status: "all",
169
+ expand: ["data.default_payment_method"],
170
+ });
171
+ const [stripeSubscription] = stripeSubscriptions.data;
172
+ if (!stripeSubscription)
173
+ return this.error("NOT_FOUND", "Subscription not found");
174
+ const plan = this.getPlanByPriceId(stripeSubscription.items.data[0]?.price.id);
175
+ if (!plan)
176
+ return this.error("NOT_FOUND", `Plan not found for price ID: ${stripeSubscription.items.data[0]?.price.id}`);
177
+ const values = {
178
+ stripeCustomerId: customerId,
179
+ referenceId: userId,
180
+ plan: plan.name,
181
+ status: stripeSubscription.status,
182
+ seats: stripeSubscription.items.data[0]?.quantity || 1,
183
+ periodEnd: new Date(stripeSubscription.items.data[0]?.current_period_end * 1000),
184
+ periodStart: new Date(stripeSubscription.items.data[0]?.current_period_start * 1000),
185
+ priceId: stripeSubscription.items.data[0]?.price.id,
186
+ interval: stripeSubscription.items.data[0]?.price.recurring?.interval,
187
+ unitAmount: stripeSubscription.items.data[0]?.price.unit_amount,
188
+ discounts: stripeSubscription.discounts.map((discount) => typeof discount === "string" ? discount : discount.id),
189
+ stripeSubscriptionId: stripeSubscription.id,
190
+ cancelAtPeriodEnd: stripeSubscription.cancel_at_period_end,
191
+ cancelAt: stripeSubscription.cancel_at
192
+ ? new Date(stripeSubscription.cancel_at * 1000)
193
+ : null,
194
+ canceledAt: stripeSubscription.canceled_at
195
+ ? new Date(stripeSubscription.canceled_at * 1000)
196
+ : null,
197
+ ...(stripeSubscription.trial_start && stripeSubscription.trial_end
198
+ ? {
199
+ trialStart: new Date(stripeSubscription.trial_start * 1000),
200
+ trialEnd: new Date(stripeSubscription.trial_end * 1000),
201
+ }
202
+ : {}),
203
+ };
204
+ const existingSubscription = await this.getActiveSubscription(userId);
205
+ if (existingSubscription.isErr())
206
+ return (0, neverthrow_1.err)(existingSubscription.error);
207
+ if (!existingSubscription.value) {
208
+ await this.orm.insert(this.schema.subscriptions).values(values);
209
+ (0, posthog_1.posthogCapture)({
210
+ distinctId: userId,
211
+ event: "stripe.subscription_created",
212
+ properties: values,
213
+ });
214
+ return (0, neverthrow_1.ok)(true);
215
+ }
216
+ await this.orm
217
+ .update(this.schema.subscriptions)
218
+ .set({ ...values, updatedAt: new Date() })
219
+ .where((0, drizzle_orm_1.eq)(this.schema.subscriptions.id, existingSubscription.value.id));
220
+ (0, posthog_1.posthogCapture)({
221
+ distinctId: userId,
222
+ event: "stripe.subscription_updated",
223
+ properties: values,
224
+ });
225
+ return (0, neverthrow_1.ok)(false);
226
+ });
227
+ }
228
+ constructEvent(body, signature, secret) {
229
+ return this.throwable(() => {
230
+ const event = this.stripe.webhooks.constructEvent(body, signature, secret);
231
+ return (0, neverthrow_1.ok)(event);
232
+ });
233
+ }
234
+ }
235
+ exports.BillingRepository = BillingRepository;