-
-
Notifications
You must be signed in to change notification settings - Fork 0
fix: invitations list limited to sender org = current org #418
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
05e90c1
5d73302
f394a63
28fb2e3
4580dd3
849f6a8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,50 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /* eslint-disable @typescript-eslint/no-explicit-any */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { sql } from "kysely"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { Kysely } from "kysely"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export async function up(db: Kysely<any>): Promise<void> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Add nullable column first | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await db.schema | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .alterTable("invitation") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .addColumn("senderOrganisationId", "uuid") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .execute(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Backfill: assign all existing invitations to the admin organisation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await sql` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UPDATE invitation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SET "sender_organisation_id" = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SELECT id FROM organisation WHERE name = 'Common Knowledge' LIMIT 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| WHERE "sender_organisation_id" IS NULL | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `.execute(db); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+12
to
+20
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Backfill: assign all existing invitations to the admin organisation | |
| await sql` | |
| UPDATE invitation | |
| SET "sender_organisation_id" = ( | |
| SELECT id FROM organisation WHERE name = 'Common Knowledge' LIMIT 1 | |
| ) | |
| WHERE "sender_organisation_id" IS NULL | |
| `.execute(db); | |
| // Backfill deterministically from the invitation's existing organisation | |
| await sql` | |
| UPDATE invitation | |
| SET "sender_organisation_id" = "organisation_id" | |
| WHERE "sender_organisation_id" IS NULL | |
| `.execute(db); | |
| const remainingNullSenderOrganisationIds = await sql<{ count: string }>` | |
| SELECT COUNT(*)::text AS count | |
| FROM invitation | |
| WHERE "sender_organisation_id" IS NULL | |
| `.execute(db); | |
| if (Number(remainingNullSenderOrganisationIds.rows[0]?.count ?? 0) > 0) { | |
| throw new Error( | |
| "Migration 1774658921005_invitation_sender_organisation failed: some invitation rows do not have an organisation_id to backfill sender_organisation_id", | |
| ); | |
| } |
Copilot
AI
Apr 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This FK is configured with onDelete('set null') but the column is made NOT NULL immediately afterwards. If a sender organisation is deleted, Postgres will try to set sender_organisation_id to NULL and the delete will fail. Either keep the column nullable (and handle NULLs in queries) or change the FK action to RESTRICT/CASCADE to match the NOT NULL constraint.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ import { PlusIcon } from "lucide-react"; | |
| import { type FormEvent, useMemo, useState } from "react"; | ||
| import { toast } from "sonner"; | ||
| import FormFieldWrapper from "@/components/forms/FormFieldWrapper"; | ||
| import { useOrganisations } from "@/hooks/useOrganisations"; | ||
| import { useTRPC } from "@/services/trpc/react"; | ||
| import { Button } from "@/shadcn/ui/button"; | ||
| import { Checkbox } from "@/shadcn/ui/checkbox"; | ||
|
|
@@ -43,6 +44,7 @@ export default function CreateInvitationModal() { | |
| Set<string> | ||
| >(new Set()); | ||
|
|
||
| const { organisationId: senderOrganisationId } = useOrganisations(); | ||
| const trpc = useTRPC(); | ||
| const client = useQueryClient(); | ||
|
|
||
|
|
@@ -62,9 +64,7 @@ export default function CreateInvitationModal() { | |
| client.invalidateQueries({ | ||
| queryKey: trpc.organisation.listAll.queryKey(), | ||
| }); | ||
| client.invalidateQueries({ | ||
| queryKey: trpc.invitation.list.queryKey(), | ||
| }); | ||
| client.invalidateQueries(trpc.invitation.list.queryFilter()); | ||
| }, | ||
| onError: (error) => { | ||
| toast.error("Failed to create invitation.", { | ||
|
|
@@ -169,6 +169,7 @@ export default function CreateInvitationModal() { | |
| } | ||
|
|
||
| createInvitationMutate({ | ||
| senderOrganisationId: senderOrganisationId ?? "", | ||
| organisationId, | ||
|
Comment on lines
171
to
173
|
||
| organisationName, | ||
| email, | ||
|
Comment on lines
171
to
175
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -11,6 +11,7 @@ import { | |||||||||||
| } from "@/server/repositories/Invitation"; | ||||||||||||
| import { | ||||||||||||
| findOrganisationById, | ||||||||||||
| findOrganisationForUser, | ||||||||||||
| upsertOrganisation, | ||||||||||||
| } from "@/server/repositories/Organisation"; | ||||||||||||
| import logger from "@/server/services/logger"; | ||||||||||||
|
|
@@ -24,6 +25,7 @@ export const invitationRouter = router({ | |||||||||||
| .object({ | ||||||||||||
| name: z.string(), | ||||||||||||
| email: z.string().email(), | ||||||||||||
| senderOrganisationId: z.string(), | ||||||||||||
|
||||||||||||
| senderOrganisationId: z.string(), | |
| senderOrganisationId: z | |
| .string() | |
| .trim() | |
| .min(1, "Sender organisation is required"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The backfill relies on an organisation named 'Common Knowledge' existing. In environments where it doesn't, the subquery returns NULL and the subsequent NOT NULL alteration will fail. Consider backfilling from existing data instead (e.g., set sender_organisation_id = organisation_id), or create/select a guaranteed default sender org.