Skip to content

Commit 67d97e7

Browse files
committed
Consolidate accidentally doubled organizations tables
1 parent 0fddce4 commit 67d97e7

7 files changed

Lines changed: 83 additions & 448 deletions

File tree

apps/dashboard/convex/_generated/ai/ai-files.state.json

Lines changed: 0 additions & 6 deletions
This file was deleted.

apps/dashboard/convex/_generated/ai/guidelines.md

Lines changed: 0 additions & 365 deletions
This file was deleted.

apps/dashboard/convex/ingestion.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,12 @@ type ParsedEmail = {
7272

7373
type StoredParsedEmail = ParsedEmail & {
7474
listservId?: Id<"listservs">;
75-
organizationId?: Id<"organizations">;
75+
organizationId?: Id<"orgs">;
7676
};
7777

7878
type MatchableListserv = {
7979
_id: Id<"listservs">;
80-
organizationId?: Id<"organizations">;
80+
organizationId?: Id<"orgs">;
8181
listEmail: string;
8282
senderEmails: string[];
8383
};
@@ -374,7 +374,7 @@ export const storeParsedMessages = internalMutation({
374374
gmailMessageId: v.string(),
375375
threadId: v.optional(v.string()),
376376
listservId: v.optional(v.id("listservs")),
377-
organizationId: v.optional(v.id("organizations")),
377+
organizationId: v.optional(v.id("orgs")),
378378
sender: v.string(),
379379
senderEmail: v.string(),
380380
to: v.array(v.string()),

apps/dashboard/convex/parser.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ type ParsedItem = {
8383

8484
type SourceMessage = Doc<"listservMessages"> & {
8585
listserv?: Doc<"listservs"> | null;
86-
organization?: Doc<"organizations"> | null;
86+
organization?: Doc<"orgs"> | null;
8787
};
8888

8989
export const runParseNow = action({
@@ -200,10 +200,28 @@ export const publishEvent = mutation({
200200
args: { token: v.string(), eventId: v.id("events") },
201201
handler: async (ctx, args) => {
202202
requireAdminToken(args.token);
203-
await ctx.db.patch(args.eventId, {
204-
visibility: "published",
205-
updatedAt: Date.now(),
206-
});
203+
const now = Date.now();
204+
205+
await ctx.db.patch(args.eventId, { visibility: "published", updatedAt: now });
206+
207+
// Insert the eventOrgs join so the org name appears in the feed post header.
208+
// organizationId now points directly to `orgs`, so no cross-table sync needed.
209+
const event = await ctx.db.get(args.eventId);
210+
if (!event?.organizationId) return;
211+
212+
const existing = await ctx.db
213+
.query("eventOrgs")
214+
.withIndex("by_event", (q) => q.eq("eventId", args.eventId))
215+
.filter((q) => q.eq(q.field("orgId"), event.organizationId!))
216+
.unique();
217+
218+
if (existing === null) {
219+
await ctx.db.insert("eventOrgs", {
220+
eventId: args.eventId,
221+
orgId: event.organizationId,
222+
eventCreationTime: event._creationTime,
223+
});
224+
}
207225
},
208226
});
209227

@@ -596,7 +614,7 @@ function getGeminiConfig(): AIConfig | null {
596614
function buildParsePrompt(message: SourceMessage) {
597615
const input = {
598616
sourceOrganization: message.organization?.name,
599-
sourceOrganizationType: message.organization?.type,
617+
sourceOrganizationType: message.organization?.orgType,
600618
sourceEmail: message.senderEmail,
601619
sourceName: message.listserv?.name,
602620
subject: message.subject,

apps/dashboard/convex/schema.ts

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ export default defineSchema({
1717
isSeed: v.optional(v.boolean()),
1818
}).index("by_user", ["userId"]),
1919

20-
// Organizations (clubs) — first-class entity for follows + org page
20+
// Organizations (clubs) — canonical table for all org data, user-facing and
21+
// admin/listserv pipeline. Merged from the former `organizations` table.
2122
orgs: defineTable({
2223
slug: v.string(),
2324
name: v.string(),
@@ -28,8 +29,21 @@ export default defineSchema({
2829
websiteUrl: v.optional(v.string()),
2930
email: v.optional(v.string()),
3031
isVerified: v.boolean(),
31-
loopSummary: v.optional(v.string()), // populated by seed/AI branch later
32+
loopSummary: v.optional(v.string()),
3233
isSeed: v.optional(v.boolean()),
34+
// Fields from the former `organizations` admin table:
35+
orgType: v.optional(
36+
v.union(
37+
v.literal("club"),
38+
v.literal("department"),
39+
v.literal("official"),
40+
v.literal("publication"),
41+
v.literal("company"),
42+
v.literal("other"),
43+
),
44+
),
45+
orgStatus: v.optional(v.union(v.literal("active"), v.literal("hidden"))),
46+
updatedAt: v.optional(v.number()),
3347
})
3448
.index("by_slug", ["slug"])
3549
.index("by_seed", ["isSeed"])
@@ -91,27 +105,6 @@ export default defineSchema({
91105
.index("by_listserv", ["listserv"])
92106
.index("by_seed", ["isSeed"]),
93107

94-
organizations: defineTable({
95-
name: v.string(),
96-
slug: v.string(),
97-
type: v.union(
98-
v.literal("club"),
99-
v.literal("department"),
100-
v.literal("official"),
101-
v.literal("publication"),
102-
v.literal("company"),
103-
v.literal("other"),
104-
),
105-
description: v.optional(v.string()),
106-
website: v.optional(v.string()),
107-
tags: v.array(v.string()),
108-
status: v.union(v.literal("active"), v.literal("hidden")),
109-
createdAt: v.number(),
110-
updatedAt: v.number(),
111-
})
112-
.index("by_slug", ["slug"])
113-
.index("by_status", ["status"]),
114-
115108
listservCandidates: defineTable({
116109
email: v.string(),
117110
displayName: v.optional(v.string()),
@@ -140,7 +133,7 @@ export default defineSchema({
140133
displayName: v.optional(v.string()),
141134
listEmail: v.string(),
142135
senderEmails: v.array(v.string()),
143-
organizationId: v.optional(v.id("organizations")),
136+
organizationId: v.optional(v.id("orgs")),
144137
sourceType: v.optional(
145138
v.union(
146139
v.literal("lyris"),
@@ -208,7 +201,7 @@ export default defineSchema({
208201
gmailMessageId: v.string(),
209202
threadId: v.optional(v.string()),
210203
listservId: v.optional(v.id("listservs")),
211-
organizationId: v.optional(v.id("organizations")),
204+
organizationId: v.optional(v.id("orgs")),
212205
sender: v.string(),
213206
senderEmail: v.string(),
214207
to: v.array(v.string()),
@@ -341,12 +334,12 @@ export default defineSchema({
341334
adminNonce: v.optional(v.boolean()),
342335
}).index("by_state", ["state"]),
343336

344-
// One row per listserv item
337+
// One row per parsed listserv event
345338
events: defineTable({
346339
listservEmailId: v.optional(v.id("listservEmails")), // legacy link to the listserv email data
347340
sourceMessageId: v.optional(v.id("listservMessages")),
348341
listservId: v.optional(v.id("listservs")),
349-
organizationId: v.optional(v.id("organizations")),
342+
organizationId: v.optional(v.id("orgs")),
350343
listserv: v.string(),
351344
listservSection: v.string(),
352345

@@ -430,7 +423,6 @@ export default defineSchema({
430423
tags: v.array(v.string()),
431424
targetAudience: v.optional(
432425
v.union(
433-
// decide if we need all or define more
434426
v.literal("all"),
435427
v.literal("first_year"),
436428
v.literal("women_nonbinary"),

apps/dashboard/convex/sourceAdmin.ts

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ const ORG_TYPES = v.union(
1313
v.literal("other"),
1414
);
1515

16+
type OrgType = "club" | "department" | "official" | "publication" | "company" | "other";
17+
1618
export const overview = query({
1719
args: { token: v.string() },
1820
handler: async (ctx, args) => {
1921
requireAdminToken(args.token);
2022

2123
const [organizations, listservs, messages] = await Promise.all([
22-
ctx.db.query("organizations").order("asc").collect(),
24+
ctx.db.query("orgs").order("asc").collect(),
2325
ctx.db.query("listservs").order("asc").collect(),
2426
ctx.db
2527
.query("listservMessages")
@@ -91,7 +93,7 @@ export const createOrganization = mutation({
9193
},
9294
handler: async (ctx, args) => {
9395
requireAdminToken(args.token);
94-
return getOrCreateOrganization(ctx, {
96+
return getOrCreateOrg(ctx, {
9597
name: args.name,
9698
type: args.type,
9799
description: cleanOptional(args.description),
@@ -104,7 +106,7 @@ export const createOrganization = mutation({
104106
export const updateOrganization = mutation({
105107
args: {
106108
token: v.string(),
107-
organizationId: v.id("organizations"),
109+
organizationId: v.id("orgs"),
108110
name: v.string(),
109111
type: ORG_TYPES,
110112
description: v.optional(v.string()),
@@ -117,11 +119,11 @@ export const updateOrganization = mutation({
117119
await ctx.db.patch(args.organizationId, {
118120
name: args.name.trim(),
119121
slug: slugify(args.name),
120-
type: args.type,
121-
description: cleanOptional(args.description),
122-
website: cleanOptional(args.website),
122+
orgType: args.type,
123+
description: cleanOptional(args.description) ?? "",
124+
websiteUrl: cleanOptional(args.website),
123125
tags: args.tags ?? [],
124-
status: args.status,
126+
orgStatus: args.status,
125127
updatedAt: Date.now(),
126128
});
127129
},
@@ -131,7 +133,7 @@ export const assignSender = mutation({
131133
args: {
132134
token: v.string(),
133135
senderEmail: v.string(),
134-
organizationId: v.optional(v.id("organizations")),
136+
organizationId: v.optional(v.id("orgs")),
135137
organizationName: v.optional(v.string()),
136138
organizationType: v.optional(ORG_TYPES),
137139
sourceName: v.optional(v.string()),
@@ -152,7 +154,7 @@ export const assignSender = mutation({
152154
const suggestion = suggestSource(senderEmail);
153155
const organizationId =
154156
args.organizationId ??
155-
(await getOrCreateOrganization(ctx, {
157+
(await getOrCreateOrg(ctx, {
156158
name:
157159
cleanOptional(args.organizationName) ?? suggestion.organizationName,
158160
type: args.organizationType ?? suggestion.organizationType,
@@ -256,7 +258,7 @@ export const assignSourceOrganization = mutation({
256258
args: {
257259
token: v.string(),
258260
listservId: v.id("listservs"),
259-
organizationId: v.id("organizations"),
261+
organizationId: v.id("orgs"),
260262
},
261263
handler: async (ctx, args) => {
262264
requireAdminToken(args.token);
@@ -275,42 +277,36 @@ export const assignSourceOrganization = mutation({
275277
},
276278
});
277279

278-
async function getOrCreateOrganization(
280+
async function getOrCreateOrg(
279281
ctx: MutationCtx,
280282
params: {
281283
name: string;
282-
type:
283-
| "club"
284-
| "department"
285-
| "official"
286-
| "publication"
287-
| "company"
288-
| "other";
284+
type: OrgType;
289285
description?: string;
290286
website?: string;
291287
tags: string[];
292288
},
293-
): Promise<Id<"organizations">> {
289+
): Promise<Id<"orgs">> {
294290
const name = params.name.trim();
295291
if (!name) throw new Error("Organization name is required.");
296292

297293
const slug = slugify(name);
298294
const existing = await ctx.db
299-
.query("organizations")
295+
.query("orgs")
300296
.withIndex("by_slug", (q) => q.eq("slug", slug))
301297
.unique();
302298
if (existing) return existing._id;
303299

304300
const now = Date.now();
305-
return ctx.db.insert("organizations", {
301+
return ctx.db.insert("orgs", {
306302
name,
307303
slug,
308-
type: params.type,
309-
description: params.description,
310-
website: params.website,
304+
orgType: params.type,
305+
description: params.description ?? "",
306+
websiteUrl: params.website,
311307
tags: params.tags,
312-
status: "active",
313-
createdAt: now,
308+
isVerified: false,
309+
orgStatus: "active",
314310
updatedAt: now,
315311
});
316312
}

0 commit comments

Comments
 (0)