fix(worksheets): remove foreign key constraint to support guest users

- Remove FK constraint from worksheet_settings table via migration 0014
- Update schema definition to remove .references() call
- Remove user existence check from POST /api/worksheets/settings
- Guest users can now save worksheet preferences without user account
- Matches pattern used by room_members table for arcade games

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock 2025-11-06 08:25:18 -06:00
parent fa6c2829cc
commit e6e9ec3e4f
6 changed files with 1115 additions and 105 deletions

View File

@ -0,0 +1,28 @@
-- Custom SQL migration file, put your code below! --
-- Remove foreign key constraint from worksheet_settings to allow guest users
-- SQLite doesn't support DROP CONSTRAINT, so we need to recreate the table
-- Create new table without foreign key
CREATE TABLE `worksheet_settings_new` (
`id` TEXT PRIMARY KEY NOT NULL,
`user_id` TEXT NOT NULL,
`worksheet_type` TEXT NOT NULL,
`config` TEXT NOT NULL,
`created_at` INTEGER NOT NULL,
`updated_at` INTEGER NOT NULL
);--> statement-breakpoint
-- Copy existing data (if any)
INSERT INTO `worksheet_settings_new`
SELECT id, user_id, worksheet_type, config, created_at, updated_at
FROM `worksheet_settings`;--> statement-breakpoint
-- Drop old table
DROP TABLE `worksheet_settings`;--> statement-breakpoint
-- Rename new table to original name
ALTER TABLE `worksheet_settings_new` RENAME TO `worksheet_settings`;--> statement-breakpoint
-- Recreate index
CREATE INDEX `worksheet_settings_user_type_idx` ON `worksheet_settings` (`user_id`, `worksheet_type`);

View File

@ -116,13 +116,9 @@
"abacus_settings_user_id_users_id_fk": {
"name": "abacus_settings_user_id_users_id_fk",
"tableFrom": "abacus_settings",
"columnsFrom": [
"user_id"
],
"columnsFrom": ["user_id"],
"tableTo": "users",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
}
@ -240,9 +236,7 @@
"indexes": {
"arcade_rooms_code_unique": {
"name": "arcade_rooms_code_unique",
"columns": [
"code"
],
"columns": ["code"],
"isUnique": true
}
},
@ -339,26 +333,18 @@
"arcade_sessions_room_id_arcade_rooms_id_fk": {
"name": "arcade_sessions_room_id_arcade_rooms_id_fk",
"tableFrom": "arcade_sessions",
"columnsFrom": [
"room_id"
],
"columnsFrom": ["room_id"],
"tableTo": "arcade_rooms",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
},
"arcade_sessions_user_id_users_id_fk": {
"name": "arcade_sessions_user_id_users_id_fk",
"tableFrom": "arcade_sessions",
"columnsFrom": [
"user_id"
],
"columnsFrom": ["user_id"],
"tableTo": "users",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
}
@ -424,9 +410,7 @@
"indexes": {
"players_user_id_idx": {
"name": "players_user_id_idx",
"columns": [
"user_id"
],
"columns": ["user_id"],
"isUnique": false
}
},
@ -434,13 +418,9 @@
"players_user_id_users_id_fk": {
"name": "players_user_id_users_id_fk",
"tableFrom": "players",
"columnsFrom": [
"user_id"
],
"columnsFrom": ["user_id"],
"tableTo": "users",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
}
@ -514,9 +494,7 @@
"indexes": {
"idx_room_members_user_id_unique": {
"name": "idx_room_members_user_id_unique",
"columns": [
"user_id"
],
"columns": ["user_id"],
"isUnique": true
}
},
@ -524,13 +502,9 @@
"room_members_room_id_arcade_rooms_id_fk": {
"name": "room_members_room_id_arcade_rooms_id_fk",
"tableFrom": "room_members",
"columnsFrom": [
"room_id"
],
"columnsFrom": ["room_id"],
"tableTo": "arcade_rooms",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
}
@ -605,13 +579,9 @@
"room_member_history_room_id_arcade_rooms_id_fk": {
"name": "room_member_history_room_id_arcade_rooms_id_fk",
"tableFrom": "room_member_history",
"columnsFrom": [
"room_id"
],
"columnsFrom": ["room_id"],
"tableTo": "arcade_rooms",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
}
@ -713,10 +683,7 @@
"indexes": {
"idx_room_invitations_user_room": {
"name": "idx_room_invitations_user_room",
"columns": [
"user_id",
"room_id"
],
"columns": ["user_id", "room_id"],
"isUnique": true
}
},
@ -724,13 +691,9 @@
"room_invitations_room_id_arcade_rooms_id_fk": {
"name": "room_invitations_room_id_arcade_rooms_id_fk",
"tableFrom": "room_invitations",
"columnsFrom": [
"room_id"
],
"columnsFrom": ["room_id"],
"tableTo": "arcade_rooms",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
}
@ -833,13 +796,9 @@
"room_reports_room_id_arcade_rooms_id_fk": {
"name": "room_reports_room_id_arcade_rooms_id_fk",
"tableFrom": "room_reports",
"columnsFrom": [
"room_id"
],
"columnsFrom": ["room_id"],
"tableTo": "arcade_rooms",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
}
@ -918,10 +877,7 @@
"indexes": {
"idx_room_bans_user_room": {
"name": "idx_room_bans_user_room",
"columns": [
"user_id",
"room_id"
],
"columns": ["user_id", "room_id"],
"isUnique": true
}
},
@ -929,13 +885,9 @@
"room_bans_room_id_arcade_rooms_id_fk": {
"name": "room_bans_room_id_arcade_rooms_id_fk",
"tableFrom": "room_bans",
"columnsFrom": [
"room_id"
],
"columnsFrom": ["room_id"],
"tableTo": "arcade_rooms",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
}
@ -998,13 +950,9 @@
"user_stats_user_id_users_id_fk": {
"name": "user_stats_user_id_users_id_fk",
"tableFrom": "user_stats",
"columnsFrom": [
"user_id"
],
"columnsFrom": ["user_id"],
"tableTo": "users",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
}
@ -1062,16 +1010,12 @@
"indexes": {
"users_guest_id_unique": {
"name": "users_guest_id_unique",
"columns": [
"guest_id"
],
"columns": ["guest_id"],
"isUnique": true
},
"users_email_unique": {
"name": "users_email_unique",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": true
}
},
@ -1091,4 +1035,4 @@
"internal": {
"indexes": {}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -99,6 +99,13 @@
"when": 1762432185673,
"tag": "0013_conscious_firebird",
"breakpoints": true
},
{
"idx": 14,
"version": "6",
"when": 1762434916279,
"tag": "0014_remarkable_master_chief",
"breakpoints": true
}
]
}
}

