mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 07:41:09 +02:00
style: auto-format codebase with Prettier
Applied formatting to 1487+ files using pnpm format:write - TypeScript/JavaScript files - Svelte components - Astro pages - JSON configs - Markdown docs 13 files still need manual review (Astro JSX comments)
This commit is contained in:
parent
0241f5554c
commit
d36b321d9d
3952 changed files with 661498 additions and 739751 deletions
|
|
@ -34,8 +34,8 @@ async function createUnifiedCardsCollection() {
|
|||
cascadeDelete: true,
|
||||
minSelect: null,
|
||||
maxSelect: 1,
|
||||
displayFields: ['email', 'username']
|
||||
}
|
||||
displayFields: ['email', 'username'],
|
||||
},
|
||||
},
|
||||
|
||||
// Card type and origin
|
||||
|
|
@ -45,8 +45,8 @@ async function createUnifiedCardsCollection() {
|
|||
required: true,
|
||||
options: {
|
||||
values: ['user', 'template', 'system'],
|
||||
maxSelect: 1
|
||||
}
|
||||
maxSelect: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
|
|
@ -54,8 +54,8 @@ async function createUnifiedCardsCollection() {
|
|||
required: false,
|
||||
options: {
|
||||
values: ['created', 'duplicated', 'imported', 'migrated'],
|
||||
maxSelect: 1
|
||||
}
|
||||
maxSelect: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'template_id',
|
||||
|
|
@ -66,8 +66,8 @@ async function createUnifiedCardsCollection() {
|
|||
cascadeDelete: false,
|
||||
minSelect: null,
|
||||
maxSelect: 1,
|
||||
displayFields: ['metadata']
|
||||
}
|
||||
displayFields: ['metadata'],
|
||||
},
|
||||
},
|
||||
|
||||
// Card configuration (unified structure)
|
||||
|
|
@ -76,24 +76,24 @@ async function createUnifiedCardsCollection() {
|
|||
type: 'json',
|
||||
required: true,
|
||||
options: {
|
||||
maxSize: 1000000 // 1MB max
|
||||
}
|
||||
maxSize: 1000000, // 1MB max
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'metadata',
|
||||
type: 'json',
|
||||
required: false,
|
||||
options: {
|
||||
maxSize: 100000 // 100KB max
|
||||
}
|
||||
maxSize: 100000, // 100KB max
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'constraints',
|
||||
type: 'json',
|
||||
required: false,
|
||||
options: {
|
||||
maxSize: 10000 // 10KB max
|
||||
}
|
||||
maxSize: 10000, // 10KB max
|
||||
},
|
||||
},
|
||||
|
||||
// Organization
|
||||
|
|
@ -104,8 +104,8 @@ async function createUnifiedCardsCollection() {
|
|||
options: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
pattern: ''
|
||||
}
|
||||
pattern: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'position',
|
||||
|
|
@ -114,8 +114,8 @@ async function createUnifiedCardsCollection() {
|
|||
options: {
|
||||
min: 0,
|
||||
max: 9999,
|
||||
noDecimal: true
|
||||
}
|
||||
noDecimal: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'folder_id',
|
||||
|
|
@ -126,8 +126,8 @@ async function createUnifiedCardsCollection() {
|
|||
cascadeDelete: false,
|
||||
minSelect: null,
|
||||
maxSelect: 1,
|
||||
displayFields: ['name']
|
||||
}
|
||||
displayFields: ['name'],
|
||||
},
|
||||
},
|
||||
|
||||
// Visibility and sharing
|
||||
|
|
@ -137,18 +137,18 @@ async function createUnifiedCardsCollection() {
|
|||
required: true,
|
||||
options: {
|
||||
values: ['private', 'public', 'unlisted'],
|
||||
maxSelect: 1
|
||||
}
|
||||
maxSelect: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'is_featured',
|
||||
type: 'bool',
|
||||
required: false
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: 'allow_duplication',
|
||||
type: 'bool',
|
||||
required: false
|
||||
required: false,
|
||||
},
|
||||
|
||||
// Statistics
|
||||
|
|
@ -159,8 +159,8 @@ async function createUnifiedCardsCollection() {
|
|||
options: {
|
||||
min: 0,
|
||||
max: 999999,
|
||||
noDecimal: true
|
||||
}
|
||||
noDecimal: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'likes_count',
|
||||
|
|
@ -169,8 +169,8 @@ async function createUnifiedCardsCollection() {
|
|||
options: {
|
||||
min: 0,
|
||||
max: 999999,
|
||||
noDecimal: true
|
||||
}
|
||||
noDecimal: true,
|
||||
},
|
||||
},
|
||||
|
||||
// Search and categorization
|
||||
|
|
@ -179,8 +179,8 @@ async function createUnifiedCardsCollection() {
|
|||
type: 'json',
|
||||
required: false,
|
||||
options: {
|
||||
maxSize: 10000
|
||||
}
|
||||
maxSize: 10000,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'category',
|
||||
|
|
@ -188,8 +188,8 @@ async function createUnifiedCardsCollection() {
|
|||
required: false,
|
||||
options: {
|
||||
values: ['personal', 'creative', 'minimal', 'social', 'portfolio', 'other'],
|
||||
maxSelect: 1
|
||||
}
|
||||
maxSelect: 1,
|
||||
},
|
||||
},
|
||||
|
||||
// Variant for styling
|
||||
|
|
@ -199,9 +199,9 @@ async function createUnifiedCardsCollection() {
|
|||
required: false,
|
||||
options: {
|
||||
values: ['default', 'compact', 'hero', 'minimal', 'glass', 'gradient'],
|
||||
maxSelect: 1
|
||||
}
|
||||
}
|
||||
maxSelect: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
// Indexes for performance
|
||||
|
|
@ -211,7 +211,7 @@ async function createUnifiedCardsCollection() {
|
|||
'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)'
|
||||
'CREATE INDEX idx_cards_position ON cards (position)',
|
||||
],
|
||||
|
||||
// API Rules
|
||||
|
|
@ -223,7 +223,7 @@ async function createUnifiedCardsCollection() {
|
|||
'@request.auth.id = user_id || (@request.auth.id != "" && type = "system" && @request.auth.admin = true)',
|
||||
deleteRule: '@request.auth.id = user_id && type != "system"',
|
||||
|
||||
options: {}
|
||||
options: {},
|
||||
};
|
||||
|
||||
// Create the collection
|
||||
|
|
@ -268,17 +268,17 @@ async function createDefaultTemplates(pb) {
|
|||
type: 'header',
|
||||
props: {
|
||||
title: 'John Doe',
|
||||
subtitle: 'Software Developer'
|
||||
subtitle: 'Software Developer',
|
||||
},
|
||||
order: 0
|
||||
order: 0,
|
||||
},
|
||||
{
|
||||
id: 'content-1',
|
||||
type: 'content',
|
||||
props: {
|
||||
text: 'Passionate about creating amazing digital experiences.'
|
||||
text: 'Passionate about creating amazing digital experiences.',
|
||||
},
|
||||
order: 1
|
||||
order: 1,
|
||||
},
|
||||
{
|
||||
id: 'links-1',
|
||||
|
|
@ -286,25 +286,25 @@ async function createDefaultTemplates(pb) {
|
|||
props: {
|
||||
links: [
|
||||
{ label: 'GitHub', href: 'https://github.com', icon: '🔗' },
|
||||
{ label: 'LinkedIn', href: 'https://linkedin.com', icon: '💼' }
|
||||
{ label: 'LinkedIn', href: 'https://linkedin.com', icon: '💼' },
|
||||
],
|
||||
style: 'button'
|
||||
style: 'button',
|
||||
},
|
||||
order: 2
|
||||
}
|
||||
]
|
||||
order: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
metadata: {
|
||||
name: 'Simple Profile',
|
||||
description: 'A clean and simple profile card',
|
||||
version: '1.0.0',
|
||||
tags: ['profile', 'minimal', 'clean']
|
||||
tags: ['profile', 'minimal', 'clean'],
|
||||
},
|
||||
constraints: {
|
||||
aspectRatio: '16/9'
|
||||
aspectRatio: '16/9',
|
||||
},
|
||||
usage_count: 0,
|
||||
likes_count: 0
|
||||
likes_count: 0,
|
||||
},
|
||||
{
|
||||
type: 'template',
|
||||
|
|
@ -352,27 +352,27 @@ async function createDefaultTemplates(pb) {
|
|||
{ 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' }
|
||||
{ name: 'phone', type: 'text', label: 'Phone' },
|
||||
],
|
||||
values: {
|
||||
name: 'Your Name',
|
||||
tagline: 'Your tagline here',
|
||||
email: 'contact@example.com',
|
||||
phone: '+1 234 567 890'
|
||||
}
|
||||
phone: '+1 234 567 890',
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
name: 'Professional Card',
|
||||
description: 'Professional contact card template',
|
||||
version: '1.0.0',
|
||||
tags: ['professional', 'contact']
|
||||
tags: ['professional', 'contact'],
|
||||
},
|
||||
constraints: {
|
||||
aspectRatio: '16/9'
|
||||
aspectRatio: '16/9',
|
||||
},
|
||||
usage_count: 0,
|
||||
likes_count: 0
|
||||
}
|
||||
likes_count: 0,
|
||||
},
|
||||
];
|
||||
|
||||
for (const template of templates) {
|
||||
|
|
|
|||
|
|
@ -20,17 +20,17 @@ const sizes = [72, 96, 128, 144, 152, 192, 384, 512];
|
|||
// Ensure directory exists
|
||||
const iconsDir = path.join(__dirname, '..', 'static', 'icons');
|
||||
if (!fs.existsSync(iconsDir)) {
|
||||
fs.mkdirSync(iconsDir, { recursive: true });
|
||||
fs.mkdirSync(iconsDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Generate SVG icons
|
||||
sizes.forEach(size => {
|
||||
const filename = `icon-${size}x${size}.svg`;
|
||||
const filepath = path.join(iconsDir, filename);
|
||||
const content = createSvgIcon(size);
|
||||
|
||||
fs.writeFileSync(filepath, content.trim());
|
||||
console.log(`Generated ${filename}`);
|
||||
sizes.forEach((size) => {
|
||||
const filename = `icon-${size}x${size}.svg`;
|
||||
const filepath = path.join(iconsDir, filename);
|
||||
const content = createSvgIcon(size);
|
||||
|
||||
fs.writeFileSync(filepath, content.trim());
|
||||
console.log(`Generated ${filename}`);
|
||||
});
|
||||
|
||||
// Also create apple-touch-icon
|
||||
|
|
@ -40,10 +40,10 @@ console.log('Generated apple-touch-icon.svg');
|
|||
|
||||
// Create maskable icon (with safe area padding)
|
||||
const createMaskableIcon = (size) => {
|
||||
const safeArea = size * 0.8; // 80% safe area
|
||||
const padding = (size - safeArea) / 2;
|
||||
|
||||
return `
|
||||
const safeArea = size * 0.8; // 80% safe area
|
||||
const padding = (size - safeArea) / 2;
|
||||
|
||||
return `
|
||||
<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="${size}" height="${size}" fill="#3B82F6"/>
|
||||
<rect x="${padding}" y="${padding}" width="${safeArea}" height="${safeArea}" fill="#3B82F6" rx="${safeArea * 0.15}"/>
|
||||
|
|
@ -53,13 +53,13 @@ const createMaskableIcon = (size) => {
|
|||
};
|
||||
|
||||
// Generate maskable icons
|
||||
[192, 512].forEach(size => {
|
||||
const filename = `icon-maskable-${size}x${size}.svg`;
|
||||
const filepath = path.join(iconsDir, filename);
|
||||
const content = createMaskableIcon(size);
|
||||
|
||||
fs.writeFileSync(filepath, content.trim());
|
||||
console.log(`Generated ${filename}`);
|
||||
[192, 512].forEach((size) => {
|
||||
const filename = `icon-maskable-${size}x${size}.svg`;
|
||||
const filepath = path.join(iconsDir, filename);
|
||||
const content = createMaskableIcon(size);
|
||||
|
||||
fs.writeFileSync(filepath, content.trim());
|
||||
console.log(`Generated ${filename}`);
|
||||
});
|
||||
|
||||
console.log('\n✅ All PWA icons generated successfully!');
|
||||
console.log('\n✅ All PWA icons generated successfully!');
|
||||
|
|
|
|||
|
|
@ -11,64 +11,65 @@ async function migrate() {
|
|||
try {
|
||||
console.log('Starting migration...');
|
||||
console.log('This script will update links with use_username=true to use the new format');
|
||||
|
||||
|
||||
// Get all links with use_username=true
|
||||
const links = await pb.collection('links').getFullList({
|
||||
filter: 'use_username = true',
|
||||
expand: 'user_id'
|
||||
expand: 'user_id',
|
||||
});
|
||||
|
||||
|
||||
console.log(`Found ${links.length} links to migrate`);
|
||||
|
||||
|
||||
let successCount = 0;
|
||||
let errorCount = 0;
|
||||
let skippedCount = 0;
|
||||
|
||||
|
||||
for (const link of links) {
|
||||
try {
|
||||
// Get the username from the expanded user
|
||||
const username = link.expand?.user_id?.username;
|
||||
|
||||
|
||||
if (!username) {
|
||||
console.error(`No username found for link ${link.id} (user: ${link.user_id})`);
|
||||
errorCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Check if already migrated (contains slash)
|
||||
if (link.short_code.includes('/')) {
|
||||
console.log(`Link ${link.id} already migrated: ${link.short_code}`);
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Create new short_code with username prefix
|
||||
const newShortCode = `${username}/${link.short_code}`;
|
||||
|
||||
|
||||
console.log(`Updating link ${link.id}: ${link.short_code} -> ${newShortCode}`);
|
||||
|
||||
|
||||
// Update the link
|
||||
await pb.collection('links').update(link.id, {
|
||||
short_code: newShortCode
|
||||
short_code: newShortCode,
|
||||
});
|
||||
|
||||
|
||||
successCount++;
|
||||
} catch (error) {
|
||||
console.error(`Error migrating link ${link.id}:`, error.message);
|
||||
errorCount++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log('\n=== Migration Summary ===');
|
||||
console.log(`✅ Successfully migrated: ${successCount} links`);
|
||||
console.log(`⏭️ Skipped (already migrated): ${skippedCount} links`);
|
||||
console.log(`❌ Errors: ${errorCount} links`);
|
||||
|
||||
|
||||
if (successCount > 0) {
|
||||
console.log('\n⚠️ Important: The use_username field should be removed from the collection schema');
|
||||
console.log(
|
||||
'\n⚠️ Important: The use_username field should be removed from the collection schema'
|
||||
);
|
||||
console.log('You can do this manually in PocketBase Admin UI');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Migration failed:', error);
|
||||
process.exit(1);
|
||||
|
|
@ -76,4 +77,4 @@ async function migrate() {
|
|||
}
|
||||
|
||||
// Run migration
|
||||
migrate();
|
||||
migrate();
|
||||
|
|
|
|||
|
|
@ -14,59 +14,58 @@ async function migrate() {
|
|||
try {
|
||||
// Authenticate as admin (update with your credentials)
|
||||
await pb.admins.authWithPassword('admin@example.com', 'your-password');
|
||||
|
||||
|
||||
console.log('Fetching all links with use_username=true...');
|
||||
|
||||
|
||||
// Get all links with use_username=true
|
||||
const links = await pb.collection('links').getFullList({
|
||||
filter: 'use_username = true',
|
||||
expand: 'user_id'
|
||||
expand: 'user_id',
|
||||
});
|
||||
|
||||
|
||||
console.log(`Found ${links.length} links to migrate`);
|
||||
|
||||
|
||||
let successCount = 0;
|
||||
let errorCount = 0;
|
||||
|
||||
|
||||
for (const link of links) {
|
||||
try {
|
||||
// Get the username from the expanded user
|
||||
const username = link.expand?.user_id?.username;
|
||||
|
||||
|
||||
if (!username) {
|
||||
console.error(`No username found for link ${link.id} (user: ${link.user_id})`);
|
||||
errorCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Create new short_code with username prefix
|
||||
const newShortCode = `${username}/${link.short_code}`;
|
||||
|
||||
|
||||
console.log(`Updating link ${link.id}: ${link.short_code} -> ${newShortCode}`);
|
||||
|
||||
|
||||
// Update the link
|
||||
await pb.collection('links').update(link.id, {
|
||||
short_code: newShortCode
|
||||
short_code: newShortCode,
|
||||
});
|
||||
|
||||
|
||||
successCount++;
|
||||
} catch (error) {
|
||||
console.error(`Error migrating link ${link.id}:`, error);
|
||||
errorCount++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log('\nMigration complete!');
|
||||
console.log(`✅ Successfully migrated: ${successCount} links`);
|
||||
console.log(`❌ Errors: ${errorCount} links`);
|
||||
|
||||
|
||||
if (successCount > 0) {
|
||||
console.log('\n⚠️ Next steps:');
|
||||
console.log('1. Remove the use_username field from the links collection in PocketBase admin');
|
||||
console.log('2. Test that all migrated links still work');
|
||||
console.log('3. Deploy the updated application code');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Migration failed:', error);
|
||||
process.exit(1);
|
||||
|
|
@ -74,4 +73,4 @@ async function migrate() {
|
|||
}
|
||||
|
||||
// Run migration
|
||||
migrate();
|
||||
migrate();
|
||||
|
|
|
|||
|
|
@ -15,32 +15,32 @@ const pb = new PocketBase(process.env.PUBLIC_POCKETBASE_URL || 'https://pb.ulo.a
|
|||
|
||||
async function migrate() {
|
||||
console.log('Starting migration to workspace system...');
|
||||
|
||||
|
||||
try {
|
||||
// Authenticate as admin
|
||||
const adminEmail = process.env.ADMIN_EMAIL;
|
||||
const adminPassword = process.env.ADMIN_PASSWORD;
|
||||
|
||||
|
||||
if (!adminEmail || !adminPassword) {
|
||||
console.error('Please set ADMIN_EMAIL and ADMIN_PASSWORD in .env.production');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
await pb.admins.authWithPassword(adminEmail, adminPassword);
|
||||
console.log('✓ Authenticated as admin');
|
||||
|
||||
|
||||
// Get all users
|
||||
const users = await pb.collection('users').getFullList();
|
||||
console.log(`Found ${users.length} users to migrate`);
|
||||
|
||||
|
||||
// Create personal workspace for each user
|
||||
for (const user of users) {
|
||||
try {
|
||||
// Check if personal workspace already exists
|
||||
const existingWorkspaces = await pb.collection('workspaces').getList(1, 1, {
|
||||
filter: `owner="${user.id}" && type="personal"`
|
||||
filter: `owner="${user.id}" && type="personal"`,
|
||||
});
|
||||
|
||||
|
||||
if (existingWorkspaces.items.length === 0) {
|
||||
// Create personal workspace
|
||||
const workspace = await pb.collection('workspaces').create({
|
||||
|
|
@ -48,18 +48,18 @@ async function migrate() {
|
|||
owner: user.id,
|
||||
type: 'personal',
|
||||
subscription_status: user.subscription_status || 'free',
|
||||
description: 'Personal workspace'
|
||||
description: 'Personal workspace',
|
||||
});
|
||||
|
||||
|
||||
console.log(`✓ Created personal workspace for ${user.email}`);
|
||||
|
||||
|
||||
// Add user as owner in workspace_members
|
||||
await pb.collection('workspace_members').create({
|
||||
workspace: workspace.id,
|
||||
user: user.id,
|
||||
role: 'owner',
|
||||
invitation_status: 'accepted',
|
||||
accepted_at: new Date().toISOString()
|
||||
accepted_at: new Date().toISOString(),
|
||||
});
|
||||
} else {
|
||||
console.log(`- Personal workspace already exists for ${user.email}`);
|
||||
|
|
@ -68,25 +68,25 @@ async function migrate() {
|
|||
console.error(`✗ Error creating workspace for ${user.email}:`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Migrate shared_access to workspace_members
|
||||
console.log('\nMigrating shared access...');
|
||||
|
||||
|
||||
try {
|
||||
const sharedAccess = await pb.collection('shared_access').getFullList({
|
||||
expand: 'owner,user'
|
||||
expand: 'owner,user',
|
||||
});
|
||||
|
||||
|
||||
console.log(`Found ${sharedAccess.length} shared access records`);
|
||||
|
||||
|
||||
for (const access of sharedAccess) {
|
||||
try {
|
||||
// Find or create team workspace for the owner
|
||||
let teamWorkspace;
|
||||
const existingTeamWorkspaces = await pb.collection('workspaces').getList(1, 1, {
|
||||
filter: `owner="${access.owner}" && type="team"`
|
||||
filter: `owner="${access.owner}" && type="team"`,
|
||||
});
|
||||
|
||||
|
||||
if (existingTeamWorkspaces.items.length === 0) {
|
||||
// Create team workspace
|
||||
const ownerData = access.expand?.owner;
|
||||
|
|
@ -94,28 +94,28 @@ async function migrate() {
|
|||
name: `${ownerData?.name || ownerData?.email}'s Team`,
|
||||
owner: access.owner,
|
||||
type: 'team',
|
||||
description: 'Team workspace for collaboration'
|
||||
description: 'Team workspace for collaboration',
|
||||
});
|
||||
|
||||
|
||||
console.log(`✓ Created team workspace for owner ${access.owner}`);
|
||||
|
||||
|
||||
// Add owner as owner in workspace_members
|
||||
await pb.collection('workspace_members').create({
|
||||
workspace: teamWorkspace.id,
|
||||
user: access.owner,
|
||||
role: 'owner',
|
||||
invitation_status: 'accepted',
|
||||
accepted_at: new Date().toISOString()
|
||||
accepted_at: new Date().toISOString(),
|
||||
});
|
||||
} else {
|
||||
teamWorkspace = existingTeamWorkspaces.items[0];
|
||||
}
|
||||
|
||||
|
||||
// Check if member already exists
|
||||
const existingMembers = await pb.collection('workspace_members').getList(1, 1, {
|
||||
filter: `workspace="${teamWorkspace.id}" && user="${access.user}"`
|
||||
filter: `workspace="${teamWorkspace.id}" && user="${access.user}"`,
|
||||
});
|
||||
|
||||
|
||||
if (existingMembers.items.length === 0) {
|
||||
// Add team member
|
||||
await pb.collection('workspace_members').create({
|
||||
|
|
@ -126,9 +126,9 @@ async function migrate() {
|
|||
invitation_status: access.invitation_status,
|
||||
invitation_token: access.invitation_token,
|
||||
invited_at: access.invited_at,
|
||||
accepted_at: access.accepted_at
|
||||
accepted_at: access.accepted_at,
|
||||
});
|
||||
|
||||
|
||||
console.log(`✓ Migrated team member ${access.user} to workspace ${teamWorkspace.id}`);
|
||||
} else {
|
||||
console.log(`- Team member ${access.user} already exists in workspace`);
|
||||
|
|
@ -140,13 +140,12 @@ async function migrate() {
|
|||
} catch (error) {
|
||||
console.error('✗ Error fetching shared access:', error.message);
|
||||
}
|
||||
|
||||
|
||||
console.log('\n✅ Migration completed successfully!');
|
||||
console.log('\nNext steps:');
|
||||
console.log('1. Test the new workspace system');
|
||||
console.log('2. Update any links/cards to reference workspace instead of owner');
|
||||
console.log('3. Once verified, you can remove the old shared_access collection');
|
||||
|
||||
} catch (error) {
|
||||
console.error('✗ Migration failed:', error);
|
||||
process.exit(1);
|
||||
|
|
@ -154,4 +153,4 @@ async function migrate() {
|
|||
}
|
||||
|
||||
// Run migration
|
||||
migrate().catch(console.error);
|
||||
migrate().catch(console.error);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
/**
|
||||
* Seed Script for Local PocketBase Development
|
||||
*
|
||||
*
|
||||
* This script creates test data for local development.
|
||||
*
|
||||
*
|
||||
* Usage:
|
||||
* 1. Make sure PocketBase is running locally (http://localhost:8090)
|
||||
* 2. Run: node scripts/seed-local-db.js
|
||||
|
|
@ -33,7 +33,7 @@ const testUsers = [
|
|||
passwordConfirm: 'test123456',
|
||||
username: 'testuser',
|
||||
name: 'Test User',
|
||||
emailVisibility: true
|
||||
emailVisibility: true,
|
||||
},
|
||||
{
|
||||
email: 'demo@localhost',
|
||||
|
|
@ -41,8 +41,8 @@ const testUsers = [
|
|||
passwordConfirm: 'demo123456',
|
||||
username: 'demouser',
|
||||
name: 'Demo User',
|
||||
emailVisibility: true
|
||||
}
|
||||
emailVisibility: true,
|
||||
},
|
||||
];
|
||||
|
||||
const testLinks = [
|
||||
|
|
@ -54,7 +54,7 @@ const testLinks = [
|
|||
is_active: true,
|
||||
click_limit: null,
|
||||
expires_at: null,
|
||||
password: null
|
||||
password: null,
|
||||
},
|
||||
{
|
||||
short_code: 'test2',
|
||||
|
|
@ -64,7 +64,7 @@ const testLinks = [
|
|||
is_active: true,
|
||||
click_limit: 100,
|
||||
expires_at: null,
|
||||
password: null
|
||||
password: null,
|
||||
},
|
||||
{
|
||||
short_code: 'protected',
|
||||
|
|
@ -74,7 +74,7 @@ const testLinks = [
|
|||
is_active: true,
|
||||
click_limit: null,
|
||||
expires_at: null,
|
||||
password: 'secret123'
|
||||
password: 'secret123',
|
||||
},
|
||||
{
|
||||
short_code: 'expired',
|
||||
|
|
@ -84,8 +84,8 @@ const testLinks = [
|
|||
is_active: true,
|
||||
click_limit: null,
|
||||
expires_at: new Date(Date.now() - 86400000).toISOString(), // Yesterday
|
||||
password: null
|
||||
}
|
||||
password: null,
|
||||
},
|
||||
];
|
||||
|
||||
async function seedDatabase() {
|
||||
|
|
@ -99,14 +99,14 @@ async function seedDatabase() {
|
|||
console.log('⚠️ Admin auth failed. You may need to:');
|
||||
console.log(' 1. Create admin account at http://localhost:8090/_/');
|
||||
console.log(' 2. Update POCKETBASE_ADMIN_EMAIL and POCKETBASE_ADMIN_PASSWORD\n');
|
||||
|
||||
|
||||
// Try to continue without admin auth (some operations might fail)
|
||||
}
|
||||
|
||||
// Step 2: Create test users
|
||||
console.log('👥 Creating test users...');
|
||||
const createdUsers = [];
|
||||
|
||||
|
||||
for (const userData of testUsers) {
|
||||
try {
|
||||
const user = await pb.collection('users').create(userData);
|
||||
|
|
@ -118,7 +118,7 @@ async function seedDatabase() {
|
|||
// Try to get existing user
|
||||
try {
|
||||
const users = await pb.collection('users').getList(1, 1, {
|
||||
filter: `email = "${userData.email}"`
|
||||
filter: `email = "${userData.email}"`,
|
||||
});
|
||||
if (users.items.length > 0) {
|
||||
createdUsers.push(users.items[0]);
|
||||
|
|
@ -135,22 +135,22 @@ async function seedDatabase() {
|
|||
|
||||
// Step 3: Create test links
|
||||
console.log('🔗 Creating test links...');
|
||||
|
||||
|
||||
// Use the first created user as the owner
|
||||
const ownerId = createdUsers[0]?.id;
|
||||
|
||||
|
||||
for (const linkData of testLinks) {
|
||||
try {
|
||||
// Add owner if we have one
|
||||
if (ownerId) {
|
||||
linkData.user_id = ownerId;
|
||||
}
|
||||
|
||||
|
||||
// Generate a random custom code if needed
|
||||
if (!linkData.custom_code) {
|
||||
linkData.custom_code = linkData.short_code;
|
||||
}
|
||||
|
||||
|
||||
const link = await pb.collection('links').create(linkData);
|
||||
console.log(` ✅ Created link: ${linkData.short_code} -> ${linkData.original_url}`);
|
||||
} catch (error) {
|
||||
|
|
@ -165,16 +165,16 @@ async function seedDatabase() {
|
|||
|
||||
// Step 4: Create some test clicks
|
||||
console.log('📊 Creating test click data...');
|
||||
|
||||
|
||||
try {
|
||||
// Get one of the links we created
|
||||
const links = await pb.collection('links').getList(1, 1, {
|
||||
filter: 'short_code = "test1"'
|
||||
filter: 'short_code = "test1"',
|
||||
});
|
||||
|
||||
|
||||
if (links.items.length > 0) {
|
||||
const link = links.items[0];
|
||||
|
||||
|
||||
// Create some fake clicks
|
||||
const clickData = [
|
||||
{
|
||||
|
|
@ -186,7 +186,7 @@ async function seedDatabase() {
|
|||
os: 'macOS',
|
||||
country: 'Germany',
|
||||
city: 'Munich',
|
||||
clicked_at: new Date().toISOString()
|
||||
clicked_at: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
link_id: link.id,
|
||||
|
|
@ -197,10 +197,10 @@ async function seedDatabase() {
|
|||
os: 'iOS',
|
||||
country: 'USA',
|
||||
city: 'New York',
|
||||
clicked_at: new Date(Date.now() - 3600000).toISOString() // 1 hour ago
|
||||
}
|
||||
clicked_at: new Date(Date.now() - 3600000).toISOString(), // 1 hour ago
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
for (const click of clickData) {
|
||||
try {
|
||||
await pb.collection('clicks').create(click);
|
||||
|
|
@ -227,7 +227,6 @@ async function seedDatabase() {
|
|||
console.log(' http://localhost:5173/protected - Password: secret123');
|
||||
console.log(' http://localhost:5173/expired - Expired link\n');
|
||||
console.log('👉 Next: Open http://localhost:5173 and test the app!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Seeding failed:', error);
|
||||
process.exit(1);
|
||||
|
|
@ -235,9 +234,11 @@ async function seedDatabase() {
|
|||
}
|
||||
|
||||
// Run the seeding
|
||||
seedDatabase().then(() => {
|
||||
process.exit(0);
|
||||
}).catch(error => {
|
||||
console.error('Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
seedDatabase()
|
||||
.then(() => {
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,21 +10,21 @@ const path = require('path');
|
|||
|
||||
const envFiles = ['.env', '.env.development', '.env.production'];
|
||||
|
||||
envFiles.forEach(file => {
|
||||
const filePath = path.join(__dirname, file);
|
||||
if (fs.existsSync(filePath)) {
|
||||
console.log(`\n${file} exists`);
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const lines = content.split('\n');
|
||||
lines.forEach(line => {
|
||||
if (line.includes('POCKETBASE_URL')) {
|
||||
console.log(` -> ${line}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
envFiles.forEach((file) => {
|
||||
const filePath = path.join(__dirname, file);
|
||||
if (fs.existsSync(filePath)) {
|
||||
console.log(`\n${file} exists`);
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const lines = content.split('\n');
|
||||
lines.forEach((line) => {
|
||||
if (line.includes('POCKETBASE_URL')) {
|
||||
console.log(` -> ${line}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
console.log('\nNOTE: SvelteKit/Vite loads environment variables differently.');
|
||||
console.log('The app should use:');
|
||||
console.log('- Development: http://localhost:8090 (from .env or fallback)');
|
||||
console.log('- Production: https://pb.ulo.ad (from .env.production)');
|
||||
console.log('- Production: https://pb.ulo.ad (from .env.production)');
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ try {
|
|||
passwordConfirm: 'testpassword123',
|
||||
username: `testuser${Date.now()}`,
|
||||
name: 'Test User',
|
||||
emailVisibility: true
|
||||
emailVisibility: true,
|
||||
});
|
||||
|
||||
console.log('✅ User created successfully:', testUser.id);
|
||||
|
|
|
|||
|
|
@ -18,13 +18,13 @@ async function test() {
|
|||
|
||||
// Test 3: Get folders for user
|
||||
const folders = await pb.collection('folders').getList(1, 50, {
|
||||
filter: `user_id="${user.id}" && is_public=true`
|
||||
filter: `user_id="${user.id}" && is_public=true`,
|
||||
});
|
||||
console.log('Public folders:', folders.items.length);
|
||||
|
||||
// Test 4: Get links for user
|
||||
const links = await pb.collection('links').getList(1, 100, {
|
||||
filter: `user_id="${user.id}" && is_active=true`
|
||||
filter: `user_id="${user.id}" && is_active=true`,
|
||||
});
|
||||
console.log('Active links:', links.items.length);
|
||||
} catch (err) {
|
||||
|
|
|
|||
|
|
@ -17,13 +17,13 @@ async function test() {
|
|||
|
||||
// Test 3: Get folders for user
|
||||
const folders = await pb.collection('folders').getList(1, 50, {
|
||||
filter: `user_id="${user.id}" && is_public=true`
|
||||
filter: `user_id="${user.id}" && is_public=true`,
|
||||
});
|
||||
console.log('Public folders:', folders.items.length);
|
||||
|
||||
// Test 4: Get links for user
|
||||
const links = await pb.collection('links').getList(1, 100, {
|
||||
filter: `user_id="${user.id}" && is_active=true`
|
||||
filter: `user_id="${user.id}" && is_active=true`,
|
||||
});
|
||||
console.log('Active links:', links.items.length);
|
||||
} catch (err) {
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ async function testConnection() {
|
|||
fields: usersCollection.schema.map((f) => ({
|
||||
name: f.name,
|
||||
type: f.type,
|
||||
required: f.required
|
||||
}))
|
||||
required: f.required,
|
||||
})),
|
||||
});
|
||||
|
||||
// Test 4: Try to list users (might fail due to permissions)
|
||||
|
|
@ -47,7 +47,7 @@ async function testConnection() {
|
|||
email: testEmail,
|
||||
password: 'Test123456!',
|
||||
passwordConfirm: 'Test123456!',
|
||||
username: `testuser${Date.now()}`
|
||||
username: `testuser${Date.now()}`,
|
||||
});
|
||||
console.log('✓ Registration test successful, user created:', result.id);
|
||||
// Clean up test user
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ async function testConnection() {
|
|||
email: testEmail,
|
||||
password: 'Test123456!',
|
||||
passwordConfirm: 'Test123456!',
|
||||
username: testUsername
|
||||
username: testUsername,
|
||||
});
|
||||
console.log('✓ Registration successful! User ID:', result.id);
|
||||
|
||||
|
|
@ -115,8 +115,8 @@ async function testConnection() {
|
|||
const response = await fetch(`${PROD_POCKETBASE_URL}/api/collections`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Origin: 'https://your-frontend-domain.com'
|
||||
}
|
||||
Origin: 'https://your-frontend-domain.com',
|
||||
},
|
||||
});
|
||||
console.log(
|
||||
'✓ CORS headers present:',
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ console.log('==========================================\n');
|
|||
async function testRedisConnection() {
|
||||
console.log('1. Testing Redis Connection...');
|
||||
const connected = await ensureRedisConnection();
|
||||
|
||||
|
||||
if (connected) {
|
||||
console.log('✅ Redis connected successfully');
|
||||
console.log(` Host: ${process.env.REDIS_HOST || 'ycsoowwsc84s0s8gc8oooosk'}`);
|
||||
|
|
@ -24,20 +24,20 @@ async function testRedisConnection() {
|
|||
|
||||
async function testBasicCacheOperations() {
|
||||
console.log('2. Testing Basic Cache Operations...');
|
||||
|
||||
|
||||
// Test set and get
|
||||
const testKey = 'test:key';
|
||||
const testValue = { message: 'Hello Redis', timestamp: Date.now() };
|
||||
|
||||
|
||||
await cache.set(testKey, testValue, 60);
|
||||
console.log(' Set test value in cache');
|
||||
|
||||
|
||||
const retrieved = await cache.get(testKey);
|
||||
console.log(' Retrieved value:', retrieved);
|
||||
|
||||
|
||||
if (retrieved && retrieved.message === testValue.message) {
|
||||
console.log('✅ Basic cache operations working\n');
|
||||
|
||||
|
||||
// Clean up
|
||||
await cache.del(testKey);
|
||||
return true;
|
||||
|
|
@ -49,21 +49,21 @@ async function testBasicCacheOperations() {
|
|||
|
||||
async function testLinkCaching() {
|
||||
console.log('3. Testing Link Cache Functions...');
|
||||
|
||||
|
||||
const testShortCode = 'test123';
|
||||
const testUrl = 'https://example.com/test';
|
||||
|
||||
|
||||
// Cache a redirect
|
||||
await linkCache.cacheRedirect(testShortCode, testUrl);
|
||||
console.log(` Cached redirect: ${testShortCode} -> ${testUrl}`);
|
||||
|
||||
|
||||
// Try to retrieve it
|
||||
const cachedUrl = await linkCache.getRedirectUrl(testShortCode);
|
||||
console.log(` Retrieved URL: ${cachedUrl}`);
|
||||
|
||||
|
||||
if (cachedUrl === testUrl) {
|
||||
console.log('✅ Link caching working correctly\n');
|
||||
|
||||
|
||||
// Clean up
|
||||
await linkCache.invalidate(testShortCode);
|
||||
return true;
|
||||
|
|
@ -75,12 +75,12 @@ async function testLinkCaching() {
|
|||
|
||||
async function checkCachedLinks() {
|
||||
console.log('4. Checking Currently Cached Links...');
|
||||
|
||||
|
||||
try {
|
||||
// Get all keys matching redirect pattern
|
||||
const keys = await redis.keys('redirect:*');
|
||||
console.log(` Found ${keys.length} cached redirects`);
|
||||
|
||||
|
||||
if (keys.length > 0) {
|
||||
console.log('\n Sample cached links:');
|
||||
for (const key of keys.slice(0, 5)) {
|
||||
|
|
@ -90,7 +90,7 @@ async function checkCachedLinks() {
|
|||
console.log(` - ${shortCode}: ${url} (TTL: ${ttl}s)`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Check trending links
|
||||
const trending = await linkCache.getTrendingLinks(5);
|
||||
if (trending.length > 0) {
|
||||
|
|
@ -99,7 +99,7 @@ async function checkCachedLinks() {
|
|||
console.log(` ${i + 1}. ${code}`);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
console.log('✅ Cache inspection complete\n');
|
||||
return true;
|
||||
} catch (error) {
|
||||
|
|
@ -110,34 +110,34 @@ async function checkCachedLinks() {
|
|||
|
||||
async function performanceTest() {
|
||||
console.log('5. Performance Test (Cache vs No Cache)...');
|
||||
|
||||
|
||||
const testCode = 'perf-test';
|
||||
const testUrl = 'https://example.com/performance';
|
||||
|
||||
|
||||
// Test without cache (simulate)
|
||||
const dbStart = Date.now();
|
||||
await new Promise(resolve => setTimeout(resolve, 50)); // Simulate DB latency
|
||||
await new Promise((resolve) => setTimeout(resolve, 50)); // Simulate DB latency
|
||||
const dbTime = Date.now() - dbStart;
|
||||
console.log(` Database fetch simulation: ${dbTime}ms`);
|
||||
|
||||
|
||||
// Cache the URL
|
||||
await linkCache.cacheRedirect(testCode, testUrl);
|
||||
|
||||
|
||||
// Test with cache
|
||||
const cacheStart = Date.now();
|
||||
await linkCache.getRedirectUrl(testCode);
|
||||
const cacheTime = Date.now() - cacheStart;
|
||||
console.log(` Cache fetch: ${cacheTime}ms`);
|
||||
|
||||
const improvement = Math.round((dbTime - cacheTime) / dbTime * 100);
|
||||
|
||||
const improvement = Math.round(((dbTime - cacheTime) / dbTime) * 100);
|
||||
console.log(` 🚀 Performance improvement: ${improvement}%`);
|
||||
|
||||
|
||||
if (cacheTime < dbTime) {
|
||||
console.log('✅ Cache is faster than database\n');
|
||||
} else {
|
||||
console.log('⚠️ Cache performance needs investigation\n');
|
||||
}
|
||||
|
||||
|
||||
// Clean up
|
||||
await linkCache.invalidate(testCode);
|
||||
return true;
|
||||
|
|
@ -146,13 +146,13 @@ async function performanceTest() {
|
|||
async function monitorLiveTraffic() {
|
||||
console.log('6. Monitoring Live Traffic (10 seconds)...');
|
||||
console.log(' Open your app and click some links to see cache activity\n');
|
||||
|
||||
|
||||
// Subscribe to Redis monitor for 10 seconds
|
||||
const monitor = await redis.monitor();
|
||||
let commandCount = 0;
|
||||
let cacheHits = 0;
|
||||
let cacheSets = 0;
|
||||
|
||||
|
||||
monitor.on('monitor', (time, args) => {
|
||||
const command = args[0];
|
||||
if (command === 'get' && args[1]?.includes('redirect:')) {
|
||||
|
|
@ -164,35 +164,35 @@ async function monitorLiveTraffic() {
|
|||
}
|
||||
commandCount++;
|
||||
});
|
||||
|
||||
|
||||
// Stop monitoring after 10 seconds
|
||||
await new Promise(resolve => setTimeout(resolve, 10000));
|
||||
await new Promise((resolve) => setTimeout(resolve, 10000));
|
||||
monitor.disconnect();
|
||||
|
||||
|
||||
console.log(`\n Monitoring complete:`);
|
||||
console.log(` - Total Redis commands: ${commandCount}`);
|
||||
console.log(` - Cache GETs: ${cacheHits}`);
|
||||
console.log(` - Cache SETs: ${cacheSets}`);
|
||||
console.log('✅ Live monitoring complete\n');
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Main test runner
|
||||
async function runAllTests() {
|
||||
console.log('Starting Redis Cache Tests...\n');
|
||||
|
||||
|
||||
const tests = [
|
||||
testRedisConnection,
|
||||
testBasicCacheOperations,
|
||||
testLinkCaching,
|
||||
checkCachedLinks,
|
||||
performanceTest
|
||||
performanceTest,
|
||||
];
|
||||
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
|
||||
for (const test of tests) {
|
||||
try {
|
||||
const result = await test();
|
||||
|
|
@ -203,30 +203,30 @@ async function runAllTests() {
|
|||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log('==========================================');
|
||||
console.log(`Test Results: ${passed} passed, ${failed} failed`);
|
||||
|
||||
|
||||
// Optional: Run live monitoring
|
||||
console.log('\nWould you like to monitor live traffic? (Ctrl+C to skip)');
|
||||
console.log('Starting in 3 seconds...\n');
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
|
||||
try {
|
||||
await monitorLiveTraffic();
|
||||
} catch (error) {
|
||||
console.log('Monitoring cancelled');
|
||||
}
|
||||
|
||||
|
||||
// Close Redis connection
|
||||
redis.disconnect();
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
// Run tests
|
||||
runAllTests().catch(error => {
|
||||
runAllTests().catch((error) => {
|
||||
console.error('Test suite failed:', error);
|
||||
redis.disconnect();
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ async function testRegistration() {
|
|||
password: testPassword,
|
||||
passwordConfirm: testPassword,
|
||||
username: `user${timestamp}`,
|
||||
emailVisibility: true
|
||||
emailVisibility: true,
|
||||
});
|
||||
|
||||
console.log('✅ User created successfully');
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import PocketBase from 'pocketbase';
|
|||
// Test verschiedene URL-Varianten
|
||||
const urls = [
|
||||
'http://pocketbase-xs0ccokk8s0goko4w40gwc0w.91.99.221.179.sslip.io', // ohne trailing slash
|
||||
'http://pocketbase-xs0ccokk8s0goko4w40gwc0w.91.99.221.179.sslip.io/' // mit trailing slash
|
||||
'http://pocketbase-xs0ccokk8s0goko4w40gwc0w.91.99.221.179.sslip.io/', // mit trailing slash
|
||||
];
|
||||
|
||||
console.log('Testing different URL configurations...\n');
|
||||
|
|
@ -30,7 +30,7 @@ async function testUrl(url) {
|
|||
passwordConfirm: 'TestPass123!',
|
||||
username: testUsername,
|
||||
name: 'Test User',
|
||||
emailVisibility: true
|
||||
emailVisibility: true,
|
||||
};
|
||||
|
||||
console.log(` Attempting registration with: ${testEmail}`);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue