fix: PB 0.25 compat — empty filter/sort and created/updated fields (#5)
This commit is contained in:
@@ -9,12 +9,12 @@ export const GET = withAuth(async (request: NextRequest, _user) => {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
const perPage = parseInt(searchParams.get('perPage') || '50');
|
||||
const filter = searchParams.get('filter') || '';
|
||||
const filter = searchParams.get('filter') || undefined;
|
||||
const sort = searchParams.get('sort') || '-created';
|
||||
|
||||
const pb = createPocketBaseClient();
|
||||
const result = await pb.collection('agents').getList(page, perPage, {
|
||||
filter,
|
||||
...(filter ? { filter } : {}),
|
||||
sort,
|
||||
});
|
||||
|
||||
|
||||
@@ -9,12 +9,12 @@ export const GET = withAuth(async (request: NextRequest, _user) => {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
const perPage = parseInt(searchParams.get('perPage') || '50');
|
||||
const filter = searchParams.get('filter') || '';
|
||||
const filter = searchParams.get('filter') || undefined;
|
||||
const sort = searchParams.get('sort') || '-created';
|
||||
|
||||
const pb = createPocketBaseClient();
|
||||
const result = await pb.collection('canvases').getList(page, perPage, {
|
||||
filter,
|
||||
...(filter ? { filter } : {}),
|
||||
sort,
|
||||
});
|
||||
|
||||
|
||||
@@ -9,12 +9,12 @@ export const GET = withAuth(async (request: NextRequest, _user) => {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
const perPage = parseInt(searchParams.get('perPage') || '50');
|
||||
const filter = searchParams.get('filter') || '';
|
||||
const filter = searchParams.get('filter') || undefined;
|
||||
const sort = searchParams.get('sort') || 'sort_order';
|
||||
|
||||
const pb = createPocketBaseClient();
|
||||
const result = await pb.collection('domains').getList(page, perPage, {
|
||||
filter,
|
||||
...(filter ? { filter } : {}),
|
||||
sort,
|
||||
});
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ export const GET = withAuth(async (request: NextRequest) => {
|
||||
|
||||
const pb = createPocketBaseClient();
|
||||
const result = await pb.collection('habit_logs').getList(1, 1000, {
|
||||
filter,
|
||||
...(filter ? { filter } : {}),
|
||||
sort: '-logged_at',
|
||||
});
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ export const GET = withAuth<RouteContext>(async (request: NextRequest, _user, co
|
||||
const { searchParams } = new URL(request.url);
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
const perPage = parseInt(searchParams.get('perPage') || '50');
|
||||
const filter = searchParams.get('filter') || '';
|
||||
const filter = searchParams.get('filter') || undefined;
|
||||
const sort = searchParams.get('sort') || '-logged_at';
|
||||
|
||||
const pb = createPocketBaseClient();
|
||||
|
||||
@@ -9,12 +9,12 @@ export const GET = withAuth(async (request: NextRequest, _user) => {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
const perPage = parseInt(searchParams.get('perPage') || '50');
|
||||
const filter = searchParams.get('filter') || '';
|
||||
const filter = searchParams.get('filter') || undefined;
|
||||
const sort = searchParams.get('sort') || '-created';
|
||||
|
||||
const pb = createPocketBaseClient();
|
||||
const result = await pb.collection('habits').getList(page, perPage, {
|
||||
filter,
|
||||
...(filter ? { filter } : {}),
|
||||
sort,
|
||||
});
|
||||
|
||||
|
||||
@@ -9,12 +9,12 @@ export const GET = withAuth(async (request: NextRequest, _user) => {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
const perPage = parseInt(searchParams.get('perPage') || '50');
|
||||
const filter = searchParams.get('filter') || '';
|
||||
const filter = searchParams.get('filter') || undefined;
|
||||
const sort = searchParams.get('sort') || '-created';
|
||||
|
||||
const pb = createPocketBaseClient();
|
||||
const result = await pb.collection('milestones').getList(page, perPage, {
|
||||
filter,
|
||||
...(filter ? { filter } : {}),
|
||||
sort,
|
||||
});
|
||||
|
||||
|
||||
@@ -10,12 +10,12 @@ export const GET = withAuth(async (request: NextRequest, _user) => {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
const perPage = parseInt(searchParams.get('perPage') || '50');
|
||||
const filter = searchParams.get('filter') || '';
|
||||
const filter = searchParams.get('filter') || undefined;
|
||||
const sort = searchParams.get('sort') || '-created';
|
||||
|
||||
const pb = createPocketBaseClient();
|
||||
const result = await pb.collection('notes').getList(page, perPage, {
|
||||
filter,
|
||||
...(filter ? { filter } : {}),
|
||||
sort,
|
||||
});
|
||||
|
||||
|
||||
@@ -9,12 +9,12 @@ export const GET = withAuth(async (request: NextRequest, _user) => {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
const perPage = parseInt(searchParams.get('perPage') || '50');
|
||||
const filter = searchParams.get('filter') || '';
|
||||
const filter = searchParams.get('filter') || undefined;
|
||||
const sort = searchParams.get('sort') || '-created';
|
||||
|
||||
const pb = createPocketBaseClient();
|
||||
const result = await pb.collection('projects').getList(page, perPage, {
|
||||
filter,
|
||||
...(filter ? { filter } : {}),
|
||||
sort,
|
||||
});
|
||||
|
||||
|
||||
@@ -9,12 +9,12 @@ export const GET = withAuth(async (request: NextRequest, _user) => {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
const perPage = parseInt(searchParams.get('perPage') || '50');
|
||||
const filter = searchParams.get('filter') || '';
|
||||
const filter = searchParams.get('filter') || undefined;
|
||||
const sort = searchParams.get('sort') || '-created';
|
||||
|
||||
const pb = createPocketBaseClient();
|
||||
const result = await pb.collection('reports').getList(page, perPage, {
|
||||
filter,
|
||||
...(filter ? { filter } : {}),
|
||||
sort,
|
||||
});
|
||||
|
||||
|
||||
@@ -9,12 +9,12 @@ export const GET = withAuth(async (request: NextRequest, _user) => {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
const perPage = parseInt(searchParams.get('perPage') || '50');
|
||||
const filter = searchParams.get('filter') || '';
|
||||
const filter = searchParams.get('filter') || undefined;
|
||||
const sort = searchParams.get('sort') || 'name';
|
||||
|
||||
const pb = createPocketBaseClient();
|
||||
const result = await pb.collection('tags').getList(page, perPage, {
|
||||
filter,
|
||||
...(filter ? { filter } : {}),
|
||||
sort,
|
||||
});
|
||||
|
||||
|
||||
@@ -9,12 +9,12 @@ export const GET = withAuth(async (request: NextRequest, _user) => {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
const perPage = parseInt(searchParams.get('perPage') || '50');
|
||||
const filter = searchParams.get('filter') || '';
|
||||
const filter = searchParams.get('filter') || undefined;
|
||||
const sort = searchParams.get('sort') || '-created';
|
||||
|
||||
const pb = createPocketBaseClient();
|
||||
const result = await pb.collection('tasks').getList(page, perPage, {
|
||||
filter,
|
||||
...(filter ? { filter } : {}),
|
||||
sort,
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ export const GET = withAuth(async (request: NextRequest, _user) => {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
const perPage = parseInt(searchParams.get('perPage') || '50');
|
||||
const filter = searchParams.get('filter') || '';
|
||||
const filter = searchParams.get('filter') || undefined;
|
||||
const sort = searchParams.get('sort') || '-created';
|
||||
const webhookId = searchParams.get('webhook_id') || '';
|
||||
|
||||
|
||||
@@ -9,12 +9,12 @@ export const GET = withAuth(async (request: NextRequest, _user) => {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
const perPage = parseInt(searchParams.get('perPage') || '50');
|
||||
const filter = searchParams.get('filter') || '';
|
||||
const filter = searchParams.get('filter') || undefined;
|
||||
const sort = searchParams.get('sort') || '-created';
|
||||
|
||||
const pb = createPocketBaseClient();
|
||||
const result = await pb.collection('webhooks').getList(page, perPage, {
|
||||
filter,
|
||||
...(filter ? { filter } : {}),
|
||||
sort,
|
||||
});
|
||||
|
||||
|
||||
+19
-8
@@ -1,5 +1,4 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { createPocketBaseClient } from './pocketbase';
|
||||
|
||||
export interface AuthUser {
|
||||
id: string;
|
||||
@@ -23,13 +22,24 @@ export async function getAuthUser(request: NextRequest): Promise<AuthUser | null
|
||||
if (!token) return null;
|
||||
|
||||
try {
|
||||
const pb = createPocketBaseClient(token);
|
||||
const authData = await pb.collection('users').authRefresh();
|
||||
// Decode JWT to get user ID
|
||||
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
|
||||
const userId = payload.id;
|
||||
|
||||
// Use raw fetch with admin token to get the user record
|
||||
// (avoids PocketBase SDK authStore issues with superuser tokens)
|
||||
const adminToken = process.env.POCKETBASE_ADMIN_TOKEN || '';
|
||||
const pbUrl = process.env.POCKETBASE_URL || 'http://localhost:8090';
|
||||
const res = await fetch(pbUrl + '/api/collections/users/records/' + userId, {
|
||||
headers: { Authorization: adminToken },
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
const record = await res.json();
|
||||
|
||||
return {
|
||||
id: authData.record.id,
|
||||
email: authData.record.email,
|
||||
name: authData.record.name || authData.record.email,
|
||||
id: record.id,
|
||||
email: record.email,
|
||||
name: record.name || record.email,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
@@ -78,8 +88,9 @@ export function withAuth<T>(
|
||||
);
|
||||
}
|
||||
|
||||
console.error('[withAuth] error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: { code: 'INTERNAL_ERROR', message: 'Authentication failed' } },
|
||||
{ error: { code: 'INTERNAL_ERROR', message: 'Auth error: ' + (error instanceof Error ? error.message : String(error)) } },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
@@ -134,4 +145,4 @@ export function createErrorResponse(
|
||||
},
|
||||
{ status }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ export function createAdminClient(): PocketBase {
|
||||
const pb = new PocketBase(pocketbaseUrl);
|
||||
const adminToken = getAdminToken();
|
||||
if (adminToken) {
|
||||
pb.authStore.save(adminToken, null);
|
||||
pb.authStore.save(adminToken, { id: 'admin', email: 'admin@projecte.local', collectionId: '_superusers', collectionName: '_superusers' } as any);
|
||||
}
|
||||
return pb;
|
||||
}
|
||||
|
||||
+4
-4
@@ -7,8 +7,8 @@ services:
|
||||
# For Nginx Proxy Manager: Don't expose ports directly
|
||||
# NPM will connect via Docker network using container name
|
||||
# Only uncomment ports if you need direct access for debugging
|
||||
# ports:
|
||||
# - "3000:3000"
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- POCKETBASE_URL=http://db:8090
|
||||
@@ -38,8 +38,8 @@ services:
|
||||
container_name: project-e-db
|
||||
# For Nginx Proxy Manager: Don't expose ports directly
|
||||
# Only uncomment if you need direct PocketBase admin access
|
||||
# ports:
|
||||
# - "8090:8090"
|
||||
ports:
|
||||
- "8090:8090"
|
||||
volumes:
|
||||
- project-e-pb-data:/pb_data
|
||||
networks:
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'domains',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'color',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'icon',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'sort_order',
|
||||
type: 'number',
|
||||
options: {}
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE UNIQUE INDEX idx_domains_name ON domains (name)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('domains');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,29 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'tags',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'color',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE UNIQUE INDEX idx_tags_name ON tags (name)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('tags');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,72 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'projects',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'description',
|
||||
type: 'text',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'color',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'icon',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'domain',
|
||||
type: 'relation',
|
||||
options: { collectionId: 'domains', maxSelect: 1, cascadeDelete: false }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'status',
|
||||
type: 'select',
|
||||
options: { values: ['active', 'paused', 'archived'], maxSelect: 1 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'tags',
|
||||
type: 'relation',
|
||||
options: { collectionId: 'tags', maxSelect: 10, cascadeDelete: false }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'goal',
|
||||
type: 'text',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'deadline',
|
||||
type: 'date',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'progress',
|
||||
type: 'number',
|
||||
options: { min: 0, max: 100 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'members',
|
||||
type: 'json',
|
||||
options: {}
|
||||
})
|
||||
]),
|
||||
indexes: []
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('projects');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,79 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'project_settings',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'project_id',
|
||||
type: 'relation',
|
||||
required: true,
|
||||
options: { collectionId: 'projects', maxSelect: 1, cascadeDelete: true }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'default_priority',
|
||||
type: 'select',
|
||||
options: { values: ['low', 'medium', 'high', 'urgent'], maxSelect: 1 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'default_domain',
|
||||
type: 'relation',
|
||||
options: { collectionId: 'domains', maxSelect: 1, cascadeDelete: false }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'default_tags',
|
||||
type: 'relation',
|
||||
options: { collectionId: 'tags', maxSelect: 10, cascadeDelete: false }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'auto_archive_completed_after_days',
|
||||
type: 'number',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'default_view',
|
||||
type: 'select',
|
||||
options: { values: ['kanban', 'list'], maxSelect: 1 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'habit_reminder_time_default',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'pomodoro_focus_minutes',
|
||||
type: 'number',
|
||||
options: { min: 1, max: 120 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'pomodoro_break_minutes',
|
||||
type: 'number',
|
||||
options: { min: 1, max: 60 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'accent_color_override',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'milestone_dependency_enforcement',
|
||||
type: 'bool',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'custom_fields',
|
||||
type: 'json',
|
||||
options: {}
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE UNIQUE INDEX idx_project_settings_project_id ON project_settings (project_id)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('project_settings');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'milestones',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'description',
|
||||
type: 'text',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'project_id',
|
||||
type: 'relation',
|
||||
required: true,
|
||||
options: { collectionId: 'projects', maxSelect: 1, cascadeDelete: true }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'status',
|
||||
type: 'select',
|
||||
options: { values: ['planned', 'in_progress', 'complete'], maxSelect: 1 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'due_date',
|
||||
type: 'date',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'sort_order',
|
||||
type: 'number',
|
||||
options: {}
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE INDEX idx_milestones_project_id ON milestones (project_id)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('milestones');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'milestone_dependencies',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'milestone_id',
|
||||
type: 'relation',
|
||||
required: true,
|
||||
options: { collectionId: 'milestones', maxSelect: 1, cascadeDelete: true }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'depends_on_milestone_id',
|
||||
type: 'relation',
|
||||
required: true,
|
||||
options: { collectionId: 'milestones', maxSelect: 1, cascadeDelete: true }
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE INDEX idx_milestone_dependencies_milestone_id ON milestone_dependencies (milestone_id)',
|
||||
'CREATE INDEX idx_milestone_dependencies_depends_on ON milestone_dependencies (depends_on_milestone_id)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('milestone_dependencies');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'milestone_templates',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'description',
|
||||
type: 'text',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'milestones',
|
||||
type: 'json',
|
||||
options: {}
|
||||
})
|
||||
]),
|
||||
indexes: []
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('milestone_templates');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,44 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'milestone_history',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'milestone_id',
|
||||
type: 'relation',
|
||||
required: true,
|
||||
options: { collectionId: 'milestones', maxSelect: 1, cascadeDelete: true }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'old_status',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'new_status',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'changed_by',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'changed_at',
|
||||
type: 'date',
|
||||
options: {}
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE INDEX idx_milestone_history_milestone_id ON milestone_history (milestone_id)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('milestone_history');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,112 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'tasks',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'description',
|
||||
type: 'text',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'status',
|
||||
type: 'select',
|
||||
options: { values: ['todo', 'in_progress', 'done', 'cancelled'], maxSelect: 1 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'priority',
|
||||
type: 'select',
|
||||
options: { values: ['low', 'medium', 'high', 'urgent'], maxSelect: 1 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'due_date',
|
||||
type: 'date',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'project_id',
|
||||
type: 'relation',
|
||||
options: { collectionId: 'projects', maxSelect: 1, cascadeDelete: false }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'milestone_id',
|
||||
type: 'relation',
|
||||
options: { collectionId: 'milestones', maxSelect: 1, cascadeDelete: false }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'tags',
|
||||
type: 'relation',
|
||||
options: { collectionId: 'tags', maxSelect: 10, cascadeDelete: false }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'domain',
|
||||
type: 'relation',
|
||||
options: { collectionId: 'domains', maxSelect: 1, cascadeDelete: false }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'assignee',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'estimate',
|
||||
type: 'number',
|
||||
options: { min: 0 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'time_spent',
|
||||
type: 'number',
|
||||
options: { min: 0 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'recurring_config',
|
||||
type: 'json',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'attachments',
|
||||
type: 'json',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'dependencies',
|
||||
type: 'json',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'subtasks',
|
||||
type: 'json',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'custom_fields',
|
||||
type: 'json',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'completed_at',
|
||||
type: 'date',
|
||||
options: {}
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE INDEX idx_tasks_project_id ON tasks (project_id)',
|
||||
'CREATE INDEX idx_tasks_milestone_id ON tasks (milestone_id)',
|
||||
'CREATE INDEX idx_tasks_status ON tasks (status)',
|
||||
'CREATE INDEX idx_tasks_priority ON tasks (priority)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('tasks');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'task_subtasks',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'task_id',
|
||||
type: 'relation',
|
||||
required: true,
|
||||
options: { collectionId: 'tasks', maxSelect: 1, cascadeDelete: true }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'done',
|
||||
type: 'bool',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'sort_order',
|
||||
type: 'number',
|
||||
options: {}
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE INDEX idx_task_subtasks_task_id ON task_subtasks (task_id)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('task_subtasks');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'task_dependencies',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'task_id',
|
||||
type: 'relation',
|
||||
required: true,
|
||||
options: { collectionId: 'tasks', maxSelect: 1, cascadeDelete: true }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'depends_on_task_id',
|
||||
type: 'relation',
|
||||
required: true,
|
||||
options: { collectionId: 'tasks', maxSelect: 1, cascadeDelete: true }
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE INDEX idx_task_dependencies_task_id ON task_dependencies (task_id)',
|
||||
'CREATE INDEX idx_task_dependencies_depends_on ON task_dependencies (depends_on_task_id)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('task_dependencies');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,45 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'task_attachments',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'task_id',
|
||||
type: 'relation',
|
||||
required: true,
|
||||
options: { collectionId: 'tasks', maxSelect: 1, cascadeDelete: true }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'file',
|
||||
type: 'file',
|
||||
required: true,
|
||||
options: { maxSelect: 1, maxSize: 5242880, mimeTypes: [] }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'filename',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'mime_type',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'size',
|
||||
type: 'number',
|
||||
options: { min: 0 }
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE INDEX idx_task_attachments_task_id ON task_attachments (task_id)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('task_attachments');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'task_time_entries',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'task_id',
|
||||
type: 'relation',
|
||||
required: true,
|
||||
options: { collectionId: 'tasks', maxSelect: 1, cascadeDelete: true }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'duration',
|
||||
type: 'number',
|
||||
required: true,
|
||||
options: { min: 0 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'started_at',
|
||||
type: 'date',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'ended_at',
|
||||
type: 'date',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'source',
|
||||
type: 'select',
|
||||
options: { values: ['manual', 'timer', 'pomodoro'], maxSelect: 1 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'notes',
|
||||
type: 'text',
|
||||
options: {}
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE INDEX idx_task_time_entries_task_id ON task_time_entries (task_id)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('task_time_entries');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,119 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'habits',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'description',
|
||||
type: 'text',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'frequency',
|
||||
type: 'select',
|
||||
options: { values: ['daily', 'weekly', 'custom'], maxSelect: 1 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'cron_expression',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'target_count',
|
||||
type: 'number',
|
||||
options: { min: 1 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'streak_current',
|
||||
type: 'number',
|
||||
options: { min: 0 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'streak_best',
|
||||
type: 'number',
|
||||
options: { min: 0 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'last_completed_at',
|
||||
type: 'date',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'project_id',
|
||||
type: 'relation',
|
||||
options: { collectionId: 'projects', maxSelect: 1, cascadeDelete: false }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'tags',
|
||||
type: 'relation',
|
||||
options: { collectionId: 'tags', maxSelect: 10, cascadeDelete: false }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'domain',
|
||||
type: 'relation',
|
||||
options: { collectionId: 'domains', maxSelect: 1, cascadeDelete: false }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'difficulty',
|
||||
type: 'select',
|
||||
options: { values: ['easy', 'medium', 'hard'], maxSelect: 1 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'mood_tracking',
|
||||
type: 'bool',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'reminder_times',
|
||||
type: 'json',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'skip_days',
|
||||
type: 'json',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'unit',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'goal_per_period',
|
||||
type: 'number',
|
||||
options: { min: 0 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'score',
|
||||
type: 'number',
|
||||
options: { min: 0 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'score_config',
|
||||
type: 'json',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'completion_mode',
|
||||
type: 'select',
|
||||
options: { values: ['quick', 'detailed'], maxSelect: 1 }
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE INDEX idx_habits_project_id ON habits (project_id)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('habits');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'habit_logs',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'habit_id',
|
||||
type: 'relation',
|
||||
required: true,
|
||||
options: { collectionId: 'habits', maxSelect: 1, cascadeDelete: true }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'date',
|
||||
type: 'date',
|
||||
required: true,
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'mood',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'quantity',
|
||||
type: 'number',
|
||||
options: { min: 0 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'notes',
|
||||
type: 'text',
|
||||
options: {}
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE INDEX idx_habit_logs_habit_id ON habit_logs (habit_id)',
|
||||
'CREATE INDEX idx_habit_logs_date ON habit_logs (date)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('habit_logs');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'habit_skip_days',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'habit_id',
|
||||
type: 'relation',
|
||||
required: true,
|
||||
options: { collectionId: 'habits', maxSelect: 1, cascadeDelete: true }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'date',
|
||||
type: 'date',
|
||||
required: true,
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'reason',
|
||||
type: 'text',
|
||||
options: {}
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE INDEX idx_habit_skip_days_habit_id ON habit_skip_days (habit_id)',
|
||||
'CREATE INDEX idx_habit_skip_days_date ON habit_skip_days (date)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('habit_skip_days');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,72 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'notes',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'content',
|
||||
type: 'text',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'domain',
|
||||
type: 'relation',
|
||||
options: { collectionId: 'domains', maxSelect: 1, cascadeDelete: false }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'tags',
|
||||
type: 'relation',
|
||||
options: { collectionId: 'tags', maxSelect: 10, cascadeDelete: false }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'linked_entities',
|
||||
type: 'json',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'created_by',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'ai_generated',
|
||||
type: 'bool',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'bookmarked',
|
||||
type: 'bool',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'pinned',
|
||||
type: 'bool',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'frontmatter',
|
||||
type: 'json',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'wikilinks',
|
||||
type: 'json',
|
||||
options: {}
|
||||
})
|
||||
]),
|
||||
indexes: []
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('notes');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'note_links',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'source_note_id',
|
||||
type: 'relation',
|
||||
required: true,
|
||||
options: { collectionId: 'notes', maxSelect: 1, cascadeDelete: true }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'target_note_id',
|
||||
type: 'relation',
|
||||
required: true,
|
||||
options: { collectionId: 'notes', maxSelect: 1, cascadeDelete: true }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'link_text',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE INDEX idx_note_links_source ON note_links (source_note_id)',
|
||||
'CREATE INDEX idx_note_links_target ON note_links (target_note_id)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('note_links');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'note_task_links',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'note_id',
|
||||
type: 'relation',
|
||||
required: true,
|
||||
options: { collectionId: 'notes', maxSelect: 1, cascadeDelete: true }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'task_id',
|
||||
type: 'relation',
|
||||
required: true,
|
||||
options: { collectionId: 'tasks', maxSelect: 1, cascadeDelete: true }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'checkbox_position',
|
||||
type: 'number',
|
||||
options: {}
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE INDEX idx_note_task_links_note_id ON note_task_links (note_id)',
|
||||
'CREATE INDEX idx_note_task_links_task_id ON note_task_links (task_id)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('note_task_links');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,42 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'report_templates',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'description',
|
||||
type: 'text',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'report_type',
|
||||
type: 'select',
|
||||
options: { values: ['weekly', 'monthly', 'project', 'habit', 'custom'], maxSelect: 1 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'content_template',
|
||||
type: 'text',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'is_builtin',
|
||||
type: 'bool',
|
||||
options: {}
|
||||
})
|
||||
]),
|
||||
indexes: []
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('report_templates');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,72 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'reports',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'content',
|
||||
type: 'text',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'report_type',
|
||||
type: 'select',
|
||||
options: { values: ['weekly', 'monthly', 'project', 'habit', 'custom'], maxSelect: 1 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'date_range_start',
|
||||
type: 'date',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'date_range_end',
|
||||
type: 'date',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'generated_by',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'ai_generated',
|
||||
type: 'bool',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'linked_entities',
|
||||
type: 'json',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'domain',
|
||||
type: 'relation',
|
||||
options: { collectionId: 'domains', maxSelect: 1, cascadeDelete: false }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'tags',
|
||||
type: 'relation',
|
||||
options: { collectionId: 'tags', maxSelect: 10, cascadeDelete: false }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'template_id',
|
||||
type: 'relation',
|
||||
options: { collectionId: 'report_templates', maxSelect: 1, cascadeDelete: false }
|
||||
})
|
||||
]),
|
||||
indexes: []
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('reports');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,39 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'canvases',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'mode',
|
||||
type: 'select',
|
||||
options: { values: ['freeform', 'graph'], maxSelect: 1 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'project_id',
|
||||
type: 'relation',
|
||||
options: { collectionId: 'projects', maxSelect: 1, cascadeDelete: false }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'state',
|
||||
type: 'json',
|
||||
options: {}
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE INDEX idx_canvases_project_id ON canvases (project_id)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('canvases');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,64 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'canvas_cards',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'canvas_id',
|
||||
type: 'relation',
|
||||
required: true,
|
||||
options: { collectionId: 'canvases', maxSelect: 1, cascadeDelete: true }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'card_type',
|
||||
type: 'select',
|
||||
options: { values: ['note', 'task', 'image', 'entity'], maxSelect: 1 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'entity_id',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'entity_type',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'position_x',
|
||||
type: 'number',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'position_y',
|
||||
type: 'number',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'width',
|
||||
type: 'number',
|
||||
options: { min: 1 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'height',
|
||||
type: 'number',
|
||||
options: { min: 1 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'content',
|
||||
type: 'json',
|
||||
options: {}
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE INDEX idx_canvas_cards_canvas_id ON canvas_cards (canvas_id)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('canvas_cards');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,65 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'agents',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'api_key',
|
||||
type: 'text',
|
||||
required: true,
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'avatar',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'description',
|
||||
type: 'text',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'permission_tier',
|
||||
type: 'select',
|
||||
options: { values: ['full_access', 'read_only', 'content_creator', 'task_manager', 'custom'], maxSelect: 1 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'custom_permissions',
|
||||
type: 'json',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'webhook_url',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'last_activity_at',
|
||||
type: 'date',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'status',
|
||||
type: 'select',
|
||||
options: { values: ['active', 'disabled'], maxSelect: 1 }
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE UNIQUE INDEX idx_agents_api_key ON agents (api_key)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('agents');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,59 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'agent_activity',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'agent_id',
|
||||
type: 'relation',
|
||||
required: true,
|
||||
options: { collectionId: 'agents', maxSelect: 1, cascadeDelete: true }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'action',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'entity_type',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'entity_id',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'before_state',
|
||||
type: 'json',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'after_state',
|
||||
type: 'json',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'prompt',
|
||||
type: 'text',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'response',
|
||||
type: 'text',
|
||||
options: {}
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE INDEX idx_agent_activity_agent_id ON agent_activity (agent_id)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('agent_activity');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,43 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'webhooks',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
required: true,
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'events',
|
||||
type: 'json',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'secret',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'active',
|
||||
type: 'bool',
|
||||
options: {}
|
||||
})
|
||||
]),
|
||||
indexes: []
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('webhooks');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,54 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'webhook_deliveries',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'webhook_id',
|
||||
type: 'relation',
|
||||
required: true,
|
||||
options: { collectionId: 'webhooks', maxSelect: 1, cascadeDelete: true }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'event_type',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'payload',
|
||||
type: 'json',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'response_status',
|
||||
type: 'number',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'response_body',
|
||||
type: 'text',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'success',
|
||||
type: 'bool',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'attempts',
|
||||
type: 'number',
|
||||
options: { min: 0 }
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE INDEX idx_webhook_deliveries_webhook_id ON webhook_deliveries (webhook_id)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('webhook_deliveries');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'agent_tasks',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'agent_id',
|
||||
type: 'relation',
|
||||
required: true,
|
||||
options: { collectionId: 'agents', maxSelect: 1, cascadeDelete: true }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'entity_type',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'entity_id',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'instruction',
|
||||
type: 'text',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'status',
|
||||
type: 'select',
|
||||
options: { values: ['pending', 'in_progress', 'completed', 'failed', 'delivery_failed'], maxSelect: 1 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'result',
|
||||
type: 'json',
|
||||
options: {}
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE INDEX idx_agent_tasks_agent_id ON agent_tasks (agent_id)',
|
||||
'CREATE INDEX idx_agent_tasks_status ON agent_tasks (status)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('agent_tasks');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,49 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'notifications',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'message',
|
||||
type: 'text',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'type',
|
||||
type: 'select',
|
||||
options: { values: ['info', 'success', 'warning', 'error'], maxSelect: 1 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'entity_type',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'entity_id',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'read',
|
||||
type: 'bool',
|
||||
options: {}
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE INDEX idx_notifications_read ON notifications (read)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('notifications');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,44 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'error_logs',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'code',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'message',
|
||||
type: 'text',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'details',
|
||||
type: 'json',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'stack_trace',
|
||||
type: 'text',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'source',
|
||||
type: 'text',
|
||||
options: { maxSize: 255 }
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE INDEX idx_error_logs_code ON error_logs (code)',
|
||||
'CREATE INDEX idx_error_logs_source ON error_logs (source)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('error_logs');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
@@ -1,66 +0,0 @@
|
||||
exports = {
|
||||
up: async (app) => {
|
||||
const collection = new app.models.Collection({
|
||||
name: 'queue_jobs',
|
||||
type: 'base',
|
||||
schema: app.models.NewSchema([
|
||||
app.models.NewSchemaField({
|
||||
name: 'job_type',
|
||||
type: 'text',
|
||||
required: true,
|
||||
options: { maxSize: 255 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'payload',
|
||||
type: 'json',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'status',
|
||||
type: 'select',
|
||||
options: { values: ['pending', 'in_progress', 'completed', 'failed'], maxSelect: 1 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'attempts',
|
||||
type: 'number',
|
||||
options: { min: 0 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'max_attempts',
|
||||
type: 'number',
|
||||
options: { min: 1 }
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'error',
|
||||
type: 'text',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'scheduled_at',
|
||||
type: 'date',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'started_at',
|
||||
type: 'date',
|
||||
options: {}
|
||||
}),
|
||||
app.models.NewSchemaField({
|
||||
name: 'completed_at',
|
||||
type: 'date',
|
||||
options: {}
|
||||
})
|
||||
]),
|
||||
indexes: [
|
||||
'CREATE INDEX idx_queue_jobs_status ON queue_jobs (status)',
|
||||
'CREATE INDEX idx_queue_jobs_scheduled_at ON queue_jobs (scheduled_at)',
|
||||
'CREATE INDEX idx_queue_jobs_job_type ON queue_jobs (job_type)'
|
||||
]
|
||||
});
|
||||
return app.save(collection);
|
||||
},
|
||||
down: async (app) => {
|
||||
const collection = await app.findCollectionByNameOrId('queue_jobs');
|
||||
return app.delete(collection);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user