Replace @vercel/postgres, Blob, and Upstash with lib/sql.js, MinIO object storage, and CT 102 Redis rate limits. Add Dockerfile for Dokploy deploy, homelab runbooks, Neon data-copy helper, and point CI smoke/visual at the homelab URL instead of Vercel previews. Co-authored-by: Cursor <cursoragent@cursor.com>
115 lines
4.2 KiB
JavaScript
Executable file
115 lines
4.2 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
||
|
||
import { config } from 'dotenv';
|
||
import { sql } from '../lib/sql.js';
|
||
|
||
// Load environment variables
|
||
config({ path: '.env.local' });
|
||
|
||
async function addCollaborationFeatures() {
|
||
try {
|
||
console.log('🔧 Adding collaboration features to database...\n');
|
||
|
||
// Add visibility and collaboration fields to collections table
|
||
console.log('📋 Updating collections table...');
|
||
await sql`
|
||
ALTER TABLE collections
|
||
ADD COLUMN IF NOT EXISTS visibility VARCHAR(20) DEFAULT 'private',
|
||
ADD COLUMN IF NOT EXISTS tcg VARCHAR(50) DEFAULT 'MTG',
|
||
ADD COLUMN IF NOT EXISTS tags TEXT
|
||
`;
|
||
console.log('✅ Updated collections table');
|
||
|
||
// Create collection_permissions table
|
||
console.log('<27><> Creating collection_permissions table...');
|
||
await sql`
|
||
CREATE TABLE IF NOT EXISTS collection_permissions (
|
||
id SERIAL PRIMARY KEY,
|
||
collection_id INTEGER REFERENCES collections(id) ON DELETE CASCADE,
|
||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||
role VARCHAR(20) NOT NULL CHECK (role IN ('owner', 'editor', 'viewer')),
|
||
status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'pending', 'declined')),
|
||
invite_token VARCHAR(255) UNIQUE,
|
||
invited_by INTEGER REFERENCES users(id),
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
UNIQUE(collection_id, user_id)
|
||
)
|
||
`;
|
||
console.log('✅ Created collection_permissions table');
|
||
|
||
// Create collection_activity table for audit trail
|
||
console.log('📊 Creating collection_activity table...');
|
||
await sql`
|
||
CREATE TABLE IF NOT EXISTS collection_activity (
|
||
id SERIAL PRIMARY KEY,
|
||
collection_id INTEGER REFERENCES collections(id) ON DELETE CASCADE,
|
||
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||
action VARCHAR(50) NOT NULL,
|
||
details JSONB,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
)
|
||
`;
|
||
console.log('✅ Created collection_activity table');
|
||
|
||
// Add is_pending field to users table for invited users
|
||
console.log('👤 Updating users table...');
|
||
await sql`
|
||
ALTER TABLE users
|
||
ADD COLUMN IF NOT EXISTS is_pending BOOLEAN DEFAULT false
|
||
`;
|
||
console.log('✅ Updated users table');
|
||
|
||
// Create indexes for better performance
|
||
console.log('⚡ Creating indexes...');
|
||
await sql`
|
||
CREATE INDEX IF NOT EXISTS idx_collection_permissions_collection_id
|
||
ON collection_permissions(collection_id)
|
||
`;
|
||
await sql`
|
||
CREATE INDEX IF NOT EXISTS idx_collection_permissions_user_id
|
||
ON collection_permissions(user_id)
|
||
`;
|
||
await sql`
|
||
CREATE INDEX IF NOT EXISTS idx_collection_activity_collection_id
|
||
ON collection_activity(collection_id)
|
||
`;
|
||
await sql`
|
||
CREATE INDEX IF NOT EXISTS idx_collections_visibility
|
||
ON collections(visibility)
|
||
`;
|
||
console.log('✅ Created indexes');
|
||
|
||
// Migrate existing collections to have owner permissions
|
||
console.log('🔄 Migrating existing collections...');
|
||
const existingCollections = await sql`
|
||
SELECT c.id, c.user_id
|
||
FROM collections c
|
||
LEFT JOIN collection_permissions cp ON c.id = cp.collection_id AND c.user_id = cp.user_id
|
||
WHERE cp.id IS NULL
|
||
`;
|
||
|
||
for (const collection of existingCollections.rows) {
|
||
await sql`
|
||
INSERT INTO collection_permissions (collection_id, user_id, role, status)
|
||
VALUES (${collection.id}, ${collection.user_id}, 'owner', 'active')
|
||
ON CONFLICT (collection_id, user_id) DO NOTHING
|
||
`;
|
||
}
|
||
console.log(`✅ Migrated ${existingCollections.rows.length} existing collections`);
|
||
|
||
console.log('\n🎉 Collaboration features added successfully!');
|
||
console.log('\n📋 New Features:');
|
||
console.log(' • Collection visibility (private, invite-only, public)');
|
||
console.log(' • User permissions (owner, editor, viewer)');
|
||
console.log(' • Invitation system with email notifications');
|
||
console.log(' • Activity logging for audit trails');
|
||
console.log(' • Pending user support for email invitations');
|
||
|
||
} catch (error) {
|
||
console.error('❌ Migration failed:', error.message);
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
addCollaborationFeatures();
|