managarten/apps-archived/uload/scripts/create-unified-cards-collection.js
Till-JS 61d181fbc2 chore: archive inactive projects to apps-archived/
Move inactive projects out of active workspace:
- bauntown (community website)
- maerchenzauber (AI story generation)
- memoro (voice memo app)
- news (news aggregation)
- nutriphi (nutrition tracking)
- reader (reading app)
- uload (URL shortener)
- wisekeep (AI wisdom extraction)

Update CLAUDE.md documentation:
- Add presi to active projects
- Document archived projects section
- Update workspace configuration

Archived apps can be re-activated by moving back to apps/

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 07:03:59 +01:00

389 lines
8.5 KiB
JavaScript

#!/usr/bin/env node
/**
* Script to create the unified cards collection in PocketBase
* Run this with: node scripts/create-unified-cards-collection.js
*/
const PocketBase = require('pocketbase');
const pb = new PocketBase('http://127.0.0.1:8090');
async function createUnifiedCardsCollection() {
try {
// Admin authentication
await pb.admins.authWithPassword(
process.env.POCKETBASE_ADMIN_EMAIL || 'admin@example.com',
process.env.POCKETBASE_ADMIN_PASSWORD || 'admin123456'
);
console.log('✅ Authenticated as admin');
// Define the unified cards collection schema
const collection = {
name: 'cards',
type: 'base',
schema: [
// Core relationships
{
name: 'user_id',
type: 'relation',
required: false, // null for system templates
options: {
collectionId: '_pb_users_auth_',
cascadeDelete: true,
minSelect: null,
maxSelect: 1,
displayFields: ['email', 'username'],
},
},
// Card type and origin
{
name: 'type',
type: 'select',
required: true,
options: {
values: ['user', 'template', 'system'],
maxSelect: 1,
},
},
{
name: 'source',
type: 'select',
required: false,
options: {
values: ['created', 'duplicated', 'imported', 'migrated'],
maxSelect: 1,
},
},
{
name: 'template_id',
type: 'relation',
required: false,
options: {
collectionId: 'cards', // Self-reference
cascadeDelete: false,
minSelect: null,
maxSelect: 1,
displayFields: ['metadata'],
},
},
// Card configuration (unified structure)
{
name: 'config',
type: 'json',
required: true,
options: {
maxSize: 1000000, // 1MB max
},
},
{
name: 'metadata',
type: 'json',
required: false,
options: {
maxSize: 100000, // 100KB max
},
},
{
name: 'constraints',
type: 'json',
required: false,
options: {
maxSize: 10000, // 10KB max
},
},
// Organization
{
name: 'page',
type: 'text',
required: false,
options: {
min: 0,
max: 100,
pattern: '',
},
},
{
name: 'position',
type: 'number',
required: false,
options: {
min: 0,
max: 9999,
noDecimal: true,
},
},
{
name: 'folder_id',
type: 'relation',
required: false,
options: {
collectionId: 'folders',
cascadeDelete: false,
minSelect: null,
maxSelect: 1,
displayFields: ['name'],
},
},
// Visibility and sharing
{
name: 'visibility',
type: 'select',
required: true,
options: {
values: ['private', 'public', 'unlisted'],
maxSelect: 1,
},
},
{
name: 'is_featured',
type: 'bool',
required: false,
},
{
name: 'allow_duplication',
type: 'bool',
required: false,
},
// Statistics
{
name: 'usage_count',
type: 'number',
required: false,
options: {
min: 0,
max: 999999,
noDecimal: true,
},
},
{
name: 'likes_count',
type: 'number',
required: false,
options: {
min: 0,
max: 999999,
noDecimal: true,
},
},
// Search and categorization
{
name: 'tags',
type: 'json',
required: false,
options: {
maxSize: 10000,
},
},
{
name: 'category',
type: 'select',
required: false,
options: {
values: ['personal', 'creative', 'minimal', 'social', 'portfolio', 'other'],
maxSelect: 1,
},
},
// Variant for styling
{
name: 'variant',
type: 'select',
required: false,
options: {
values: ['default', 'compact', 'hero', 'minimal', 'glass', 'gradient'],
maxSelect: 1,
},
},
],
// Indexes for performance
indexes: [
'CREATE INDEX idx_cards_user_id ON cards (user_id)',
'CREATE INDEX idx_cards_type ON cards (type)',
'CREATE INDEX idx_cards_page ON cards (page)',
'CREATE INDEX idx_cards_visibility ON cards (visibility)',
'CREATE INDEX idx_cards_template_id ON cards (template_id)',
'CREATE INDEX idx_cards_position ON cards (position)',
],
// API Rules
listRule:
'@request.auth.id = user_id || visibility = "public" || (visibility = "unlisted" && @request.query.id != "")',
viewRule: '@request.auth.id = user_id || visibility != "private"',
createRule: '@request.auth.id != ""',
updateRule:
'@request.auth.id = user_id || (@request.auth.id != "" && type = "system" && @request.auth.admin = true)',
deleteRule: '@request.auth.id = user_id && type != "system"',
options: {},
};
// Create the collection
const result = await pb.collections.create(collection);
console.log('✅ Created unified cards collection:', result.name);
// Create some default system templates
await createDefaultTemplates(pb);
console.log('🎉 Successfully created unified cards collection!');
} catch (error) {
console.error('❌ Error creating collection:', error);
// If collection already exists, try to update it
if (error.response?.code === 400) {
console.log('Collection might already exist. Trying to update...');
try {
const existing = await pb.collections.getOne('cards');
await pb.collections.update(existing.id, collection);
console.log('✅ Updated existing cards collection');
} catch (updateError) {
console.error('❌ Failed to update:', updateError);
}
}
}
}
async function createDefaultTemplates(pb) {
const templates = [
{
type: 'template',
visibility: 'public',
is_featured: true,
allow_duplication: true,
category: 'personal',
variant: 'default',
config: {
mode: 'beginner',
modules: [
{
id: 'header-1',
type: 'header',
props: {
title: 'John Doe',
subtitle: 'Software Developer',
},
order: 0,
},
{
id: 'content-1',
type: 'content',
props: {
text: 'Passionate about creating amazing digital experiences.',
},
order: 1,
},
{
id: 'links-1',
type: 'links',
props: {
links: [
{ label: 'GitHub', href: 'https://github.com', icon: '🔗' },
{ label: 'LinkedIn', href: 'https://linkedin.com', icon: '💼' },
],
style: 'button',
},
order: 2,
},
],
},
metadata: {
name: 'Simple Profile',
description: 'A clean and simple profile card',
version: '1.0.0',
tags: ['profile', 'minimal', 'clean'],
},
constraints: {
aspectRatio: '16/9',
},
usage_count: 0,
likes_count: 0,
},
{
type: 'template',
visibility: 'public',
is_featured: true,
allow_duplication: true,
category: 'personal',
variant: 'gradient',
config: {
mode: 'advanced',
template: `
<div class="professional-card">
<h1>{{name}}</h1>
<p class="tagline">{{tagline}}</p>
<div class="contact">
<a href="mailto:{{email}}">{{email}}</a>
<a href="tel:{{phone}}">{{phone}}</a>
</div>
</div>
`,
css: `
.professional-card {
padding: 2rem;
text-align: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.tagline {
font-size: 1.2rem;
margin: 1rem 0;
opacity: 0.9;
}
.contact {
display: flex;
justify-content: center;
gap: 2rem;
margin-top: 2rem;
}
.contact a {
color: white;
text-decoration: none;
}
`,
variables: [
{ name: 'name', type: 'text', label: 'Your Name' },
{ name: 'tagline', type: 'text', label: 'Tagline' },
{ name: 'email', type: 'text', label: 'Email' },
{ name: 'phone', type: 'text', label: 'Phone' },
],
values: {
name: 'Your Name',
tagline: 'Your tagline here',
email: 'contact@example.com',
phone: '+1 234 567 890',
},
},
metadata: {
name: 'Professional Card',
description: 'Professional contact card template',
version: '1.0.0',
tags: ['professional', 'contact'],
},
constraints: {
aspectRatio: '16/9',
},
usage_count: 0,
likes_count: 0,
},
];
for (const template of templates) {
try {
await pb.collection('cards').create(template);
console.log(`✅ Created template: ${template.metadata.name}`);
} catch (error) {
console.error(`❌ Failed to create template: ${template.metadata.name}`, error);
}
}
}
// Run the script
createUnifiedCardsCollection();