diff --git a/package.json b/package.json index 7b00655cd0..68bf769dc1 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@9c55755", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@e621876", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bbad65f", "@appwrite.io/pink-legacy": "^1.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 23cbb7f832..906920bf00 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^1.1.24 version: 1.1.24(svelte@5.25.3)(zod@3.24.3) '@appwrite.io/console': - specifier: https://pkg.vc/-/@appwrite/@appwrite.io/console@9c55755 - version: https://pkg.vc/-/@appwrite/@appwrite.io/console@9c55755 + specifier: https://pkg.vc/-/@appwrite/@appwrite.io/console@e621876 + version: https://pkg.vc/-/@appwrite/@appwrite.io/console@e621876 '@appwrite.io/pink-icons': specifier: 0.25.0 version: 0.25.0 @@ -272,8 +272,8 @@ packages: '@analytics/type-utils@0.6.2': resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==} - '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@9c55755': - resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/console@9c55755} + '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@e621876': + resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/console@e621876} version: 2.1.0 '@appwrite.io/pink-icons-svelte@2.0.0-RC.1': @@ -3822,7 +3822,7 @@ snapshots: '@analytics/type-utils@0.6.2': {} - '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@9c55755': {} + '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@e621876': {} '@appwrite.io/pink-icons-svelte@2.0.0-RC.1(svelte@5.25.3)': dependencies: diff --git a/src/lib/actions/analytics.ts b/src/lib/actions/analytics.ts index cfd42ae8f2..584a533fcf 100644 --- a/src/lib/actions/analytics.ts +++ b/src/lib/actions/analytics.ts @@ -52,7 +52,7 @@ const analytics = Analytics({ }); export function trackEvent(name: string, data: object = null): void { - if (!isTrackingAllowed()) { + if (!name || !isTrackingAllowed()) { return; } @@ -76,7 +76,7 @@ export function trackEvent(name: string, data: object = null): void { } export function trackError(exception: Error, event: Submit): void { - if (exception instanceof AppwriteException && exception.type) { + if (exception instanceof AppwriteException && exception.type && event) { trackEvent(Submit.Error, { type: exception.type, form: event @@ -148,11 +148,14 @@ export enum Click { ConnectRepositoryClick = 'click_connect_repository', CreditsRedeemClick = 'click_credits_redeem', CloudSignupClick = 'click_cloud_signup', + DatabaseColumnDelete = 'click_column_delete', DatabaseIndexDelete = 'click_index_delete', DatabaseTableDelete = 'click_table_delete', + DatabaseRowDelete = 'click_row_delete', DatabaseDatabaseDelete = 'click_database_delete', DatabaseImportCsv = 'click_database_import_csv', + DomainCreateClick = 'click_domain_create', DomainDeleteClick = 'click_domain_delete', DomainRetryDomainVerificationClick = 'click_domain_retry_domain_verification', @@ -279,6 +282,7 @@ export enum Submit { DatabaseUpdateName = 'submit_database_update_name', DatabaseImportCsv = 'submit_database_import_csv', DatabaseBackupDelete = 'submit_database_backup_delete', + DatabaseBackupPolicyCreate = 'submit_database_backup_policy_create', ColumnCreate = 'submit_column_create', ColumnUpdate = 'submit_column_update', @@ -289,15 +293,18 @@ export enum Submit { RowDelete = 'submit_row_delete', RowUpdate = 'submit_row_update', RowUpdatePermissions = 'submit_row_update_permissions', + IndexCreate = 'submit_index_create', IndexDelete = 'submit_index_delete', - TableCreate = 'submit_row_create', - TableDelete = 'submit_row_delete', - TableUpdateName = 'submit_row_update_name', - TableUpdatePermissions = 'submit_row_update_permissions', - TableUpdateSecurity = 'submit_row_update_security', - TableUpdateEnabled = 'submit_row_update_enabled', - TableUpdateDisplayNames = 'submit_row_update_display_names', + + TableCreate = 'submit_table_create', + TableDelete = 'submit_table_delete', + TableUpdateName = 'submit_table_update_name', + TableUpdatePermissions = 'submit_table_update_permissions', + TableUpdateSecurity = 'submit_table_update_security', + TableUpdateEnabled = 'submit_table_update_enabled', + TableUpdateDisplayNames = 'submit_table_update_display_names', + FunctionCreate = 'submit_function_create', FunctionDelete = 'submit_function_delete', FunctionUpdateName = 'submit_function_update_name', diff --git a/src/lib/components/backupRestoreBox.svelte b/src/lib/components/backupRestoreBox.svelte index 05cb96e8f1..8c4ac61af9 100644 --- a/src/lib/components/backupRestoreBox.svelte +++ b/src/lib/components/backupRestoreBox.svelte @@ -57,21 +57,22 @@ } function updateOrAddItem(payload: Payload) { - // todo: @itznotabug - might need a change to $table? - const { $id, status, $collection, policyId } = payload; - if ($collection === 'archives' && policyId !== null) { + // the internal structure still uses `$collection`, + // and is basically an identifier of the op. type here! + const { $id, status, $collection: type, policyId } = payload; + if (type === 'archives' && policyId !== null) { return; } - if ($collection in backupRestoreItems) { - const collectionMap = backupRestoreItems[$collection]; + if (type in backupRestoreItems) { + const collectionMap = backupRestoreItems[type]; if (collectionMap.has($id)) { collectionMap.get($id).status = status; if (status === 'completed') { invalidate(Dependencies.BACKUPS); - if ($collection === 'restorations') { + if (type === 'restorations') { const { newId, newName } = collectionMap.get($id).options?.['databases']?.['database'][0] || {}; @@ -81,7 +82,7 @@ } else if (status === 'pending' || status === 'processing' || status === 'uploading') { collectionMap.set($id, payload); } - backupRestoreItems[$collection] = collectionMap; + backupRestoreItems[type] = collectionMap; } } diff --git a/src/lib/helpers/faker.ts b/src/lib/helpers/faker.ts index 421288bb3b..99444f81e3 100644 --- a/src/lib/helpers/faker.ts +++ b/src/lib/helpers/faker.ts @@ -1,71 +1,88 @@ +import { sdk } from '$lib/stores/sdk'; import { faker } from '@faker-js/faker'; -import type { Columns } from '$routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store'; +import type { NestedNumberArray } from './types'; import { ID, type Models } from '@appwrite.io/console'; -import { sdk } from '$lib/stores/sdk'; import { isWithinSafeRange } from '$lib/helpers/numbers'; -import type { NestedNumberArray } from './types'; +import type { DatabaseType, Field } from '$database/(entity)'; -export async function generateColumns( +export async function generateFields( project: Models.Project, databaseId: string, - tableId: string -): Promise { + tableId: string, + databaseType: DatabaseType +): Promise { const client = sdk.forProject(project.region, project.$id); - return await Promise.all([ - client.tablesDB.createStringColumn({ - databaseId, - tableId, - key: 'name', - size: 255, - required: false - }), - client.tablesDB.createEmailColumn({ databaseId, tableId, key: 'email', required: false }), - client.tablesDB.createIntegerColumn({ - databaseId, - tableId, - key: 'age', - required: false, - min: 18, - max: 80 - }), - client.tablesDB.createStringColumn({ - databaseId, - tableId, - key: 'city', - size: 100, - required: false - }), - client.tablesDB.createStringColumn({ - databaseId, - tableId, - key: 'description', - size: 1000, - required: false - }), - client.tablesDB.createBooleanColumn({ - databaseId, - tableId, - key: 'active', - required: false - }), - client.tablesDB.createPointColumn({ - databaseId, - tableId, - key: 'location', - required: false - }), - client.tablesDB.createLineColumn({ - databaseId, - tableId, - key: 'route', - required: false - }) - ]); + switch (databaseType) { + case 'legacy': + case 'tablesdb': { + return await Promise.all([ + client.tablesDB.createStringColumn({ + databaseId, + tableId, + key: 'name', + size: 255, + required: false + }), + client.tablesDB.createEmailColumn({ + databaseId, + tableId, + key: 'email', + required: false + }), + client.tablesDB.createIntegerColumn({ + databaseId, + tableId, + key: 'age', + required: false, + min: 18, + max: 80 + }), + client.tablesDB.createStringColumn({ + databaseId, + tableId, + key: 'city', + size: 100, + required: false + }), + client.tablesDB.createStringColumn({ + databaseId, + tableId, + key: 'description', + size: 1000, + required: false + }), + client.tablesDB.createBooleanColumn({ + databaseId, + tableId, + key: 'active', + required: false + }), + client.tablesDB.createPointColumn({ + databaseId, + tableId, + key: 'location', + required: false + }), + client.tablesDB.createLineColumn({ + databaseId, + tableId, + key: 'route', + required: false + }) + ]); + } + + case 'documentsdb': /* doesn't need any fields */ + case 'vectordb': /* vector embeddings + metadata defined at collection creation */ { + /* no individual field creation needed */ + return []; + } + } } export function generateFakeRecords( - columns: Columns[], + fields: Field[], count: number ): { ids: string[]; @@ -73,7 +90,7 @@ export function generateFakeRecords( } { if (count <= 0) return { ids: [], rows: [] }; - const filteredColumns = columns.filter( + const filteredColumns = fields.filter( (col) => col.type !== 'relationship' && col.status === 'available' ); @@ -108,7 +125,7 @@ export function generateFakeRecords( }; } else { for (const column of filteredColumns) { - row[column.key] = generateValueForColumn(column); + row[column.key] = generateValueForField(column); } } @@ -140,15 +157,15 @@ function generateStringValue(key: string, maxLength: number): string { return text.length <= maxLength ? text : text.substring(0, maxLength); } -function generateValueForColumn( - column: Columns +function generateValueForField( + field: Field ): string | number | boolean | null | Array { - if (column.array) { + if (field.array) { const arraySize = faker.number.int({ min: 1, max: 5 }); const items: Array = []; for (let i = 0; i < arraySize; i++) { - const itemAttribute = { ...column, array: false }; + const itemAttribute = { ...field, array: false }; const item = generateSingleValue(itemAttribute); if (item !== null) { items.push(item); @@ -158,16 +175,14 @@ function generateValueForColumn( return items; } - return generateSingleValue(column); + return generateSingleValue(field); } -function generateSingleValue( - column: Columns -): string | number | boolean | NestedNumberArray | null { - switch (column.type) { +function generateSingleValue(field: Field): string | number | boolean | NestedNumberArray | null { + switch (field.type) { case 'string': { - if ('format' in column && column.format) { - switch (column.format) { + if ('format' in field && field.format) { + switch (field.format) { case 'email': { return faker.internet.email(); } @@ -181,7 +196,7 @@ function generateSingleValue( } case 'enum': { - const enumAttr = column as Models.ColumnEnum; + const enumAttr = field as Models.ColumnEnum; if (enumAttr.elements?.length > 0) { return faker.helpers.arrayElement(enumAttr.elements); } @@ -190,14 +205,14 @@ function generateSingleValue( } return ''; } else { - const stringAttr = column as Models.ColumnString; + const stringAttr = field as Models.ColumnString; const maxLength = Math.min(stringAttr.size ?? 255, 1000); - return generateStringValue(column.key, maxLength); + return generateStringValue(field.key, maxLength); } } case 'integer': { - const intAttr = column as Models.ColumnInteger; + const intAttr = field as Models.ColumnInteger; const min = isWithinSafeRange(intAttr.min) ? intAttr.min : 0; const fallbackMax = Math.max(min + 100, 100); const max = isWithinSafeRange(intAttr.max) @@ -207,7 +222,7 @@ function generateSingleValue( } case 'double': { - const floatAttr = column as Models.ColumnFloat; + const floatAttr = field as Models.ColumnFloat; const min = isWithinSafeRange(floatAttr.min) ? floatAttr.min : 0; const fallbackMax = Math.max(min + 100, 100); const max = isWithinSafeRange(floatAttr.max) diff --git a/src/lib/helpers/string.ts b/src/lib/helpers/string.ts index 6f867f86ce..b013f2b152 100644 --- a/src/lib/helpers/string.ts +++ b/src/lib/helpers/string.ts @@ -23,6 +23,49 @@ export function singular(str: string): string { return str.replace(/s$/, ''); } +/** + * Given a string, returns the plural version of it. + * + * Handles common English pluralization rules: + * - Words ending in consonant + y → ies + * - Words ending in vowel + y → s + * - Words ending in sibilants (s, sh, ch, x, z) → es + * - Regular words → s + * + * @export + * @param {string} str + * @returns {string} + */ +export function plural(str: string): string { + if (!str) return str; + + const lower = str.toLowerCase(); + + // Words ending in sibilants: s, sh, ch, x, z + if ( + lower.endsWith('s') || + lower.endsWith('x') || + lower.endsWith('z') || + lower.endsWith('ch') || + lower.endsWith('sh') + ) { + return str + 'es'; + } + + // Words ending in consonant + y → ies + // Words ending in vowel + y → s + if (str.endsWith('y')) { + const beforeY = str.slice(-2, -1).toLowerCase(); + if (beforeY && !['a', 'e', 'i', 'o', 'u'].includes(beforeY)) { + return str.slice(0, -1) + 'ies'; + } + return str + 's'; + } + + // Default: add 's' + return str + 's'; +} + /** * Convert a dash/underscore/space separated string to camelCase. * diff --git a/src/lib/stores/navigation.ts b/src/lib/stores/navigation.ts new file mode 100644 index 0000000000..00d165a87e --- /dev/null +++ b/src/lib/stores/navigation.ts @@ -0,0 +1,36 @@ +import { resolve } from '$app/paths'; +import { goto } from '$app/navigation'; +import type { Pathname, RouteId, RouteParams } from '$app/types'; + +// taken directly from svelte's source! +type ResolveArgs = T extends RouteId + ? RouteParams extends Record + ? [route: T] + : [route: T, params: RouteParams] + : [route: T]; + +export function withPath(base: string, ...parts: string[]) { + // remove slashes at the end if any + const normalizedBase = base.replace(/\/+$/, ''); + + // remove slashes at the start of each part if any + const normalizedParts = parts.map((part) => part.replace(/^\/+/, '')); + + // join em with slashes + return [normalizedBase, ...normalizedParts].join('/'); +} + +export function resolveRoute(route: T, params?: Record) { + // type cast is necessary here! + const resolveArgs = params ? ([route, params] as [T, RouteParams]) : [route]; + + return resolve(...(resolveArgs as ResolveArgs)); +} + +export function navigate( + route: T, + params?: Record +): Promise { + // type cast is necessary here! + return goto(resolveRoute(route, params)); +} diff --git a/src/lib/stores/sdk.ts b/src/lib/stores/sdk.ts index d7e0cc7b9d..bfe73f6080 100644 --- a/src/lib/stores/sdk.ts +++ b/src/lib/stores/sdk.ts @@ -23,6 +23,7 @@ import { Tokens, TablesDB, Domains, + DocumentsDB, Realtime, Organizations } from '@appwrite.io/console'; @@ -137,6 +138,7 @@ const sdkForProject = { migrations: new Migrations(clientProject), sites: new Sites(clientProject), tablesDB: new TablesDB(clientProject), + documentsDB: new DocumentsDB(clientProject), console: new Console(clientProject) // for suggestions API }; diff --git a/src/routes/(console)/project-[region]-[project]/databases/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/+page.svelte index adba24b050..08f06a6ad0 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/+page.svelte @@ -1,52 +1,59 @@ @@ -78,7 +85,7 @@ {:else} @@ -92,7 +99,7 @@ {:else if data.search} diff --git a/src/routes/(console)/project-[region]-[project]/databases/+page.ts b/src/routes/(console)/project-[region]-[project]/databases/+page.ts index cec0eda648..7b823fa466 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/+page.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/+page.ts @@ -7,6 +7,7 @@ import type { PageLoad, RouteParams } from './$types'; import { isSelfHosted } from '$lib/system'; import { isCloud } from '$lib/system'; import type { Plan } from '$lib/sdk/billing'; +import { useDatabaseSdk } from '$database/(entity)'; export const load: PageLoad = async ({ url, route, depends, params, parent }) => { depends(Dependencies.DATABASES); @@ -20,7 +21,7 @@ export const load: PageLoad = async ({ url, route, depends, params, parent }) => // already loaded by parent. const { currentPlan } = await parent(); - const { databases, tables, policies, lastBackups } = await fetchDatabasesAndBackups( + const { databases, entities, policies, lastBackups } = await fetchDatabasesAndBackups( limit, offset, params, @@ -33,7 +34,7 @@ export const load: PageLoad = async ({ url, route, depends, params, parent }) => limit, view, search, - tables, + entities, policies, databases, lastBackups @@ -49,24 +50,24 @@ async function fetchDatabasesAndBackups( ) { const backupsEnabled = currentPlan?.backupsEnabled ?? true; - const projectSDK = sdk.forProject(params.region, params.project); - - const databases = await projectSDK.tablesDB.list({ + const databaseSdk = useDatabaseSdk(params.region, params.project); + const databases = await databaseSdk.list({ queries: [Query.limit(limit), Query.offset(offset), Query.orderDesc('$createdAt')], search: search || undefined }); - const tables: Record = {}; + const entities: Record = {}; await Promise.all( // TODO: backend should allow `Query.select` for perf! - databases.databases.map(async ({ $id }) => { - const res = await projectSDK.tablesDB.listTables({ + databases.databases.map(async ({ $id, type }) => { + const res = await databaseSdk.listEntities({ databaseId: $id, + databaseType: type, queries: [Query.limit(1), Query.orderDesc('')] }); - tables[$id] = res.tables?.[0]?.$id ?? null; + entities[$id] = res.entities?.[0]?.$id ?? null; }) ); @@ -80,7 +81,7 @@ async function fetchDatabasesAndBackups( ]); } - return { databases, tables, policies, lastBackups }; + return { databases, entities, policies, lastBackups }; } async function fetchPolicies(databases: Models.DatabaseList, params: RouteParams) { diff --git a/src/routes/(console)/project-[region]-[project]/databases/breadcrumbs.svelte b/src/routes/(console)/project-[region]-[project]/databases/breadcrumbs.svelte index 056141e765..4202af6079 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/breadcrumbs.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/breadcrumbs.svelte @@ -1,24 +1,30 @@ diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/analytics.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/analytics.ts new file mode 100644 index 0000000000..1999b2505f --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/analytics.ts @@ -0,0 +1,51 @@ +import type { Page } from '@sveltejs/kit'; + +import { useTerminology } from './terminology'; +import { Submit, Click } from '$lib/actions/analytics'; +import type { AnalyticsResult, TerminologyResult, TerminologyShape } from './types'; + +export function useAnalytics(pageOrTerms: Page | TerminologyResult): AnalyticsResult { + // source is in `TerminologyResult`. + const terminology = 'source' in pageOrTerms ? pageOrTerms : useTerminology(pageOrTerms); + + const createSubmitHandler = (termType: keyof TerminologyShape) => { + return (action: TAction) => { + const term = terminology.source[termType]; + if (!term) { + throw new Error(`No ${termType} terminology found`); + } + const enumKey = `${term.title.singular}${action}`; + return Submit[enumKey as keyof typeof Submit]; + }; + }; + + const createClickHandler = (termType: keyof TerminologyShape) => { + return (action: TAction) => { + const term = terminology.source[termType]; + if (!term) { + throw new Error(`No ${termType} terminology found`); + } + const enumKey = `Database${term.title.singular}${action}`; + return Click[enumKey as keyof typeof Click]; + }; + }; + + const result: AnalyticsResult = { submit: {}, click: {} }; + + if (terminology.entity) { + result.click.entity = createClickHandler('entity'); + result.submit.entity = createSubmitHandler('entity'); + } + + if (terminology.field) { + result.click.field = createClickHandler('field'); + result.submit.field = createSubmitHandler('field'); + } + + if (terminology.record) { + result.click.record = createClickHandler('record'); + result.submit.record = createSubmitHandler('record'); + } + + return result; +} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/dependencies.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/dependencies.ts new file mode 100644 index 0000000000..b8467dde1a --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/dependencies.ts @@ -0,0 +1,21 @@ +import type { Page } from '@sveltejs/kit'; + +import { useTerminology } from './terminology'; +import { Dependencies } from '$lib/constants'; +import type { DependenciesResult, Term, TerminologyResult } from './types'; + +export function useDependencies(pageOrTerms: Page | TerminologyResult): DependenciesResult { + // source is in `TerminologyResult`. + const terminology = 'source' in pageOrTerms ? pageOrTerms : useTerminology(pageOrTerms); + + const getDependencies = (term: { title: Term }) => ({ + singular: Dependencies[term.title.singular.toUpperCase() as keyof typeof Dependencies], + plural: Dependencies[term.title.plural.toUpperCase() as keyof typeof Dependencies] + }); + + return { + entity: terminology.entity ? getDependencies(terminology.entity) : undefined, + field: terminology.field ? getDependencies(terminology.field) : undefined, + record: terminology.record ? getDependencies(terminology.record) : undefined + }; +} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/index.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/index.ts new file mode 100644 index 0000000000..b930cd9b8e --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/index.ts @@ -0,0 +1,6 @@ +export * from './sdk'; +export * from './init'; +export * from './types'; +export * from './analytics'; +export * from './terminology'; +export * from './dependencies'; diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/init.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/init.ts new file mode 100644 index 0000000000..bae5ea68f7 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/init.ts @@ -0,0 +1,39 @@ +import type { Page } from '@sveltejs/kit'; +import { getContext, setContext } from 'svelte'; +import { + type AnalyticsResult, + type DatabaseSdkResult, + type DependenciesResult, + type TerminologyResult, + useAnalytics, + useDependencies, + useTerminology, + useDatabaseSdk +} from '$database/(entity)'; + +const TERMINOLOGIES_KEY = Symbol('terminologies'); + +export type Terminologies = { + analytics: AnalyticsResult; + terminology: TerminologyResult; + dependencies: DependenciesResult; + databaseSdk: DatabaseSdkResult; +}; + +export function getTerminologies(): Terminologies { + return getContext(TERMINOLOGIES_KEY); +} + +export function setTerminologies(page: Page) { + setContext(TERMINOLOGIES_KEY, buildTerminologies(page)); +} + +function buildTerminologies(page: Page): Terminologies { + const terminology = useTerminology(page); + return { + terminology, + analytics: useAnalytics(terminology), + dependencies: useDependencies(terminology), + databaseSdk: useDatabaseSdk(page, terminology) + }; +} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/sdk.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/sdk.ts new file mode 100644 index 0000000000..f062a13e74 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/sdk.ts @@ -0,0 +1,119 @@ +import { sdk } from '$lib/stores/sdk'; +import type { Page } from '@sveltejs/kit'; +import type { TerminologyResult } from './types'; +import { type DatabaseType, type Entity, type EntityList, toSupportiveEntity } from './terminology'; +import type { Models } from '@appwrite.io/console'; + +export type DatabaseSdkResult = { + list: (params: { queries?: string[]; search?: string }) => Promise; + getEntity: (params: { + databaseId: string; + entityId: string; + databaseType?: DatabaseType; + }) => Promise; + listEntities: (params: { + databaseId: string; + queries?: string[]; + search?: string; + databaseType?: DatabaseType; + }) => Promise; + delete: (params: { databaseId: string; databaseType?: DatabaseType }) => Promise<{}>; +}; + +export function useDatabaseSdk( + regionOrPage: string | Page, + projectOrTerminology: string | TerminologyResult, + databaseType?: DatabaseType /* nullable for use at top `databases` level */ +): DatabaseSdkResult { + let region: string; + let project: string; + let type: DatabaseType; + + if (typeof regionOrPage === 'object' && typeof projectOrTerminology === 'object') { + type = projectOrTerminology.type; + region = regionOrPage?.params?.region || ''; + project = regionOrPage?.params?.project || ''; + } else { + type = databaseType!; + region = regionOrPage as string; + project = projectOrTerminology as string; + } + + const baseSdk = sdk.forProject(region, project); + + return { + async list(params): Promise { + const results = await Promise.all([ + baseSdk.tablesDB.list(params) + + // not available just yet! + // baseSdk.documentsDB.list(params), + ]); + + return results.reduce( + (acc, curr) => ({ + total: acc.total + curr.total, + databases: [...acc.databases, ...curr.databases] + }), + { total: 0, databases: [] as Models.Database[] } + ); + }, + + async listEntities(params) { + switch (type ?? params.databaseType) { + case 'legacy': /* databases api */ + case 'tablesdb': { + const { total, tables } = await baseSdk.tablesDB.listTables(params); + return { total, entities: tables.map(toSupportiveEntity) }; + } + case 'documentsdb': { + const { total, collections } = + await baseSdk.documentsDB.listCollections(params); + return { total, entities: collections.map(toSupportiveEntity) }; + } + case 'vectordb': + throw new Error(`Database type not supported yet`); + default: + throw new Error(`Unknown database type`); + } + }, + + async getEntity(params) { + switch (type ?? params.databaseType) { + case 'legacy': /* databases api */ + case 'tablesdb': { + const table = await baseSdk.tablesDB.getTable({ + databaseId: params.databaseId, + tableId: params.entityId + }); + return toSupportiveEntity(table); + } + case 'documentsdb': { + const table = await baseSdk.documentsDB.getCollection({ + databaseId: params.databaseId, + collectionId: params.entityId + }); + return toSupportiveEntity(table); + } + case 'vectordb': + throw new Error(`Database type not supported yet`); + default: + throw new Error(`Unknown database type`); + } + }, + + async delete(params) { + switch (type ?? params.databaseType) { + case 'legacy': /* databases api */ + case 'tablesdb': + return await baseSdk.tablesDB.delete(params); + case 'documentsdb': + return await baseSdk.documentsDB.delete(params); + case 'vectordb': + throw new Error(`Database type not supported yet`); + default: + throw new Error(`Unknown database type`); + } + } + }; +} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/terminology.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/terminology.ts new file mode 100644 index 0000000000..6fff779c4e --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/terminology.ts @@ -0,0 +1,127 @@ +import type { Page } from '@sveltejs/kit'; + +import { capitalize, plural } from '$lib/helpers/string'; +import { AppwriteException, type Models } from '@appwrite.io/console'; +import type { Attributes, Columns, Table } from '$database/table-[table]/store'; +import type { Term, TerminologyResult, TerminologyShape } from '$database/(entity)/helpers/types'; + +export type DatabaseType = 'legacy' | 'tablesdb' | 'documentsdb' | 'vectordb'; + +export type Entity = Partial & { + indexes?: Index[]; + fields?: (Attributes | Columns)[]; + recordSecurity?: Models.Collection['documentSecurity'] | Models.Table['rowSecurity']; +}; + +export type Field = Partial | Partial; + +export type Index = Partial & { + fields: Models.Index['attributes'] | Models.ColumnIndex['columns']; +}; + +export type EntityList = { + total: number; + entities: Entity[]; +}; + +export const baseTerminology = { + /** + * this is no longer used on console so + * we don't really show old terminology for older databases and, + * therefore we use the new routes, terms and sdk for these databases. + */ + legacy: { + entity: 'table', + field: 'column', + record: 'row' + }, + tablesdb: { + entity: 'table', + field: 'column', + record: 'row' + }, + documentsdb: { + entity: 'collection', + field: 'attribute', + record: 'document' + }, + vectordb: {} +} as const; + +const createTerm = (singular: string, pluralForm: string): Term => { + return { singular, plural: pluralForm }; +}; + +// transforms a base into lower/title variants +const createTermVariants = (baseTerm: string) => ({ + lower: createTerm(baseTerm, plural(baseTerm)), + title: createTerm(capitalize(baseTerm), plural(capitalize(baseTerm))) +}); + +// transforms terminology for a database type +const transformDatabaseTerms = (terms: Partial) => + Object.fromEntries( + Object.entries(terms).map(([key, term]) => [ + key, + term ? createTermVariants(term) : undefined + ]) + ); + +// build the terminology data +const terminologyData = Object.fromEntries( + Object.entries(baseTerminology).map(([dbType, terms]) => [ + dbType, + transformDatabaseTerms(terms) + ]) +); + +const toIndex = (index: Models.Index | Models.ColumnIndex): Index => ({ + ...index, + fields: (index as Models.Index).attributes ?? (index as Models.ColumnIndex).columns ?? [] +}); + +/** + * Transforms a raw `Collection` / `Table` model to normalized `Entity`. + */ +export function toSupportiveEntity(raw: Models.Collection | Models.Table): Entity { + const isTable = 'columns' in raw; + const indexes = raw.indexes?.map(toIndex) ?? []; + + const fields = isTable ? raw.columns : raw.attributes; + const recordSecurity = isTable ? raw.rowSecurity : raw.documentSecurity; + + return { + ...raw, + fields, + recordSecurity, + indexes + } as Entity; +} + +export function toRelationalField(raw: Field): Columns { + return raw as Columns; +} + +/** + * @internal + * Use `getTerminologies()` instead when in `database-[database]` routes where context is available. + */ +export function useTerminology(pageOrType: Page | DatabaseType): TerminologyResult { + const type = + typeof pageOrType === 'object' + ? (pageOrType.data?.database?.type as DatabaseType) + : pageOrType; + if (!type) { + // strict check because this should always be available! + throw new AppwriteException('Database type is required', 500); + } + + const dbTerminologies = terminologyData[type] || {}; + return { + type, + source: dbTerminologies, + field: dbTerminologies.field, + record: dbTerminologies.record, + entity: dbTerminologies.entity + }; +} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/types.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/types.ts new file mode 100644 index 0000000000..639c7412d7 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/types.ts @@ -0,0 +1,67 @@ +import { Dependencies } from '$lib/constants'; +import { Click, Submit } from '$lib/actions/analytics'; +import { baseTerminology, type DatabaseType } from './terminology'; + +export type TerminologyShape = { + entity: string; + field?: string; + record?: string; +}; + +export type Term = { singular: string; plural: string }; + +export type TerminologyResult = { + type: DatabaseType; + source: { + entity?: { lower: Term; title: Term }; + field?: { lower: Term; title: Term }; + record?: { lower: Term; title: Term }; + }; + entity: { lower: Term; title: Term }; + field?: { lower: Term; title: Term }; + record?: { lower: Term; title: Term }; +}; + +// for derived analytics! +type ExtractActionsForPrefix = { + [K in keyof typeof Submit]: K extends `${TPrefix}${infer Action}` ? Action : never; +}[keyof typeof Submit]; + +type ExtractClickActionsForPrefix = { + [K in keyof typeof Click]: K extends `Database${TPrefix}${infer Action}` ? Action : never; +}[keyof typeof Click]; + +type TermValuesForKey = { + [K in keyof typeof baseTerminology]: TKey extends keyof (typeof baseTerminology)[K] + ? (typeof baseTerminology)[K][TKey] + : never; +}[keyof typeof baseTerminology]; + +type SubmitActionsFor = ExtractActionsForPrefix< + Capitalize> +>; + +type ClickActionsFor = ExtractClickActionsForPrefix< + Capitalize> +>; + +export type AnalyticsResult = { + submit: { + entity?: (action: SubmitActionsFor<'entity'>) => Submit; + field?: (action: SubmitActionsFor<'field'>) => Submit; + record?: (action: SubmitActionsFor<'record'>) => Submit; + }; + click: { + entity?: (action: ClickActionsFor<'entity'>) => Click; + field?: (action: ClickActionsFor<'field'>) => Click; + record?: (action: ClickActionsFor<'record'>) => Click; + }; +}; + +// for derived dependencies! +export type DependenciesResult = { + [K in keyof Omit]: { + singular: Dependencies; + plural: Dependencies; + }; +}; diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/index.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/index.ts new file mode 100644 index 0000000000..8437673d36 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/index.ts @@ -0,0 +1,12 @@ +export * from './helpers'; +export * from './views/field'; +export * from './views/indexes'; +export * from './views/layouts'; +export * from './views/settings'; + +export { default as Header } from './views/header.svelte'; +export { default as Breadcrumbs } from './views/breadcrumbs.svelte'; + +export { default as Usage } from './views/usage/view.svelte'; +export { default as CreateEntity } from './views/create.svelte'; +export { default as FailedModal } from './views/failedModal.svelte'; diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/breadcrumbs.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/breadcrumbs.svelte new file mode 100644 index 0000000000..234482f610 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/breadcrumbs.svelte @@ -0,0 +1,44 @@ + + + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/create.svelte similarity index 50% rename from src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte rename to src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/create.svelte index 78f6923444..c8830a36e9 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/create.svelte @@ -1,71 +1,78 @@ - + { if (!touchedId) { touchedId = true; } }} /> - + {#if useSuggestions} + + {/if} - - + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/failedModal.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/failedModal.svelte new file mode 100644 index 0000000000..44914f7bab --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/failedModal.svelte @@ -0,0 +1,25 @@ + + + + {error} + + + + + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/field/csvDisabled.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/field/csvDisabled.svelte new file mode 100644 index 0000000000..9a37954a8c --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/field/csvDisabled.svelte @@ -0,0 +1,16 @@ + + + +
{@render children()}
+ +
This action is disabled during import.
+
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/field/index.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/field/index.ts new file mode 100644 index 0000000000..dfe3a26079 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/field/index.ts @@ -0,0 +1 @@ +export { default as CsvDisabled } from './csvDisabled.svelte'; diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/header.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/header.svelte new file mode 100644 index 0000000000..8258a787cd --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/header.svelte @@ -0,0 +1,128 @@ + + +
+ + + + + {entity.name} + + + {#key entity.$id} + + {entity.$id} + + {/key} + + + +
+ + {#each tabs as tab} + + {tab.title} + + {/each} + +
+
+
+ + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/createIndex.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/indexes/create.svelte similarity index 59% rename from src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/createIndex.svelte rename to src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/indexes/create.svelte index f706ab7395..bd8d8984a2 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/createIndex.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/indexes/create.svelte @@ -1,5 +1,14 @@ + + @@ -189,10 +222,12 @@ bind:value={key} autofocus /> - + - {#each columnList as column, index} + {@const fieldType = terminology.field.title.singular} + {@const fieldTypeLower = terminology.field.lower.singular} + {#each fieldList as field, index} {@const direction = $isSmallViewport ? 'column' : 'row'} + id={`field-${index}`} + label={index === 0 ? fieldType : undefined} + placeholder="Select {fieldType}" + bind:value={field.value} /> {#if selectedType === IndexType.Key} @@ -234,7 +269,7 @@ id={`length-${index}`} label={index === 0 ? 'Length' : undefined} placeholder="Enter length" - bind:value={column.length} /> + bind:value={field.length} /> {/if} {#if $isSmallViewport} @@ -242,9 +277,9 @@ @@ -255,9 +290,9 @@ icon size="s" secondary - disabled={columnList.length <= 1} + disabled={fieldList.length <= 1} on:click={() => { - columnList = remove(columnList, index); + fieldList = remove(fieldList, index); }}> @@ -266,9 +301,9 @@ {/each}
-
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/deleteIndex.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/indexes/delete.svelte similarity index 53% rename from src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/deleteIndex.svelte rename to src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/indexes/delete.svelte index 5916d398aa..a1f0d819fe 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/deleteIndex.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/indexes/delete.svelte @@ -1,53 +1,57 @@ + -{#if selectedIndex?.columns?.length} - {#each selectedIndex.columns as column, i} +{#if selectedIndex?.fields?.length} + {#each selectedIndex.fields as field, i} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/indexes/view.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/indexes/view.svelte new file mode 100644 index 0000000000..032575c19e --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/indexes/view.svelte @@ -0,0 +1,369 @@ + + + + + {#if $canWriteTables} + + {/if} + + + +
+ {#if entity.fields?.length} + {#if entity.indexes.length} + + (showCreateIndex = true)} + on:columnsResize={(resize) => saveColumnsWidth(resize.detail)}> + + Key + Type + {terminology.field.title.singular} + + Lengths + + + + {#each entity.indexes as index (index.key)} + + + + {index.key} + {#if index.status !== 'available'} + + {#if index.error} + { + e.preventDefault(); + error = index.error; + showFailed = true; + }}>Details + {/if} + {/if} + + + {index.type} + + {index.fields.join(', ')} + + + + {index.lengths} + + + + + + { + toggle(); + selectedIndex = index; + showOverview = true; + }}>Overview + +
+ +
+ + { + toggle(); + showDelete = true; + selectedIndex = index; + trackEvent(Click.DatabaseIndexDelete); + }}>Delete +
+
+
+
+ {/each} + + + + + {@const length = entity.indexes.length} + {length} + {length === 1 ? 'index' : 'indexes'} + + + +
+
+ {:else} + {@render emptyIndexesSheetView(() => (showCreateIndex = true))} + {/if} + {:else} + {@render emptyEntitiesSheetView?.()} + {/if} + + {#if selectedIndexes.length > 0} +
+ + +
+ + + + {selectedIndexes.length > 1 ? 'indexes' : 'index'} + selected + + +
+
+ + + + +
+
+ {/if} +
+ + await createIndex.create() + }}> + + + +{#if selectedIndex} + +{:else if selectedIndexes && selectedIndexes.length} + +{/if} + + + + + + + + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/emptySheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/layouts/empty.svelte similarity index 95% rename from src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/emptySheet.svelte rename to src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/layouts/empty.svelte index c77dd08d82..640bb39351 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/emptySheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/layouts/empty.svelte @@ -11,27 +11,21 @@ import { isSmallViewport, isTabletViewport } from '$lib/stores/viewport'; import { SortButton } from '$lib/components'; import type { Column } from '$lib/helpers/types'; - import { - tableColumns, - columnsOrder, - showCreateColumnSheet, - spreadsheetLoading, - expandTabs - } from '../store'; - import SpreadsheetContainer from './spreadsheet.svelte'; + import { SpreadsheetContainer } from '$database/(entity)'; import { onDestroy, onMount, type Snippet } from 'svelte'; import { debounce } from '$lib/helpers/debounce'; - import { columnOptions } from '../columns/store'; + import { expandTabs, spreadsheetLoading } from '$database/table-[table]/store'; type Mode = 'rows' | 'rows-filtered' | 'indexes'; const { mode, - customColumns = [], title, - subtitle, actions, - showActions + subtitle, + showActions = true, + customColumns = [], + onOpenCreateColumn } = $props<{ mode: Mode; customColumns?: Column[]; @@ -39,6 +33,7 @@ subtitle?: Snippet; actions?: Snippet; showActions?: boolean; + onOpenCreateColumn?: () => Promise | void; }>(); let spreadsheetContainer: HTMLElement; @@ -116,7 +111,6 @@ customColumns.map((col: Column) => ({ ...col, hide: false, - icon: columnOptions.find((colOpt) => colOpt.type === col?.type)?.icon, ...baseColProps })); @@ -272,10 +266,7 @@ variant="extra-compact" onclick={() => { if (mode === 'rows') { - $showCreateColumnSheet.show = true; - $showCreateColumnSheet.title = 'Create column'; - $showCreateColumnSheet.columns = $tableColumns; - $showCreateColumnSheet.columnsOrder = $columnsOrder; + onOpenCreateColumn?.(); } }}> diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/emptySheetCards.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/layouts/emptySheetCards.svelte similarity index 100% rename from src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/emptySheetCards.svelte rename to src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/layouts/emptySheetCards.svelte diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/layouts/index.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/layouts/index.ts new file mode 100644 index 0000000000..36dbcca040 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/layouts/index.ts @@ -0,0 +1,4 @@ +export { default as EmptySheet } from './empty.svelte'; +export { default as SideSheet } from './sidesheet.svelte'; +export { default as EmptySheetCards } from './emptySheetCards.svelte'; +export { default as SpreadsheetContainer } from './spreadsheet.svelte'; diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/sidesheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/layouts/sidesheet.svelte similarity index 98% rename from src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/sidesheet.svelte rename to src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/layouts/sidesheet.svelte index afe51be679..c0b525c612 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/sidesheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/layouts/sidesheet.svelte @@ -57,9 +57,9 @@ let submitting = $state(writable(false)); let copyText = $state(undefined); - beforeNavigate(() => { - show = false; - }); + + // hide on a nav trigger! + beforeNavigate(() => (show = false));
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/spreadsheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/layouts/spreadsheet.svelte similarity index 98% rename from src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/spreadsheet.svelte rename to src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/layouts/spreadsheet.svelte index 185f2aec78..30483eb680 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/spreadsheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/layouts/spreadsheet.svelte @@ -1,6 +1,6 @@ + + + Delete {type} + The {type} will be permanently deleted, including all the {records} within it. This action is irreversible. + + + +
{entity.name}
+
+

Last updated: {toLocaleDateTime(entity.$updatedAt)}

+
+
+ + + + +
+ +{#if show} + + + Are you sure you want to delete {entity.name}? + + +{/if} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/settings/index.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/settings/index.ts new file mode 100644 index 0000000000..24fe556518 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/settings/index.ts @@ -0,0 +1,5 @@ +export { default as DangerZone } from './danger.svelte'; +export { default as UpdateName } from './name.svelte'; +export { default as UpdatePermissions } from './permissions.svelte'; +export { default as UpdateSecurity } from './security.svelte'; +export { default as UpdateStatus } from './status.svelte'; diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/settings/name.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/settings/name.svelte new file mode 100644 index 0000000000..ac96aee5df --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/settings/name.svelte @@ -0,0 +1,60 @@ + + +
+ + Name + + + + + + + + + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/settings/permissions.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/settings/permissions.svelte new file mode 100644 index 0000000000..90534375c8 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/settings/permissions.svelte @@ -0,0 +1,67 @@ + + + + Permissions + Choose who can access your {type} and {records}. + Learn more + . + + {#if entityPermissions} + + {/if} + + + + + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/settings/security.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/settings/security.svelte new file mode 100644 index 0000000000..112aa6deb3 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/settings/security.svelte @@ -0,0 +1,66 @@ + + + + {title} security + + + +

+ When {recordLower} security is enabled, users will be able to access {recordsLower} + for which they have been granted + either {recordLower} or {entityLower} permissions. +

+

+ If {recordLower} security is disabled, users can access {recordsLower} + only if they have {entityLower} permissions. {title} permissions will be ignored. +

+
+ + + +
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/settings/status.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/settings/status.svelte new file mode 100644 index 0000000000..44cae7b7d7 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/settings/status.svelte @@ -0,0 +1,60 @@ + + + + {entity.name} + +
    + +
+
+

Created: {toLocaleDateTime(entity.$createdAt)}

+

Last updated: {toLocaleDateTime(entity.$updatedAt)}

+
+
+ + + + +
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/usage/view.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/usage/view.svelte new file mode 100644 index 0000000000..c47c8da008 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/usage/view.svelte @@ -0,0 +1,47 @@ + + +
+ + + +
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte index 00347261ff..b7b90e70c6 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte @@ -17,7 +17,7 @@ import { SortButton } from '$lib/components'; import { expandTabs, columnsOrder, columnsWidth, reorderItems } from '../table-[table]/store'; import { preferences } from '$lib/stores/preferences'; - import SpreadsheetContainer from '../table-[table]/layout/spreadsheet.svelte'; + import { SpreadsheetContainer } from '$database/(entity)'; import { onDestroy, onMount, tick } from 'svelte'; import { sdk, realtime, type RealtimeResponse } from '$lib/stores/sdk'; import { page } from '$app/state'; diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/indexes.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/indexes.svelte index 85158b884d..836ea3b9c1 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/indexes.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/indexes.svelte @@ -9,11 +9,11 @@ type SuggestedIndexSchema } from './store'; import { Modal } from '$lib/components'; - import SideSheet from '../table-[table]/layout/sidesheet.svelte'; + import { type Entity, SideSheet } from '$database/(entity)'; import { isSmallViewport } from '$lib/stores/viewport'; - import { IndexType, type Models } from '@appwrite.io/console'; + import { IndexType } from '@appwrite.io/console'; import { capitalize } from '$lib/helpers/string'; - import { type Columns, table } from '../table-[table]/store'; + import { type Columns } from '../table-[table]/store'; import { isRelationship } from '../table-[table]/rows/store'; import { VARS } from '$lib/system'; import { sleep } from '$lib/helpers/promises'; @@ -26,6 +26,12 @@ import { type ComponentType, onDestroy, onMount } from 'svelte'; import { columnOptions as baseColumnOptions } from '../table-[table]/columns/store'; + const { + table + }: { + table: Entity; + } = $props(); + const MAX_INDEXES = 5; let modalError = $state(null); @@ -38,9 +44,6 @@ leadingIcon?: ComponentType; }> = $state(); - const tableId = page.params.table; - const databaseId = page.params.database; - function makeColumnOptions() { if (VARS.MOCK_AI_SUGGESTIONS) { columnOptions = mockSuggestions.columns.map((column) => ({ @@ -49,8 +52,8 @@ leadingIcon: baseColumnOptions.find((option) => option.type === column.type)?.icon })); } else { - columnOptions = $table.columns - .filter((column) => !isRelationship(column)) + columnOptions = table.fields + .filter((column: Columns) => !isRelationship(column)) .map((column) => ({ value: column.key, label: column.key, @@ -70,7 +73,7 @@ indexes = mockSuggestions.columns.slice(0, 3).map((column, index) => ({ key: column.name, type: IndexType.Key, - columns: [column.name], + fields: [column.name], orders: index === 2 ? IndexOrder.DESC : IndexOrder.ASC, lengths: [] })); @@ -79,8 +82,8 @@ const suggestions = await sdk .forProject(page.params.region, page.params.project) .console.suggestIndexes({ - databaseId, - tableId: $table.$id + databaseId: table.databaseId, + tableId: table.$id }); indexes = suggestions.indexes.map((index) => { @@ -88,7 +91,7 @@ key: index.columns[0], type: index.type as IndexType, orders: (index.orders?.[0] as IndexOrder) || IndexOrder.ASC, - columns: index.columns, + fields: index.columns, lengths: index.lengths ?? [] }; }); @@ -116,7 +119,7 @@ key: '', type: IndexType.Key, orders: IndexOrder.ASC, - columns: [], + fields: [], lengths: null }); } @@ -129,9 +132,9 @@ function syncIndexState(event: CustomEvent, index: SuggestedIndexSchema) { const selected = event.detail; index.key = selected; - index.columns = selected ? [selected] : []; + index.fields = selected ? [selected] : []; if (index.lengths) { - index.lengths = index.lengths.slice(0, index.columns.length); + index.lengths = index.lengths.slice(0, index.fields.length); } } @@ -146,8 +149,8 @@ } function generateUniqueIndexKey(index: SuggestedIndexSchema, usedKeys: Set): string { - const existingKeys = $table.indexes.map((idx) => idx.key); - let suggestedKey = `${index.key || index.columns[0]}_${index.type.toLowerCase()}`; + const existingKeys = table.indexes.map((idx) => idx.key); + let suggestedKey = `${index.key || index.fields[0]}_${index.type.toLowerCase()}`; let uniqueKey = suggestedKey; let counter = 1; @@ -160,25 +163,20 @@ return uniqueKey; } - function prepareIndexForCreation(index: SuggestedIndexSchema, columnMap: Map) { + function prepareIndexForCreation(index: SuggestedIndexSchema, columnMap: Map) { // prepare orders array - const orders = index.orders !== null ? index.columns.map(() => String(index.orders)) : []; + const orders = index.orders !== null ? index.fields.map(() => String(index.orders)) : []; // prepare lengths array let lengths: (number | null)[]; if (index.type === IndexType.Key) { // only validate if it's a key index - lengths = index.columns.map((columnKey, i) => { - const column = columnMap.get(columnKey); - if (column?.type === 'string') { - const stringColumn = column as Models.ColumnString; + lengths = index.fields.map((columnKey, i) => { + const maxSize = columnMap.get(columnKey); + if (maxSize) { const requestedLength = index.lengths?.[i]; - if ( - requestedLength && - stringColumn.size && - requestedLength > stringColumn.size - ) { - return stringColumn.size; + if (requestedLength && requestedLength > maxSize) { + return maxSize; } return requestedLength || null; } @@ -186,7 +184,7 @@ }); } else { // non-key indexes, lengths are null! - lengths = Array(index.columns.length).fill(null); + lengths = Array(index.fields.length).fill(null); } return { orders, lengths }; @@ -213,7 +211,7 @@ creatingIndexes = true; for (const [i, index] of indexes.entries()) { - if (!index.key || !index.type || !index.columns || index.columns.length === 0) { + if (!index.key || !index.type || !index.fields || index.fields.length === 0) { modalError = `Index ${i + 1}: Selected column or type invalid`; creatingIndexes = false; return true; // keep sheet open! @@ -222,7 +220,12 @@ let successCount = 0; const usedKeys = new Set(); - const columnMap = new Map($table.columns.map((col) => [col.key, col])); + const columnMap: Map = new Map( + table.fields + .filter((field) => field.type === 'string' && 'size' in field) + .map((field) => [field.key, field['size']]) + ); + const sdkClient = sdk.forProject(page.params.region, page.params.project); for (const [_, index] of indexes.entries()) { @@ -234,11 +237,11 @@ const uniqueIndexKey = generateUniqueIndexKey(index, usedKeys); await sdkClient.tablesDB.createIndex({ - databaseId, - tableId, + databaseId: table.databaseId, + tableId: table.$id, key: uniqueIndexKey, type: index.type, - columns: index.columns, + columns: index.fields, lengths, ...(orders.length ? { orders } : {}) }); diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/input.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/input.svelte index 70dec29632..1ec1f395bf 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/input.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/input.svelte @@ -4,6 +4,7 @@ import IconAI from './icon/ai.svelte'; import { slide } from 'svelte/transition'; import { tableColumnSuggestions } from './store'; + import { getTerminologies } from '$database/(entity)'; import { Button, InputTextarea } from '$lib/elements/forms'; import { Card, Layout, Selector, Typography } from '@appwrite.io/pink-svelte'; @@ -20,19 +21,22 @@ }); const featureActive = $derived(isCloud); + const { terminology } = getTerminologies(); + const field = terminology.field.lower; + const entity = terminology.entity.lower.singular; const title = $derived.by(() => { return featureActive - ? 'Smart column suggestions' - : 'Smart column suggestions available on Cloud'; + ? `Smart ${field.singular} suggestions` + : `Smart ${field.singular} suggestions available on Cloud`; }); const subtitle = $derived.by(() => { return featureActive ? isModal - ? 'Use AI to suggest useful columns' - : 'Enable AI to suggest useful columns based on your table name' - : 'Sign up for Cloud to generate columns based on your table name'; + ? `Use AI to suggest useful ${field.plural}` + : `Enable AI to suggest useful ${field.plural} based on your ${entity} name` + : `Sign up for Cloud to generate ${field.plural} based on your ${entity} name`; }); diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/options.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/options.svelte index 48ae977ce0..cce941a2ef 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/options.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/options.svelte @@ -2,7 +2,7 @@ import type { Snippet } from 'svelte'; import { Popover, Tooltip } from '@appwrite.io/pink-svelte'; import { isSmallViewport } from '$lib/stores/viewport'; - import SideSheet from '../table-[table]/layout/sidesheet.svelte'; + import { SideSheet } from '$database/(entity)'; let { children, diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/store.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/store.ts index c27e09eaeb..0e8daa0362 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/store.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/store.ts @@ -40,7 +40,7 @@ export type SuggestedIndexSchema = { key: string; type: IndexType; orders: IndexOrder; - columns: string[]; + fields: string[]; lengths?: number[] | undefined; }; diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+layout.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+layout.svelte index fe77a776bd..b11e83f6ef 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+layout.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+layout.svelte @@ -10,8 +10,7 @@ } from '$lib/commandCenter'; import { tablesSearcher } from '$lib/commandCenter/searchers'; import { Dependencies } from '$lib/constants'; - import CreateTable from './createTable.svelte'; - import { showCreateTable } from './store'; + import { showCreateEntity } from './store'; import { TablesPanel } from '$lib/commandCenter/panels'; import { canWriteTables, canWriteDatabases } from '$lib/stores/roles'; import { showCreateBackup, showCreatePolicy } from './backups/store'; @@ -19,6 +18,8 @@ import { currentPlan } from '$lib/stores/organization'; import { isCloud } from '$lib/system'; import { noWidthTransition } from '$lib/stores/sidebar'; + import { CreateEntity, setTerminologies } from '$database/(entity)'; + import { sdk } from '$lib/stores/sdk'; const project = page.params.project; const databaseId = page.params.database; @@ -27,7 +28,7 @@ { label: 'Create table', callback() { - $showCreateTable = true; + $showCreateEntity = true; if (!page.url.pathname.endsWith(databaseId)) { goto( `${base}/project-${page.params.region}-${project}/databases/database-${databaseId}` @@ -135,6 +136,23 @@ $: $updateCommandGroupRanks({ tables: 10 }); $noWidthTransition = true; + + async function createEntity(tableId: string, name: string) { + const table = await sdk + .forProject(page.params.region, page.params.project) + .tablesDB.createTable({ + databaseId, + tableId, + name + }); + + await invalidate(Dependencies.DATABASE); + await goto( + `${base}/project-${page.params.region}-${project}/databases/database-${databaseId}/table-${table.$id}` + ); + } + + $: setTerminologies(page); @@ -146,11 +164,4 @@ - { - await invalidate(Dependencies.DATABASE); - await goto( - `${base}/project-${page.params.region}-${project}/databases/database-${databaseId}/table-${table.$id}` - ); - }} /> + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+layout.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+layout.ts index ac97110f0e..49b60c099b 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+layout.ts @@ -13,9 +13,9 @@ export const load: LayoutLoad = async ({ params, depends }) => { }); return { + database, header: Header, breadcrumbs: Breadcrumbs, - subNavigation: SubNavigation, - database + subNavigation: SubNavigation }; }; diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+page.svelte index 8234805e3a..90066c9f96 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+page.svelte @@ -2,20 +2,38 @@ import { EmptySearch, PaginationWithLimit, SearchQuery, ViewSelector } from '$lib/components'; import { Button } from '$lib/elements/forms'; import { Container } from '$lib/layout'; - import { showCreateTable, tableViewColumns } from './store'; + import { showCreateEntity, tableViewColumns } from './store'; import Table from './table.svelte'; import Grid from './grid.svelte'; - import type { PageData } from './$types'; + import type { PageProps } from './$types'; import { Card, Empty, Icon, Layout } from '@appwrite.io/pink-svelte'; - import { base } from '$app/paths'; import { app } from '$lib/stores/app'; import { canWriteTables } from '$lib/stores/roles'; import { IconPlus } from '@appwrite.io/pink-icons-svelte'; import { page } from '$app/state'; + import { resolveRoute } from '$lib/stores/navigation'; + import { getTerminologies } from '$database/(entity)'; + import { withPath } from '$lib/stores/navigation.js'; - export let data: PageData; + const { data }: PageProps = $props(); - const databaseId = page.params.database; + const { terminology } = getTerminologies(); + const entityTitle = terminology.entity.title; + const entityLower = terminology.entity.lower; + + /** + * init update because `getContext` + * doesn't work on typescript context! + */ + tableViewColumns.update((columns) => { + /* $id */ + columns[0].title = `${entityTitle.singular} ID`; + return columns; + }); + + function getImageRoute(type: 'light' | 'dark'): string { + return withPath(resolveRoute('/'), `/images/empty-database-${type}.svg`); + } @@ -29,11 +47,11 @@ ui="new" view={data.view} columns={tableViewColumns} - hideColumns={!data.tables.total} - hideView={!data.tables.total} /> + hideColumns={!data.entities.total} + hideView={!data.entities.total} /> {#if $canWriteTables} - @@ -41,51 +59,48 @@ - {#if data.tables.total} + {#if data.entities.total} {#if data.view === 'grid'} - + {:else} -
+
{/if} + total={data.entities.total} + name={entityTitle.plural} /> {:else if data.search} - + + href={resolveRoute( + '/(console)/project-[region]-[project]/databases/database-[database]', + page.params + )}>Clear Search {:else} + src={getImageRoute($app.themeInUse)} + title="Create your first {entityLower.singular}"> - Create, organize, and query structured data with Tables. + Create, organize, and query structured data with {entityTitle.plural}. + + ariaLabel="create {entityLower.singular}">Documentation {#if $canWriteTables} - {/if} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+page.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+page.ts index 475ed13fee..358f8d97ee 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+page.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+page.ts @@ -1,10 +1,11 @@ import { Query } from '@appwrite.io/console'; -import { sdk } from '$lib/stores/sdk'; import { getLimit, getPage, getSearch, getView, pageToOffset, View } from '$lib/helpers/load'; import type { PageLoad } from './$types'; import { CARD_LIMIT, Dependencies } from '$lib/constants'; +import { type DatabaseType, useDatabaseSdk } from '$database/(entity)'; -export const load: PageLoad = async ({ params, url, route, depends }) => { +export const load: PageLoad = async ({ params, url, route, depends, parent }) => { + const { database } = await parent(); depends(Dependencies.TABLES); const page = getPage(url); @@ -13,10 +14,13 @@ export const load: PageLoad = async ({ params, url, route, depends }) => { const view = getView(url, route, View.Grid); const offset = pageToOffset(page, limit); - const tables = await sdk.forProject(params.region, params.project).tablesDB.listTables({ - databaseId: params.database, - queries: [Query.limit(limit), Query.offset(offset), Query.orderDesc('')], - search: search || undefined + const databaseType = database.type as DatabaseType; + + const databaseSdk = useDatabaseSdk(params.region, params.project, databaseType); + const entities = await databaseSdk.listEntities({ + databaseId: database.$id, + search: search || undefined, + queries: [Query.limit(limit), Query.offset(offset), Query.orderDesc('')] }); return { @@ -24,6 +28,6 @@ export const load: PageLoad = async ({ params, url, route, depends }) => { limit, search, view, - tables + entities }; }; diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/+page.svelte index e644905413..63946ac700 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/+page.svelte @@ -5,7 +5,7 @@ import BackupPolicy from './policy.svelte'; import LockedCard from './locked.svelte'; import Table from './table.svelte'; - import type { PageData } from './$types'; + import type { PageProps } from './$types'; import CreatePolicy from './createPolicy.svelte'; import { Button } from '$lib/elements/forms'; import { addNotification, dismissAllNotifications } from '$lib/stores/notifications'; @@ -20,16 +20,17 @@ import { ID } from '@appwrite.io/console'; import { showCreateBackup, showCreatePolicy } from './store'; import { getProjectId } from '$lib/helpers/project'; - import { trackEvent } from '$lib/actions/analytics'; + import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { Layout, Typography } from '@appwrite.io/pink-svelte'; import { page } from '$app/state'; import IconQuestionMarkCircle from './components/questionIcon.svelte'; - let policyCreateError: string; - let totalPolicies: UserBackupPolicy[] = []; - let isDisabled = isSelfHosted || (isCloud && !$currentPlan.backupsEnabled); + const { data }: PageProps = $props(); - export let data: PageData; + let policyCreateError: string | null = $state(null); + let totalPolicies: UserBackupPolicy[] = $state([]); + + const isDisabled = $derived(isSelfHosted || (isCloud && !$currentPlan.backupsEnabled)); const showFeedbackNotification = () => { let counter = localStorage.getItem('createBackupsCounter'); @@ -112,7 +113,7 @@ if (actualDay) message['monthlyInterval'] = actualDay; - trackEvent('submit_policy_submit', message); + trackEvent(Submit.DatabaseBackupPolicyCreate, message); }); }; @@ -148,14 +149,12 @@ await invalidate(Dependencies.BACKUPS); showFeedbackNotification(); - } catch (err) { - addNotification({ - type: 'error', - message: err.message - }); - } finally { + totalPolicies = []; $showCreatePolicy = false; + } catch (err) { + policyCreateError = err.message; + trackError(err, Submit.DatabaseBackupPolicyCreate); } }; diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/table.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/table.svelte index 5cd8075920..d1c29d7126 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/table.svelte @@ -17,7 +17,6 @@ import { calculateSize } from '$lib/helpers/sizeConvertion'; import { ID, type Models } from '@appwrite.io/console'; import { columns } from './store'; - import { database } from '../store'; import { Click, Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { copy } from '$lib/helpers/copy'; import { LabelCard } from '$lib/components/index.js'; @@ -51,10 +50,12 @@ data: PageData; } = $props(); + const database = $derived(data.database); + let showDelete = $state(false); let selectedBackup: Models.BackupArchive | null = $state(null); - let showDropdown = []; + let showDropdown = $state([]); let showRestore = $state(false); let showCustomId = $state(false); @@ -65,6 +66,7 @@ let confirmSameDbRestore = $state(false); let selectedRestoreOption = $state('new'); + const restoreOptions = [ { id: 'new', @@ -78,14 +80,6 @@ } ]; - const disableRestoreButton = $derived.by(() => { - return ( - (selectedRestoreOption === 'new' && - (!newDatabaseInfo.name || $database.$id === newDatabaseInfo.id)) || - (selectedRestoreOption === 'same' && !confirmSameDbRestore) - ); - }); - function getPolicyDetails(policyId: string | null): Models.BackupPolicy | null { return data.policies.policies.find((policy) => policy.$id === policyId); } @@ -154,8 +148,8 @@ async function restoreBackup() { if (selectedRestoreOption === 'same') { - newDatabaseInfo.id = $database.$id; - newDatabaseInfo.name = $database.name; + newDatabaseInfo.id = database.$id; + newDatabaseInfo.name = database.name; } try { @@ -184,6 +178,14 @@ } } + const disableRestoreButton = $derived.by(() => { + return ( + (selectedRestoreOption === 'new' && + (!newDatabaseInfo.name || database.$id === newDatabaseInfo.id)) || + (selectedRestoreOption === 'same' && !confirmSameDbRestore) + ); + }); + $effect(() => { if (!showRestore && !showDelete) { showCustomId = false; @@ -373,7 +375,7 @@ autofocus={false} name="Database" bind:show={showCustomId} - databaseId={$database.$id} + databaseId={database.$id} bind:id={newDatabaseInfo.id} /> {/if} @@ -383,7 +385,7 @@ size="s" id="delete_policy" bind:checked={confirmSameDbRestore} - label="Overwrite '{$database.name}' with the selected backup version"> + label="Overwrite '{database.name}' with the selected backup version"> {/if} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/breadcrumbs.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/breadcrumbs.svelte index e1f0459d41..2da3c51b03 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/breadcrumbs.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/breadcrumbs.svelte @@ -1,28 +1,37 @@ diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/delete.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/delete.svelte index be59aa9ab8..2ab3fa80da 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/delete.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/delete.svelte @@ -1,34 +1,38 @@

- {#if tableItems.length > 0} - The following tables and all data associated with {$database.name}, will be + {#if entityItems.length > 0} + The following tables and all data associated with {database.name}, will be permanently deleted. {:else} - Are you sure you want to delete {$database.name}? + Are you sure you want to delete {database.name}? {/if}

@@ -114,16 +128,16 @@ {:else if error}

- Are you sure you want to delete {$database.name}? + Are you sure you want to delete {database.name}?

- {:else if tableItems.length > 0} + {:else if entityItems.length > 0}
Table Last Updated - {#each tableItems as table} + {#each entityItems as table} {table.name} @@ -133,9 +147,9 @@ {/each} - {#if tableItems.length < tables.total} + {#if entityItems.length < entities.total}
- @@ -143,11 +157,11 @@
{/if}
- {:else if tableItems.length > 25} + {:else if entityItems.length > 25} @@ -100,18 +110,20 @@ Delete database - The database will be permanently deleted, including all tables within it. This action is - irreversible. + The database will be permanently deleted, including all {terminology.entity.lower + .plural} within it. This action is irreversible. -
{$database.name}
+
{database.name}
- {#await loadTableCount()} + {#await loadEntityCount()} {:then count} - {count} {count === 1 ? 'Table' : 'Tables'} + {@const entity = terminology.entity.title} + {count} + {count === 1 ? entity.singular : entity.plural} {/await}
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/store.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/store.ts index 36e755d77e..d5e75fb54e 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/store.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/store.ts @@ -1,11 +1,10 @@ -import { page } from '$app/stores'; +import { writable } from 'svelte/store'; import type { Column } from '$lib/helpers/types'; -import type { Models } from '@appwrite.io/console'; -import { derived, writable } from 'svelte/store'; import { IconChartBar, IconCloudUpload, IconCog } from '@appwrite.io/pink-icons-svelte'; +import { resolveRoute, withPath } from '$lib/stores/navigation'; +import type { Page } from '@sveltejs/kit'; -export const database = derived(page, ($page) => $page.data.database as Models.Database); -export const showCreateTable = writable(false); +export const showCreateEntity = writable(false); export const tableViewColumns = writable([ { id: '$id', title: 'Table ID', type: 'string', width: 200 }, @@ -36,3 +35,13 @@ export const databaseSubNavigationItems = [ { title: 'Usage', href: 'usage', icon: IconChartBar }, { title: 'Settings', href: 'settings', icon: IconCog } ]; + +export function buildEntityRoute(page: Page, entityType: string, entityId: string): string { + return withPath( + resolveRoute( + '/(console)/project-[region]-[project]/databases/database-[database]', + page.params + ), + `/${entityType}-${entityId}` + ); +} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/subNavigation.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/subNavigation.svelte index 31bb9d04a6..011dc9a88a 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/subNavigation.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/subNavigation.svelte @@ -1,10 +1,9 @@ - {$table?.name ?? 'Table'} - Appwrite + {table?.name ?? 'Table'} - Appwrite @@ -502,6 +507,7 @@ {#key currentRowId} + externalFieldKey={$showCreateIndexSheet.column} + onCreateIndex={async (index) => { + await sdk.forProject(page.params.region, page.params.project).tablesDB.createIndex({ + databaseId: page.params.database, + tableId: page.params.table, + key: index.key, + type: index.type, + columns: index.fields, + lengths: index.lengths, + orders: index.orders + }); + + await invalidate(Dependencies.TABLE); + }} /> editRowPermissions?.updatePermissions() }}> @@ -581,7 +602,7 @@ - + +{#if table} +
+{/if} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/+page.svelte index 9f2313ae13..28dd10b930 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/+page.svelte @@ -1,352 +1,103 @@ - - - {#if $canWriteTables} - - {/if} - - - -
- {#if data.table?.columns?.length} - {#if data.table.indexes.length} - - (showCreateIndex = true)} - on:columnsResize={(resize) => saveColumnsWidth(resize.detail)}> - - Key - Type - Columns - - - Lengths - - - - {#each data.table.indexes as index} - - - - {index.key} - {#if index.status !== 'available'} - - {#if index.error} - { - e.preventDefault(); - error = index.error; - showFailed = true; - }}>Details - {/if} - {/if} - - - {index.type} - - {index.columns.join(', ')} - - - - - - {index.lengths} - - - - - - { - toggle(); - selectedIndex = index; - showOverview = true; - }}>Overview - -
- -
- - { - toggle(); - showDelete = true; - selectedIndex = index; - trackEvent(Click.DatabaseIndexDelete); - }}>Delete -
-
-
-
- {/each} - - - - - {@const length = data.table.indexes.length} - {length} - {length === 1 ? 'index' : 'indexes'} - - - -
-
- {:else} - - {#snippet subtitle()} - {#if isCloud} - - Need a hand? Learn more in the - - docs. - - - {/if} - {/snippet} - - {#snippet actions()} - {#if isCloud} - { - showIndexesSuggestions.update(() => true); - }} /> - {/if} + + {#snippet emptyIndexesSheetView(toggle)} + + {#snippet subtitle()} + {#if isCloud} + + Need a hand? Learn more in the + + docs. + + + {/if} + {/snippet} + {#snippet actions()} + {#if isCloud} { - showCreateIndex = true; + showIndexesSuggestions.update(() => true); }} /> + {/if} + + - {#if !isCloud} - - {/if} - {/snippet} - - {/if} - {:else} + {#if !isCloud} + + {/if} + {/snippet} + + {/snippet} + + {#snippet emptyEntitiesSheetView()} {#snippet subtitle()} {#if isCloud} @@ -395,59 +146,5 @@ {/if} {/snippet} - {/if} - - {#if selectedIndexes.length > 0} -
- - -
- - - - {selectedIndexes.length > 1 ? 'indexes' : 'index'} - selected - - -
-
- - - - -
-
- {/if} -
- - await createIndex.create() - }}> - - - -{#if selectedIndex} - -{:else if selectedIndexes && selectedIndexes.length} - -{/if} - - - - - - - - + {/snippet} + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/select.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/select.svelte deleted file mode 100644 index e356097a5e..0000000000 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/select.svelte +++ /dev/null @@ -1,39 +0,0 @@ - - - -
- - -
-{#if error} - {error} -{/if} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/relationship.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/relationship.svelte index fccc94df4a..4f8c836bd8 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/relationship.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/relationship.svelte @@ -124,7 +124,12 @@ } function updateRelatedList() { + /** + * don't be alarmed here. + * reassigning to trigger reactivity! + */ relatedList = relatedList; + value = relatedList; } diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/create.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/create.svelte index 8ac677c606..62eb13dd6f 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/create.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/create.svelte @@ -10,7 +10,7 @@ import { type Columns, spreadsheetRenderKey } from '../store'; import { ID, type Models } from '@appwrite.io/console'; import { Alert, Layout, Typography, Selector } from '@appwrite.io/pink-svelte'; - import SideSheet from '../layout/sidesheet.svelte'; + import { type Entity, SideSheet, toRelationalField } from '$database/(entity)'; import { invalidate } from '$app/navigation'; import { Dependencies } from '$lib/constants'; import { tick } from 'svelte'; @@ -29,8 +29,8 @@ showSheet = $bindable(false), existingData = $bindable(null) }: { + table: Entity; showSheet: boolean; - table: Models.Table; existingData: Models.Row | null; } = $props(); @@ -45,15 +45,17 @@ } function computeInitialCreateRow(): CreateRow { - const availableColumns = table.columns.filter((a) => a.status === 'available'); + const availableColumns = table.fields + .map(toRelationalField) + .filter((column: Columns) => column.status === 'available'); return { id: null, row: existingData ? existingData : availableColumns.reduce( - (acc, attr) => { - acc[attr.key] = attr.array ? [] : null; + (acc, field) => { + acc[field.key] = field.array ? [] : null; return acc; }, {} as Record @@ -189,7 +191,7 @@ Choose which permission scopes to grant your application. It is best practice to allow only the permissions you need to meet your project goals. - {#if table.rowSecurity} + {#if table.recordSecurity} Row security is enabled Users will be able to access this row if they have been granted diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/edit.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/edit.svelte index bfa9e7b4c5..418b8a6c14 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/edit.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/edit.svelte @@ -8,7 +8,7 @@ import type { Models } from '@appwrite.io/console'; import { Dependencies } from '$lib/constants'; import { invalidate } from '$app/navigation'; - import { table, type Columns, PROHIBITED_ROW_KEYS } from '../store'; + import { type Columns, PROHIBITED_ROW_KEYS } from '../store'; import ColumnItem from './columns/columnItem.svelte'; import { buildWildcardColumnsQuery, @@ -18,18 +18,18 @@ } from './store'; import { Layout, Skeleton } from '@appwrite.io/pink-svelte'; import { deepClone } from '$lib/helpers/object'; + import { type Entity, toRelationalField } from '$database/(entity)'; import deepEqual from 'deep-equal'; import { onMount } from 'svelte'; - const tableId = page.params.table; - const databaseId = page.params.database; - let { + table, row = $bindable(), rowId = $bindable(null), autoFocus = true, disabled = $bindable(true) }: { + table: Entity; row?: Models.Row | null; rowId?: string | null; autoFocus?: boolean; @@ -65,10 +65,10 @@ try { row = await sdk.forProject(page.params.region, page.params.project).tablesDB.getRow({ - databaseId, - tableId, + databaseId: table.databaseId, + tableId: table.$id, rowId, - queries: buildWildcardColumnsQuery($table) + queries: buildWildcardColumnsQuery(table) }); } catch (error) { addNotification({ @@ -143,8 +143,8 @@ try { await sdk.forProject(page.params.region, page.params.project).tablesDB.updateRow({ - databaseId, - tableId, + databaseId: table.databaseId, + tableId: table.$id, rowId: row.$id, data: $work, permissions: $work.$permissions @@ -166,12 +166,12 @@ } $effect(() => { - if (!work || !row || !$table?.columns?.length) { + if (!work || !row || !table?.fields?.length) { disabled = true; return; } - disabled = $table.columns.every((column) => compareColumns(column, $work, row)); + disabled = table.fields.every((column: Columns) => compareColumns(column, $work, row)); }); function focusFirstInput() { @@ -191,16 +191,16 @@
-{:else if $table.columns?.length && work} +{:else if table.fields?.length && work}
- {#each $table.columns as column} + {#each table.fields as column} {@const label = column.key} {/each} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/editPermissions.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/editPermissions.svelte index e9edae26e7..d3f58dc7a9 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/editPermissions.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/editPermissions.svelte @@ -1,5 +1,4 @@
- {#if $table} - - - - - - - {/if} + updateTable({ enabled })} /> + + updateTable({ name })} /> + + + + updateTable({ permissions })} /> + + updateTable({ rowSecurity })} /> + +
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/settings/dangerZone.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/settings/dangerZone.svelte deleted file mode 100644 index fa75976966..0000000000 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/settings/dangerZone.svelte +++ /dev/null @@ -1,34 +0,0 @@ - - - - Delete table - The table will be permanently deleted, including all the rows within it. This action is irreversible. - - - -
{$table.name}
-
-

Last updated: {toLocaleDateTime($table.$updatedAt)}

-
-
- - - - -
- - diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/settings/deleteTable.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/settings/deleteTable.svelte deleted file mode 100644 index 697dd79622..0000000000 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/settings/deleteTable.svelte +++ /dev/null @@ -1,61 +0,0 @@ - - - - - Are you sure you want to delete {$table.name}? - - diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/settings/updateName.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/settings/updateName.svelte deleted file mode 100644 index 45ad32ed0d..0000000000 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/settings/updateName.svelte +++ /dev/null @@ -1,68 +0,0 @@ - - -
- - Name - - - - - - - - - diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/settings/updatePermissions.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/settings/updatePermissions.svelte deleted file mode 100644 index 8041cb2c2f..0000000000 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/settings/updatePermissions.svelte +++ /dev/null @@ -1,72 +0,0 @@ - - - - Permissions - Choose who can access your tables and rows. - Learn more - . - - {#if tablePermissions} - - {/if} - - - - - diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/settings/updateSecurity.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/settings/updateSecurity.svelte deleted file mode 100644 index 1b92ad0596..0000000000 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/settings/updateSecurity.svelte +++ /dev/null @@ -1,66 +0,0 @@ - - - - Row security - - - -

- When row security is enabled, users will be able to access rows for which they have been - granted either row or table permissions. -

-

- If row security is disabled, users can access rows only if they have table permissions. Row permissions will be ignored. -

-
- - - -
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/settings/updateStatus.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/settings/updateStatus.svelte deleted file mode 100644 index 8d91f266e7..0000000000 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/settings/updateStatus.svelte +++ /dev/null @@ -1,66 +0,0 @@ - - - - {$table.name} - -
    - -
-
-

Created: {toLocaleDateTime($table.$createdAt)}

-

Last updated: {toLocaleDateTime($table.$updatedAt)}

-
-
- - - - -
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte index dabc3955cf..0b7cf540bc 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte @@ -1,7 +1,7 @@ -
- - - -
+ diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte index 6a5c733521..3768e05434 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte @@ -1,6 +1,5 @@ @@ -72,18 +63,21 @@ {/snippet} {#snippet children(root)} - {#each data.tables.tables as table (table.$id)} - + {#each data.entities.entities as entity (entity.$id)} + {#each $tableViewColumns as column} {#if column.id === '$id'} {#key $tableViewColumns} - {table.$id} + {entity.$id} {/key} {:else if column.id === 'name'} - {table.name} + {entity.name} {:else} - + {/if} {/each} diff --git a/src/routes/(console)/project-[region]-[project]/databases/table.svelte b/src/routes/(console)/project-[region]-[project]/databases/table.svelte index 0764bf9ea2..ad134a1feb 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/table.svelte @@ -1,20 +1,21 @@ @@ -40,13 +54,11 @@ {title} {/each} + {#each databases.databases as database (database.$id)} - {@const tableId = tables[database?.$id] ?? null} - {@const tableHref = tableId ? `/table-${tableId}` : ''} - + {@const entityId = entities[database?.$id] ?? null} + {#each $columns as column} {#if column.id === '$id'} diff --git a/src/routes/(console)/project-[region]-[project]/overview/onboard.svelte b/src/routes/(console)/project-[region]-[project]/overview/onboard.svelte index 8650aa4987..42216d01aa 100644 --- a/src/routes/(console)/project-[region]-[project]/overview/onboard.svelte +++ b/src/routes/(console)/project-[region]-[project]/overview/onboard.svelte @@ -35,6 +35,7 @@ import { Click, trackEvent } from '$lib/actions/analytics'; import { goto } from '$app/navigation'; import { page } from '$app/state'; + import { Platform } from './platforms/+page.svelte'; let { pingCount = 0, @@ -67,7 +68,7 @@ goto(`${projectRoute}/overview/api-keys/create`, { replaceState: true }); } - function openPlatformWizard(type: number, platform?: Models.Platform) { + function openPlatformWizard(type: Platform, platform?: Models.Platform) { if (platform) { continuePlatform(type, platform.name, platform.type); } else { @@ -109,7 +110,10 @@ direction={$isSmallViewport ? 'column' : 'row'}> { - openPlatformWizard(0, platformMap.get('Web')); + openPlatformWizard( + Platform.Web, + platformMap.get('Web') + ); }} padding="s" > { - openPlatformWizard(4, platformMap.get('React Native')); + openPlatformWizard( + Platform.ReactNative, + platformMap.get('React Native') + ); }} padding="s" > { - openPlatformWizard(3, platformMap.get('Apple')); + openPlatformWizard( + Platform.Apple, + platformMap.get('Apple') + ); }} padding="s"> { - openPlatformWizard(2, platformMap.get('Android')); + openPlatformWizard( + Platform.Android, + platformMap.get('Android') + ); }} padding="s"> { - openPlatformWizard(1, platformMap.get('Flutter')); + openPlatformWizard( + Platform.Flutter, + platformMap.get('Flutter') + ); }} padding="s"> Security settings - Enable or disable security services for the bucket including Ecryption + Enable or disable security services for the bucket including Encryption and Antivirus scanning. { } if (error.type === 'user_more_factors_required') { - if (url.pathname === `${base}/mfa`) + if (url.pathname === resolve('/mfa')) return { mfaRequired: true }; - redirect(303, withParams(`${base}/mfa`, url.searchParams)); + redirect(303, withParams(resolve('/mfa'), url.searchParams)); } if (!isPublicRoute) { @@ -66,7 +66,7 @@ export const load: LayoutLoad = async ({ depends, url, route }) => { checkPricingRefAndRedirect(url.searchParams, true); } - redirect(303, withParams(`${base}/login`, url.searchParams)); + redirect(303, withParams(resolve('/login'), url.searchParams)); } }; diff --git a/svelte.config.js b/svelte.config.js index 18b6687901..831de20405 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -19,7 +19,9 @@ const config = { kit: { alias: { $routes: './src/routes', - $themes: './src/themes' + $themes: './src/themes', + $database: + './src/routes/(console)/project-[region]-[project]/databases/database-[database]' }, adapter: adapter({ fallback: 'index.html',