@primocaredentgroup/convex-campaigns-component 0.3.5 → 0.3.9

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.
@@ -34,8 +34,11 @@ function normalizeScopeClinicIds(
34
34
  return [...new Set(scopeClinicIds.map((id) => String(id).trim()).filter(Boolean))];
35
35
  }
36
36
 
37
+ const trustedUserIdArg = { _trustedUserId: v.optional(v.string()) };
38
+
37
39
  export const createCampaign = mutation({
38
40
  args: {
41
+ ...trustedUserIdArg,
39
42
  input: v.object({
40
43
  name: v.string(),
41
44
  description: v.optional(v.string()),
@@ -47,7 +50,7 @@ export const createCampaign = mutation({
47
50
  }),
48
51
  },
49
52
  handler: async (ctx, args) => {
50
- await assertAuthorized(ctx, ["Admin", "Marketing"]);
53
+ await assertAuthorized(ctx, ["Admin", "Marketing"], { trustedUserId: args._trustedUserId });
51
54
  const now = Date.now();
52
55
  return ctx.db.insert("campaigns", {
53
56
  ...args.input,
@@ -62,6 +65,7 @@ export const createCampaign = mutation({
62
65
 
63
66
  export const updateCampaign = mutation({
64
67
  args: {
68
+ ...trustedUserIdArg,
65
69
  campaignId: v.id("campaigns"),
66
70
  patch: v.object({
67
71
  name: v.optional(v.string()),
@@ -74,7 +78,7 @@ export const updateCampaign = mutation({
74
78
  }),
75
79
  },
76
80
  handler: async (ctx, args) => {
77
- await assertAuthorized(ctx, ["Admin", "Marketing"]);
81
+ await assertAuthorized(ctx, ["Admin", "Marketing"], { trustedUserId: args._trustedUserId });
78
82
  const campaign = await ctx.db.get(args.campaignId);
79
83
  if (!campaign) throw new Error("Campaign not found");
80
84
  await ctx.db.patch(args.campaignId, {
@@ -93,11 +97,12 @@ export const updateCampaign = mutation({
93
97
 
94
98
  export const setCampaignStatus = mutation({
95
99
  args: {
100
+ ...trustedUserIdArg,
96
101
  campaignId: v.id("campaigns"),
97
102
  status: campaignStatusValidator,
98
103
  },
99
104
  handler: async (ctx, args) => {
100
- await assertAuthorized(ctx, ["Admin", "Marketing"]);
105
+ await assertAuthorized(ctx, ["Admin", "Marketing"], { trustedUserId: args._trustedUserId });
101
106
  await ctx.db.patch(args.campaignId, { status: args.status, updatedAt: Date.now() });
102
107
  await ctx.db.insert("event_log", {
103
108
  campaignId: args.campaignId,
@@ -111,6 +116,7 @@ export const setCampaignStatus = mutation({
111
116
 
112
117
  export const addOrUpdateSteps = mutation({
113
118
  args: {
119
+ ...trustedUserIdArg,
114
120
  campaignId: v.id("campaigns"),
115
121
  steps: v.array(
116
122
  v.object({
@@ -123,7 +129,7 @@ export const addOrUpdateSteps = mutation({
123
129
  ),
124
130
  },
125
131
  handler: async (ctx, args) => {
126
- await assertAuthorized(ctx, ["Admin", "Marketing"]);
132
+ await assertAuthorized(ctx, ["Admin", "Marketing"], { trustedUserId: args._trustedUserId });
127
133
  const now = Date.now();
128
134
  const createdOrUpdated: string[] = [];
129
135
  for (const step of args.steps) {
@@ -155,12 +161,13 @@ export const addOrUpdateSteps = mutation({
155
161
 
156
162
  export const previewAudience = mutation({
157
163
  args: {
164
+ ...trustedUserIdArg,
158
165
  campaignId: v.id("campaigns"),
159
166
  segmentationRules: segmentationRulesValidator,
160
167
  sampleSize: v.optional(v.number()),
161
168
  },
162
169
  handler: async (ctx, args) => {
163
- await assertAuthorized(ctx, ["Admin", "Marketing"]);
170
+ await assertAuthorized(ctx, ["Admin", "Marketing"], { trustedUserId: args._trustedUserId });
164
171
  const campaign = await ctx.db.get(args.campaignId);
165
172
  if (!campaign) throw new Error("Campaign not found");
166
173
 
@@ -229,12 +236,13 @@ export const previewAudience = mutation({
229
236
 
230
237
  export const buildAudienceSnapshot = mutation({
231
238
  args: {
239
+ ...trustedUserIdArg,
232
240
  campaignId: v.id("campaigns"),
233
241
  segmentationRules: segmentationRulesValidator,
234
242
  runConfig: v.optional(runConfigValidator),
235
243
  },
236
244
  handler: async (ctx, args) => {
237
- await assertAuthorized(ctx, ["Admin", "Marketing"]);
245
+ await assertAuthorized(ctx, ["Admin", "Marketing"], { trustedUserId: args._trustedUserId });
238
246
  const campaign = await ctx.db.get(args.campaignId);
239
247
  if (!campaign) throw new Error("Campaign not found");
240
248
 
@@ -11,6 +11,7 @@ import type { RuleGroup } from "../v2/types";
11
11
  import { DEFAULT_CALL_POLICY } from "../domain/types";
12
12
 
13
13
  const MAX_PREVIEW = 5000;
14
+ const trustedUserIdArg = { _trustedUserId: v.optional(v.string()) };
14
15
 
15
16
  function hashRules(rules: unknown): string {
16
17
  const text = JSON.stringify(rules);
@@ -81,12 +82,13 @@ export const getFieldRegistry = query({
81
82
 
82
83
  export const seedDemoData = mutation({
83
84
  args: {
85
+ ...trustedUserIdArg,
84
86
  orgId: v.string(),
85
87
  clinicIds: v.array(v.string()),
86
88
  nPatients: v.number(),
87
89
  },
88
90
  handler: async (ctx, args) => {
89
- await assertCanManageCampaigns(ctx);
91
+ await assertCanManageCampaigns(ctx, { trustedUserId: args._trustedUserId });
90
92
  const clinicIds = normalizeClinicIds(args.clinicIds);
91
93
  if (clinicIds.length === 0) throw new Error("Almeno una clinicId è obbligatoria.");
92
94
 
@@ -185,11 +187,12 @@ export const seedDemoData = mutation({
185
187
 
186
188
  export const updateCampaignCallPolicy = mutation({
187
189
  args: {
190
+ ...trustedUserIdArg,
188
191
  campaignId: v.id("campaigns"),
189
192
  callPolicy: v.any(),
190
193
  },
191
194
  handler: async (ctx, args) => {
192
- await assertCanManageCampaigns(ctx);
195
+ await assertCanManageCampaigns(ctx, { trustedUserId: args._trustedUserId });
193
196
  const validation = validateCallPolicyInput(args.callPolicy);
194
197
  if (!validation.ok) {
195
198
  throw new Error(`CALL_POLICY_VALIDATION_ERROR: ${validation.errors.join(" | ")}`);
@@ -215,10 +218,10 @@ export const updateCampaignCallPolicy = mutation({
215
218
  });
216
219
 
217
220
  export const getCampaignCallPolicy = query({
218
- args: { campaignId: v.id("campaigns") },
221
+ args: { ...trustedUserIdArg, campaignId: v.id("campaigns") },
219
222
  handler: async (ctx, args) => {
220
223
  try {
221
- await assertCanManageCampaigns(ctx);
224
+ await assertCanManageCampaigns(ctx, { trustedUserId: args._trustedUserId });
222
225
  const campaign = await ctx.db.get(args.campaignId);
223
226
  if (!campaign) return null;
224
227
  const policy = campaign.callPolicy ?? getDefaultCallPolicy();
@@ -232,6 +235,7 @@ export const getCampaignCallPolicy = query({
232
235
 
233
236
  export const upsertCampaign = mutation({
234
237
  args: {
238
+ ...trustedUserIdArg,
235
239
  id: v.optional(v.id("campaigns")),
236
240
  orgId: v.string(),
237
241
  name: v.string(),
@@ -243,7 +247,7 @@ export const upsertCampaign = mutation({
243
247
  frequencyCapDays: v.optional(v.number()),
244
248
  },
245
249
  handler: async (ctx, args) => {
246
- await assertCanManageCampaigns(ctx);
250
+ await assertCanManageCampaigns(ctx, { trustedUserId: args._trustedUserId });
247
251
 
248
252
  const rulesValidation = validateRuleDsl(args.rules);
249
253
  if (!rulesValidation.ok) {
@@ -297,11 +301,12 @@ export const upsertCampaign = mutation({
297
301
 
298
302
  export const setCampaignLifecycleStatus = mutation({
299
303
  args: {
304
+ ...trustedUserIdArg,
300
305
  campaignId: v.id("campaigns"),
301
306
  status: v.union(v.literal("draft"), v.literal("ready"), v.literal("archived")),
302
307
  },
303
308
  handler: async (ctx, args) => {
304
- await assertCanManageCampaigns(ctx);
309
+ await assertCanManageCampaigns(ctx, { trustedUserId: args._trustedUserId });
305
310
  const campaign = await ctx.db.get(args.campaignId);
306
311
  if (!campaign) throw new Error("Campaign non trovata.");
307
312
  const statusToV2: Record<string, "draft" | "ready" | "archived"> = {
@@ -333,12 +338,13 @@ export const setCampaignLifecycleStatus = mutation({
333
338
 
334
339
  export const listCampaignsV2 = query({
335
340
  args: {
341
+ ...trustedUserIdArg,
336
342
  orgId: v.string(),
337
343
  status: v.optional(v.union(v.literal("draft"), v.literal("ready"), v.literal("archived"))),
338
344
  },
339
345
  handler: async (ctx, args) => {
340
346
  try {
341
- await assertCanManageCampaigns(ctx);
347
+ await assertCanManageCampaigns(ctx, { trustedUserId: args._trustedUserId });
342
348
  const rows = args.status
343
349
  ? await safeCampaignsByOrgStatus(ctx, args.orgId, args.status)
344
350
  : await safeCampaignsByOrg(ctx, args.orgId);
@@ -351,10 +357,10 @@ export const listCampaignsV2 = query({
351
357
  });
352
358
 
353
359
  export const getCampaignV2 = query({
354
- args: { id: v.id("campaigns") },
360
+ args: { ...trustedUserIdArg, id: v.id("campaigns") },
355
361
  handler: async (ctx, args) => {
356
362
  try {
357
- await assertCanManageCampaigns(ctx);
363
+ await assertCanManageCampaigns(ctx, { trustedUserId: args._trustedUserId });
358
364
  return await ctx.db.get(args.id);
359
365
  } catch (error) {
360
366
  console.error("[getCampaignV2]", error);
@@ -364,10 +370,10 @@ export const getCampaignV2 = query({
364
370
  });
365
371
 
366
372
  export const listCampaignVersions = query({
367
- args: { campaignId: v.id("campaigns") },
373
+ args: { ...trustedUserIdArg, campaignId: v.id("campaigns") },
368
374
  handler: async (ctx, args) => {
369
375
  try {
370
- await assertCanManageCampaigns(ctx);
376
+ await assertCanManageCampaigns(ctx, { trustedUserId: args._trustedUserId });
371
377
  const versions = await ctx.db
372
378
  .query("campaignVersions")
373
379
  .withIndex("by_campaign", (q: any) => q.eq("campaignId", args.campaignId))
@@ -382,6 +388,7 @@ export const listCampaignVersions = query({
382
388
 
383
389
  export const previewCampaignAudience = query({
384
390
  args: {
391
+ ...trustedUserIdArg,
385
392
  campaignId: v.optional(v.id("campaigns")),
386
393
  orgId: v.optional(v.string()),
387
394
  clinicIds: v.optional(v.array(v.string())),
@@ -389,7 +396,7 @@ export const previewCampaignAudience = query({
389
396
  frequencyCapDays: v.optional(v.number()),
390
397
  },
391
398
  handler: async (ctx, args) => {
392
- await assertCanManageCampaigns(ctx);
399
+ await assertCanManageCampaigns(ctx, { trustedUserId: args._trustedUserId });
393
400
 
394
401
  let orgId = args.orgId ?? "";
395
402
  let clinicIds = normalizeClinicIds(args.clinicIds);
@@ -470,11 +477,12 @@ export const previewCampaignAudience = query({
470
477
 
471
478
  export const publishCampaignVersion = mutation({
472
479
  args: {
480
+ ...trustedUserIdArg,
473
481
  campaignId: v.id("campaigns"),
474
482
  label: v.optional(v.string()),
475
483
  },
476
484
  handler: async (ctx, args) => {
477
- await assertCanManageCampaigns(ctx);
485
+ await assertCanManageCampaigns(ctx, { trustedUserId: args._trustedUserId });
478
486
  const campaign = await ctx.db.get(args.campaignId);
479
487
  if (!campaign) throw new Error("Campaign non trovata.");
480
488
  if (campaign.status !== "ready") {
@@ -588,12 +596,13 @@ async function safeAudienceMembersByOrgPatient(ctx: any, orgId: string, patientI
588
596
 
589
597
  export const getPatientCampaignMemberships = query({
590
598
  args: {
599
+ ...trustedUserIdArg,
591
600
  orgId: v.string(),
592
601
  patientId: v.string(),
593
602
  },
594
603
  handler: async (ctx, args) => {
595
604
  try {
596
- await assertCanManageCampaigns(ctx);
605
+ await assertCanManageCampaigns(ctx, { trustedUserId: args._trustedUserId });
597
606
  const memberships = await safeAudienceMembersByOrgPatient(
598
607
  ctx,
599
608
  args.orgId,
@@ -665,13 +674,14 @@ export const getPatientCampaignMemberships = query({
665
674
 
666
675
  export const getAudiencePage = query({
667
676
  args: {
677
+ ...trustedUserIdArg,
668
678
  versionId: v.id("campaignVersions"),
669
679
  clinicId: v.optional(v.string()),
670
680
  cursor: v.optional(v.string()),
671
681
  limit: v.optional(v.number()),
672
682
  },
673
683
  handler: async (ctx, args) => {
674
- await assertCanManageCampaigns(ctx);
684
+ await assertCanManageCampaigns(ctx, { trustedUserId: args._trustedUserId });
675
685
  const limit = Math.min(200, Math.max(1, args.limit ?? 50));
676
686
  if (args.clinicId) {
677
687
  const page = await ctx.db
@@ -701,6 +711,7 @@ export const getAudiencePage = query({
701
711
 
702
712
  export const recordContactAttempt = mutation({
703
713
  args: {
714
+ ...trustedUserIdArg,
704
715
  orgId: v.string(),
705
716
  clinicId: v.string(),
706
717
  patientId: v.string(),
@@ -718,7 +729,7 @@ export const recordContactAttempt = mutation({
718
729
  ),
719
730
  },
720
731
  handler: async (ctx, args) => {
721
- await assertCanManageCampaigns(ctx);
732
+ await assertCanManageCampaigns(ctx, { trustedUserId: args._trustedUserId });
722
733
  const id = await ctx.db.insert("contactLog", {
723
734
  ...args,
724
735
  createdAt: Date.now(),
@@ -10,6 +10,8 @@ import { getDefaultCallPolicy } from "../v2/callPolicy";
10
10
  import { normalizeClinicId, normalizeClinicIds } from "../lib/helpers";
11
11
  import type { Id } from "../../../_generated/dataModel";
12
12
 
13
+ const trustedUserIdArg = { _trustedUserId: v.optional(v.string()) };
14
+
13
15
  async function safeCampaignsByOrgStatus(ctx: any, orgId: string, status: string) {
14
16
  try {
15
17
  return await ctx.db
@@ -56,11 +58,12 @@ async function safeAudienceMembersByOrgPatient(ctx: any, orgId: string, patientI
56
58
 
57
59
  export const listActiveCampaignsForOrg = query({
58
60
  args: {
61
+ ...trustedUserIdArg,
59
62
  orgId: v.string(),
60
63
  clinicId: v.optional(v.string()),
61
64
  },
62
65
  handler: async (ctx, args) => {
63
- await assertCanManageCampaigns(ctx);
66
+ await assertCanManageCampaigns(ctx, { trustedUserId: args._trustedUserId });
64
67
  const campaigns = await safeCampaignsByOrgStatus(ctx, args.orgId, "ready");
65
68
  const clinicIdNorm = args.clinicId ? normalizeClinicId(args.clinicId) : null;
66
69
 
@@ -107,9 +110,9 @@ export const listActiveCampaignsForOrg = query({
107
110
  });
108
111
 
109
112
  export const getLatestPublishedVersion = query({
110
- args: { campaignId: v.id("campaigns") },
113
+ args: { ...trustedUserIdArg, campaignId: v.id("campaigns") },
111
114
  handler: async (ctx, args) => {
112
- await assertCanManageCampaigns(ctx);
115
+ await assertCanManageCampaigns(ctx, { trustedUserId: args._trustedUserId });
113
116
  const versions = await ctx.db
114
117
  .query("campaignVersions")
115
118
  .withIndex("by_campaign", (q: any) => q.eq("campaignId", args.campaignId))
@@ -128,9 +131,9 @@ export const getLatestPublishedVersion = query({
128
131
  });
129
132
 
130
133
  export const getPatientCampaignMemberships = query({
131
- args: { orgId: v.string(), patientId: v.string() },
134
+ args: { ...trustedUserIdArg, orgId: v.string(), patientId: v.string() },
132
135
  handler: async (ctx, args) => {
133
- await assertCanManageCampaigns(ctx);
136
+ await assertCanManageCampaigns(ctx, { trustedUserId: args._trustedUserId });
134
137
  const memberships = await safeAudienceMembersByOrgPatient(
135
138
  ctx,
136
139
  args.orgId,
@@ -184,13 +187,14 @@ export const getPatientCampaignMemberships = query({
184
187
 
185
188
  export const getAudiencePage = query({
186
189
  args: {
190
+ ...trustedUserIdArg,
187
191
  versionId: v.id("campaignVersions"),
188
192
  clinicId: v.optional(v.string()),
189
193
  cursor: v.optional(v.string()),
190
194
  limit: v.optional(v.number()),
191
195
  },
192
196
  handler: async (ctx, args) => {
193
- await assertCanManageCampaigns(ctx);
197
+ await assertCanManageCampaigns(ctx, { trustedUserId: args._trustedUserId });
194
198
  const limit = Math.min(200, Math.max(1, args.limit ?? 50));
195
199
  const clinicIdNorm = args.clinicId ? normalizeClinicId(args.clinicId) : undefined;
196
200
 
@@ -214,6 +218,7 @@ export const getAudiencePage = query({
214
218
 
215
219
  export const recordContactAttempt = mutation({
216
220
  args: {
221
+ ...trustedUserIdArg,
217
222
  orgId: v.string(),
218
223
  clinicId: v.string(),
219
224
  patientId: v.string(),
@@ -231,7 +236,7 @@ export const recordContactAttempt = mutation({
231
236
  ),
232
237
  },
233
238
  handler: async (ctx, args) => {
234
- await assertCanManageCampaigns(ctx);
239
+ await assertCanManageCampaigns(ctx, { trustedUserId: args._trustedUserId });
235
240
  const clinicIdNorm = normalizeClinicId(args.clinicId);
236
241
  const id = await ctx.db.insert("contactLog", {
237
242
  ...args,
@@ -4,6 +4,7 @@ import { assertAuthorized } from "../permissions";
4
4
  import { campaignStatusValidator, scopeTypeValidator } from "../domain/types";
5
5
 
6
6
  const clinicIdFilterValidator = v.union(v.id("clinics"), v.string());
7
+ const trustedUserIdArg = { _trustedUserId: v.optional(v.string()) };
7
8
 
8
9
  function isRecoverableQueryError(error: unknown): boolean {
9
10
  const message = error instanceof Error ? error.message : String(error);
@@ -72,6 +73,7 @@ async function safeCollectMembersBySnapshot(ctx: any, snapshotId: string) {
72
73
 
73
74
  export const listCampaigns = query({
74
75
  args: {
76
+ ...trustedUserIdArg,
75
77
  filters: v.optional(
76
78
  v.object({
77
79
  status: v.optional(campaignStatusValidator),
@@ -82,7 +84,7 @@ export const listCampaigns = query({
82
84
  },
83
85
  handler: async (ctx, args) => {
84
86
  try {
85
- await assertAuthorized(ctx, ["Admin", "Marketing"]);
87
+ await assertAuthorized(ctx, ["Admin", "Marketing"], { trustedUserId: args._trustedUserId });
86
88
  let campaigns = args.filters?.status
87
89
  ? await safeCollectCampaignsByStatus(ctx, args.filters.status)
88
90
  : await ctx.db.query("campaigns").collect();
@@ -105,10 +107,10 @@ export const listCampaigns = query({
105
107
  });
106
108
 
107
109
  export const getCampaign = query({
108
- args: { campaignId: v.id("campaigns") },
110
+ args: { ...trustedUserIdArg, campaignId: v.id("campaigns") },
109
111
  handler: async (ctx, args) => {
110
112
  try {
111
- await assertAuthorized(ctx, ["Admin", "Marketing"]);
113
+ await assertAuthorized(ctx, ["Admin", "Marketing"], { trustedUserId: args._trustedUserId });
112
114
  const campaign = await ctx.db.get(args.campaignId);
113
115
  if (!campaign) return null;
114
116
 
@@ -129,10 +131,10 @@ export const getCampaign = query({
129
131
  });
130
132
 
131
133
  export const getSnapshot = query({
132
- args: { snapshotId: v.id("audience_snapshots") },
134
+ args: { ...trustedUserIdArg, snapshotId: v.id("audience_snapshots") },
133
135
  handler: async (ctx, args) => {
134
136
  try {
135
- await assertAuthorized(ctx, ["Admin", "Marketing"]);
137
+ await assertAuthorized(ctx, ["Admin", "Marketing"], { trustedUserId: args._trustedUserId });
136
138
  const snapshot = await ctx.db.get(args.snapshotId);
137
139
  if (!snapshot) return null;
138
140
 
@@ -152,12 +154,13 @@ export const getSnapshot = query({
152
154
 
153
155
  export const getSnapshotMemberSample = query({
154
156
  args: {
157
+ ...trustedUserIdArg,
155
158
  snapshotId: v.id("audience_snapshots"),
156
159
  limit: v.optional(v.number()),
157
160
  },
158
161
  handler: async (ctx, args) => {
159
162
  try {
160
- await assertAuthorized(ctx, ["Admin", "Marketing"]);
163
+ await assertAuthorized(ctx, ["Admin", "Marketing"], { trustedUserId: args._trustedUserId });
161
164
  const limit = Math.min(Math.max(1, args.limit ?? 10), 100);
162
165
 
163
166
  let members: any[];
@@ -220,6 +223,7 @@ export const getSnapshotMemberSample = query({
220
223
 
221
224
  export const getCampaignReport = query({
222
225
  args: {
226
+ ...trustedUserIdArg,
223
227
  campaignId: v.id("campaigns"),
224
228
  range: v.object({
225
229
  from: v.number(),
@@ -245,7 +249,7 @@ export const getCampaignReport = query({
245
249
  };
246
250
 
247
251
  try {
248
- await assertAuthorized(ctx, ["Admin", "Marketing"]);
252
+ await assertAuthorized(ctx, ["Admin", "Marketing"], { trustedUserId: args._trustedUserId });
249
253
  const snapshots = await safeCollectSnapshotsByCampaign(ctx, args.campaignId);
250
254
 
251
255
  const inRange = snapshots.filter(
@@ -115,14 +115,25 @@ async function getUserRoleCodes(ctx: AnyCtx, userId: Id<"users">): Promise<Set<s
115
115
  return roles;
116
116
  }
117
117
 
118
+ type AssertAuthOptions = {
119
+ trustedUserId?: string;
120
+ };
121
+
118
122
  /**
119
123
  * MVP authorization guard.
120
- * - If auth identity is available: enforces required roles.
121
- * - If auth identity is missing: currently allows access to avoid blocking service calls
122
- * while host identity wiring is being finalized.
123
- * TODO: tighten unauthenticated fallback once host auth is fully configured.
124
+ * - Se _trustedUserId è fornito dall'host: bypass completo (l'host ha già validato l'utente).
125
+ * - Se auth identity è disponibile: enforce dei ruoli richiesti.
126
+ * - Se auth identity manca: fallback per servizi in fase di wiring.
124
127
  */
125
- export async function assertAuthorized(ctx: AnyCtx, requiredRoles: Role[]): Promise<void> {
128
+ export async function assertAuthorized(
129
+ ctx: AnyCtx,
130
+ requiredRoles: Role[],
131
+ options?: AssertAuthOptions,
132
+ ): Promise<void> {
133
+ if (options?.trustedUserId) {
134
+ return;
135
+ }
136
+
126
137
  const identity = await ctx.auth.getUserIdentity();
127
138
  if (!identity) {
128
139
  if (requiredRoles.includes("System")) {
@@ -180,11 +191,15 @@ export async function assertAuthorized(ctx: AnyCtx, requiredRoles: Role[]): Prom
180
191
  }
181
192
  }
182
193
 
183
- export async function assertCanManageCampaigns(ctx: AnyCtx): Promise<void> {
194
+ export async function assertCanManageCampaigns(
195
+ ctx: AnyCtx,
196
+ options?: AssertAuthOptions,
197
+ ): Promise<void> {
198
+ if (options?.trustedUserId) {
199
+ return;
200
+ }
184
201
  const identity = await ctx.auth.getUserIdentity();
185
202
  if (!identity) {
186
203
  throw authError("CAMPAIGNS_UNAUTHORIZED", "Missing identity in request context.");
187
204
  }
188
- // Placeholder policy: any authenticated user can manage campaigns.
189
- // TODO: replace with PrimoCore role checks (admin/marketing).
190
205
  }
package/convex.config.ts CHANGED
@@ -1,13 +1,5 @@
1
- import { defineApp, defineComponent } from "convex/server";
1
+ import { defineComponent } from "convex/server";
2
2
 
3
3
  const campaignsComponent = defineComponent("campaigns");
4
4
 
5
- // App per npx convex dev dalla root del package (sviluppo standalone).
6
- // L'host che installa da npm usa: import campaignsComponent from "package/convex.config"
7
- // e fa app.use(campaignsComponent) - quindi serve il componente.
8
- const app = defineApp();
9
- app.use(campaignsComponent, { name: "campaigns" });
10
-
11
- export default app;
12
- // Per host npm: import { campaignsComponent } from "package/convex.config"
13
- export { campaignsComponent };
5
+ export default campaignsComponent;
package/package.json CHANGED
@@ -1,10 +1,14 @@
1
1
  {
2
2
  "name": "@primocaredentgroup/convex-campaigns-component",
3
- "version": "0.3.5",
3
+ "version": "0.3.9",
4
4
  "description": "Convex Campaigns backend component for PrimoCore",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
7
7
  "main": "convex.config.ts",
8
+ "exports": {
9
+ "./convex.config.js": "./convex.config.ts",
10
+ "./convex.config": "./convex.config.ts"
11
+ },
8
12
  "files": [
9
13
  "convex.config.ts",
10
14
  "README.md",
@@ -16,7 +20,7 @@
16
20
  "prepack": "npm run sync:from-repo",
17
21
  "test": "tsx --test tests/**/*.test.ts",
18
22
  "smoke": "node scripts/smoke-test.mjs",
19
- "version:patch": "npm version patch --no-git-tag-version",
23
+ "version:patch": "node scripts/bump-version.mjs",
20
24
  "publish:component": "npm run version:patch && npm publish"
21
25
  },
22
26
  "peerDependencies": {