diff --git a/server/config/database.js b/server/config/database.js index 747d3be..47b4e1a 100644 --- a/server/config/database.js +++ b/server/config/database.js @@ -49,6 +49,12 @@ class SqliteAdapter { return !!columns.find(c => c.name === column); } + async columnIsNullable(table, column) { + const columns = this.db.pragma(`table_info(${table})`); + const col = columns.find(c => c.name === column); + return col ? col.notnull === 0 : true; + } + close() { this.db.close(); } @@ -98,6 +104,14 @@ class PostgresAdapter { return result.rows.length > 0; } + async columnIsNullable(table, column) { + const result = await this.pool.query( + 'SELECT is_nullable FROM information_schema.columns WHERE table_name = $1 AND column_name = $2', + [table, column] + ); + return result.rows.length > 0 ? result.rows[0].is_nullable === 'YES' : true; + } + close() { this.pool?.end(); } @@ -691,6 +705,34 @@ export async function initDatabase() { await db.exec('CREATE INDEX IF NOT EXISTS idx_caldav_tokens_hash ON caldav_tokens(token_hash)'); } + // CalDAV: make token column nullable (now only token_hash is stored for new tokens) + if (!(await db.columnIsNullable('caldav_tokens', 'token'))) { + if (isPostgres) { + await db.exec('ALTER TABLE caldav_tokens ALTER COLUMN token DROP NOT NULL'); + } else { + // SQLite does not support ALTER COLUMN — recreate the table + await db.exec(` + CREATE TABLE caldav_tokens_new ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + token TEXT UNIQUE, + token_hash TEXT DEFAULT NULL, + name TEXT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + last_used_at DATETIME DEFAULT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE + ); + INSERT INTO caldav_tokens_new (id, user_id, token, token_hash, name, created_at, last_used_at) + SELECT id, user_id, token, token_hash, name, created_at, last_used_at FROM caldav_tokens; + DROP TABLE caldav_tokens; + ALTER TABLE caldav_tokens_new RENAME TO caldav_tokens; + CREATE INDEX IF NOT EXISTS idx_caldav_tokens_user_id ON caldav_tokens(user_id); + CREATE INDEX IF NOT EXISTS idx_caldav_tokens_token ON caldav_tokens(token); + CREATE INDEX IF NOT EXISTS idx_caldav_tokens_hash ON caldav_tokens(token_hash); + `); + } + } + // ── OAuth tables ──────────────────────────────────────────────────────── if (isPostgres) { await db.exec(` diff --git a/server/routes/calendar.js b/server/routes/calendar.js index 6c0cf45..f7fc946 100644 --- a/server/routes/calendar.js +++ b/server/routes/calendar.js @@ -752,7 +752,7 @@ router.post('/caldav-tokens', authenticateToken, async (req, res) => { const tokenHash = crypto.createHash('sha256').update(token).digest('hex'); const result = await db.run( // Store only the hash — never the plaintext — to limit exposure on DB breach. - 'INSERT INTO caldav_tokens (user_id, token, token_hash, name) VALUES (?, NULL, ?, ?)', + 'INSERT INTO caldav_tokens (user_id, token_hash, name) VALUES (?, ?, ?)', [req.user.id, tokenHash, name.trim()], ); res.status(201).json({