View File

@ -32,7 +32,10 @@ export async function GET(req: NextRequest) {
// Only 'addition' is supported for now
if (worksheetType !== 'addition') {
return NextResponse.json({ error: `Unsupported worksheet type: ${worksheetType}` }, { status: 400 })
return NextResponse.json(
{ error: `Unsupported worksheet type: ${worksheetType}` },
{ status: 400 }
)
}
// Look up user's saved settings
@ -97,20 +100,10 @@ export async function POST(req: NextRequest) {
// Only 'addition' is supported for now
if (worksheetType !== 'addition') {
return NextResponse.json({ error: `Unsupported worksheet type: ${worksheetType}` }, { status: 400 })
}
// Check if user exists in database
const [user] = await db.select().from(schema.users).where(eq(schema.users.id, viewerId)).limit(1)
if (!user) {
// User doesn't exist yet - this is OK for guest users
// Don't save settings for non-existent users
console.log(`[Worksheet Settings] Skipping save for non-existent user: ${viewerId}`)
return NextResponse.json({
success: false,
message: 'Settings not saved - user account not created yet',
})
return NextResponse.json(
{ error: `Unsupported worksheet type: ${worksheetType}` },
{ status: 400 }
)
}
// Serialize config (adds version automatically)

View File

@ -1,5 +1,4 @@
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
import { users } from './users'
/**
* Worksheet generator settings table - persists user preferences per worksheet type
@ -9,15 +8,16 @@ import { users } from './users'
*
* The config column stores versioned JSON that is validated at runtime
* See src/app/create/worksheets/config-schemas.ts for schema definitions
*
* Note: No foreign key constraint - allows guest users to save settings
* (matches pattern used by room_members table)
*/
export const worksheetSettings = sqliteTable('worksheet_settings', {
/** Unique identifier (UUID) */
id: text('id').primaryKey(),
/** Foreign key to users table */
userId: text('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
/** User ID (may be authenticated user or guest ID) */
userId: text('user_id').notNull(),
/** Type of worksheet: 'addition', 'subtraction', 'multiplication', etc. */
worksheetType: text('worksheet_type').notNull(),