Fix unter invite process after unifying flows

This commit is contained in:
Karl Ludwig Weise
2026-05-31 21:26:05 +02:00
parent 00885844f9
commit e0c1b55fc7
5 changed files with 867 additions and 9 deletions
+2
View File
@@ -0,0 +1,2 @@
ALTER TABLE "user_invite" ALTER COLUMN "tenant_id" DROP NOT NULL;--> statement-breakpoint
ALTER TABLE "user_invite" ALTER COLUMN "invited_by" DROP NOT NULL;
+853
View File
@@ -0,0 +1,853 @@
{
"id": "8da6b050-6e61-46c0-8480-8b0c2ac62e55",
"prevId": "f0ff401a-6299-471e-9812-80c678b9d47e",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.challenge_throttle": {
"name": "challenge_throttle",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"tenant_id": {
"name": "tenant_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"failed_attempts": {
"name": "failed_attempts",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 0
},
"last_attempt_at": {
"name": "last_attempt_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"reset_at": {
"name": "reset_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"challenge_throttle_tenant_id_tenant_id_fk": {
"name": "challenge_throttle_tenant_id_tenant_id_fk",
"tableFrom": "challenge_throttle",
"tableTo": "tenant",
"columnsFrom": ["tenant_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.tenant": {
"name": "tenant",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"short_name": {
"name": "short_name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"long_name": {
"name": "long_name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"descriptions": {
"name": "descriptions",
"type": "json",
"primaryKey": false,
"notNull": true
},
"languages": {
"name": "languages",
"type": "json",
"primaryKey": false,
"notNull": true
},
"defaultLanguage": {
"name": "defaultLanguage",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'en'"
},
"logo": {
"name": "logo",
"type": "varchar(100000)",
"primaryKey": false,
"notNull": false
},
"database_url": {
"name": "database_url",
"type": "text",
"primaryKey": false,
"notNull": true
},
"setup_state": {
"name": "setup_state",
"type": "setup_state",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'SETTINGS'"
},
"links": {
"name": "links",
"type": "json",
"primaryKey": false,
"notNull": true,
"default": "'{}'::json"
},
"domain": {
"name": "domain",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {
"tenant_database_url_idx": {
"name": "tenant_database_url_idx",
"columns": [
{
"expression": "database_url",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"tenant_short_name_unique": {
"name": "tenant_short_name_unique",
"nullsNotDistinct": false,
"columns": ["short_name"]
},
"tenant_domain_unique": {
"name": "tenant_domain_unique",
"nullsNotDistinct": false,
"columns": ["domain"]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.tenant_config": {
"name": "tenant_config",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"tenant_id": {
"name": "tenant_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"type": {
"name": "type",
"type": "config_type",
"typeSchema": "public",
"primaryKey": false,
"notNull": true
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {
"tenant_config_tenant_name_idx": {
"name": "tenant_config_tenant_name_idx",
"columns": [
{
"expression": "tenant_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "name",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"tenant_config_tenant_id_tenant_id_fk": {
"name": "tenant_config_tenant_id_tenant_id_fk",
"tableFrom": "tenant_config",
"tableTo": "tenant",
"columnsFrom": ["tenant_id"],
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"role": {
"name": "role",
"type": "user_role",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'STAFF'"
},
"tenant_id": {
"name": "tenant_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"last_login_at": {
"name": "last_login_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"is_active": {
"name": "is_active",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": true
},
"confirmation_state": {
"name": "confirmation_state",
"type": "confirmation_state",
"typeSchema": "public",
"primaryKey": false,
"notNull": false,
"default": "'INVITED'"
},
"passphrase_hash": {
"name": "passphrase_hash",
"type": "text",
"primaryKey": false,
"notNull": false
},
"recovery_passphrase": {
"name": "recovery_passphrase",
"type": "text",
"primaryKey": false,
"notNull": false
},
"language": {
"name": "language",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'de'"
}
},
"indexes": {
"user_email_idx": {
"name": "user_email_idx",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"user_tenant_id_tenant_id_fk": {
"name": "user_tenant_id_tenant_id_fk",
"tableFrom": "user",
"tableTo": "tenant",
"columnsFrom": ["tenant_id"],
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_email_unique": {
"name": "user_email_unique",
"nullsNotDistinct": false,
"columns": ["email"]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user_invite": {
"name": "user_invite",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"invite_code": {
"name": "invite_code",
"type": "uuid",
"primaryKey": false,
"notNull": true,
"default": "gen_random_uuid()"
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"role": {
"name": "role",
"type": "user_role",
"typeSchema": "public",
"primaryKey": false,
"notNull": true
},
"tenant_id": {
"name": "tenant_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"invited_by": {
"name": "invited_by",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"language": {
"name": "language",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'de'"
},
"used": {
"name": "used",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"used_at": {
"name": "used_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"created_user_id": {
"name": "created_user_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"user_invite_code_idx": {
"name": "user_invite_code_idx",
"columns": [
{
"expression": "invite_code",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
},
"user_invite_email_idx": {
"name": "user_invite_email_idx",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"user_invite_tenant_idx": {
"name": "user_invite_tenant_idx",
"columns": [
{
"expression": "tenant_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"user_invite_tenant_id_tenant_id_fk": {
"name": "user_invite_tenant_id_tenant_id_fk",
"tableFrom": "user_invite",
"tableTo": "tenant",
"columnsFrom": ["tenant_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
},
"user_invite_invited_by_user_id_fk": {
"name": "user_invite_invited_by_user_id_fk",
"tableFrom": "user_invite",
"tableTo": "user",
"columnsFrom": ["invited_by"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
},
"user_invite_created_user_id_user_id_fk": {
"name": "user_invite_created_user_id_user_id_fk",
"tableFrom": "user_invite",
"tableTo": "user",
"columnsFrom": ["created_user_id"],
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_invite_invite_code_unique": {
"name": "user_invite_invite_code_unique",
"nullsNotDistinct": false,
"columns": ["invite_code"]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user_passkey": {
"name": "user_passkey",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"public_key": {
"name": "public_key",
"type": "text",
"primaryKey": false,
"notNull": true
},
"counter": {
"name": "counter",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 0
},
"device_name": {
"name": "device_name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"last_used_at": {
"name": "last_used_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"user_passkey_user_idx": {
"name": "user_passkey_user_idx",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"user_passkey_user_id_user_id_fk": {
"name": "user_passkey_user_id_user_id_fk",
"tableFrom": "user_passkey",
"tableTo": "user",
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user_session": {
"name": "user_session",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"session_token": {
"name": "session_token",
"type": "text",
"primaryKey": false,
"notNull": true
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": true
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": true
},
"passkey_id": {
"name": "passkey_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"ip_address": {
"name": "ip_address",
"type": "text",
"primaryKey": false,
"notNull": false
},
"user_agent": {
"name": "user_agent",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"last_used_at": {
"name": "last_used_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {
"user_session_user_idx": {
"name": "user_session_user_idx",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"user_session_token_idx": {
"name": "user_session_token_idx",
"columns": [
{
"expression": "session_token",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"user_session_user_id_user_id_fk": {
"name": "user_session_user_id_user_id_fk",
"tableFrom": "user_session",
"tableTo": "user",
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_session_session_token_unique": {
"name": "user_session_session_token_unique",
"nullsNotDistinct": false,
"columns": ["session_token"]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {
"public.config_type": {
"name": "config_type",
"schema": "public",
"values": ["BOOLEAN", "NUMBER", "STRING"]
},
"public.confirmation_state": {
"name": "confirmation_state",
"schema": "public",
"values": ["INVITED", "CONFIRMED", "ACCESS_GRANTED"]
},
"public.setup_state": {
"name": "setup_state",
"schema": "public",
"values": ["SETTINGS", "AGENTS", "CHANNELS", "STAFF", "READY"]
},
"public.user_role": {
"name": "user_role",
"schema": "public",
"values": ["GLOBAL_ADMIN", "TENANT_ADMIN", "STAFF"]
}
},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
+7
View File
@@ -92,6 +92,13 @@
"when": 1780157869249,
"tag": "0012_polite_impossible_man",
"breakpoints": true
},
{
"idx": 13,
"version": "7",
"when": 1780252077820,
"tag": "0013_abnormal_vengeance",
"breakpoints": true
}
]
}
+2 -6
View File
@@ -204,13 +204,9 @@ export const userInvite = pgTable(
/** Role to assign to user when they register */
role: userRoleEnum("role").notNull(),
/** Tenant the user is being invited to */
tenantId: uuid("tenant_id")
.notNull()
.references(() => tenant.id, { onDelete: "cascade" }),
tenantId: uuid("tenant_id").references(() => tenant.id, { onDelete: "cascade" }),
/** User who sent the invitation */
invitedBy: uuid("invited_by")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
invitedBy: uuid("invited_by").references(() => user.id, { onDelete: "cascade" }),
/** Language preference for the invitation */
language: text("language").notNull().default("de"),
/** Whether the invitation has been used */
+3 -3
View File
@@ -126,7 +126,6 @@ export class UserService {
name: userData.name,
role: userData.role!,
tenantId: userData.tenantId!,
invitedBy: "system",
expiresAt: addMinutes(new Date(), 10),
inviteCode: uuidv7(),
};
@@ -341,7 +340,9 @@ export class UserService {
.where(
and(
eq(centralSchema.user.email, matchingInvite.email),
eq(centralSchema.user.tenantId, matchingInvite.tenantId),
matchingInvite.tenantId
? eq(centralSchema.user.tenantId, matchingInvite.tenantId)
: undefined,
),
)
.limit(1);
@@ -354,7 +355,6 @@ export class UserService {
role: resultData.role,
tenantId: resultData.tenantId,
language: resultData.language || "de",
recoveryPassphrase: "warum landet der hier",
confirmationState: "CONFIRMED",
isActive: true,
};