From 01639860454a6cb8dbd9934c466cdad94c9da85b Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Tue, 31 Mar 2026 18:20:51 +0530 Subject: [PATCH 1/2] fix: resource block console --- AGENTS.md | 12 +++--- CLAUDE.md | 1 + src/lib/helpers/project.ts | 43 ++++++++++++++++++- .../auth/teams/team-[team]/+layout.ts | 6 ++- .../auth/user-[user]/+layout.ts | 5 ++- .../databases/database-[database]/+layout.ts | 5 ++- .../table-[table]/+layout.ts | 4 +- .../functions/function-[function]/+layout.ts | 5 ++- .../messaging/message-[message]/+layout.ts | 5 ++- .../providers/provider-[provider]/+layout.ts | 5 ++- .../messaging/topics/topic-[topic]/+layout.ts | 5 ++- .../sites/site-[site]/+layout.ts | 6 ++- .../storage/bucket-[bucket]/+layout.ts | 5 ++- .../bucket-[bucket]/file-[file]/+layout.ts | 5 ++- 14 files changed, 94 insertions(+), 18 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 69919acdf7..86083b656c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -152,12 +152,12 @@ await sdk.forProject(region, projectId).tablesDB.listTables(); The databases feature unifies multiple database backends behind a polymorph API (`$database/(entity)/helpers/sdk.ts`): -| Type | Entity | Field | Record | Status | -| ------------- | ---------- | --------- | -------- | ------------------------ | -| `tablesdb` | table | column | row | Implemented | -| `documentsdb` | collection | attribute | document | Implemented | -| `vectorsdb` | -- | -- | -- | Not yet implemented | -| `dedicateddb` | table | column | row | Cross-repo (cloud/edge) | +| Type | Entity | Field | Record | Status | +| ------------- | ---------- | --------- | -------- | ----------------------- | +| `tablesdb` | table | column | row | Implemented | +| `documentsdb` | collection | attribute | document | Implemented | +| `vectorsdb` | -- | -- | -- | Not yet implemented | +| `dedicateddb` | table | column | row | Cross-repo (cloud/edge) | - `useDatabaseSdk()` returns a unified interface regardless of backing type - `useTerminology()` returns singular/plural names for the current database type diff --git a/CLAUDE.md b/CLAUDE.md index 6fd2fb21ce..21aebfd114 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,2 +1,3 @@ + @AGENTS.md diff --git a/src/lib/helpers/project.ts b/src/lib/helpers/project.ts index 92047c8479..6cd9860db0 100644 --- a/src/lib/helpers/project.ts +++ b/src/lib/helpers/project.ts @@ -3,6 +3,7 @@ import { get } from 'svelte/store'; import { sdk } from '$lib/stores/sdk'; import { projectRegion } from '$routes/(console)/project-[region]-[project]/store'; import type { Models } from '@appwrite.io/console'; +import { error } from '@sveltejs/kit'; /** * Returns the current project ID. @@ -45,5 +46,45 @@ export function getProjectEndpoint(): string { } export function isProjectBlocked(project: Models.Project | null | undefined): boolean { - return project?.status !== 'paused' && !!project?.blocks?.length; + const hasGlobalProjectBlock = (project?.blocks ?? []).some((block) => { + const type = block.resourceType?.trim(); + const id = block.resourceId?.trim(); + + // Global project block: no specific resourceType or resourceId set + return !type && !id; + }); + + return project?.status !== 'paused' && hasGlobalProjectBlock; +} + +export function isResourceBlocked( + project: Models.Project | null | undefined, + resourceType: string, + resourceId: string +): boolean { + const normalizedType = resourceType.trim().toLowerCase(); + const normalizedId = resourceId.trim(); + + return (project?.blocks ?? []).some((block) => { + const type = block.resourceType?.trim().toLowerCase(); + const id = block.resourceId?.trim(); + + return type === normalizedType && id === normalizedId; + }); +} + +export function guardResourceBlock( + project: Models.Project | null | undefined, + resourceType: string | string[], + resourceId: string +) { + const resourceTypes = Array.isArray(resourceType) ? resourceType : [resourceType]; + const isBlocked = resourceTypes.some((type) => isResourceBlocked(project, type, resourceId)); + + if (isBlocked) { + error(403, { + type: 'general_resource_blocked', + message: 'This resource page cannot be accessed.' + }); + } } diff --git a/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/+layout.ts b/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/+layout.ts index 5dd8c4fa9a..aec93c9e7e 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/+layout.ts @@ -3,9 +3,13 @@ import Header from './header.svelte'; import { sdk } from '$lib/stores/sdk'; import type { LayoutLoad } from './$types'; import { Dependencies } from '$lib/constants'; +import { guardResourceBlock } from '$lib/helpers/project'; -export const load: LayoutLoad = async ({ params, depends }) => { +export const load: LayoutLoad = async ({ params, depends, parent }) => { depends(Dependencies.TEAM); + const { project } = await parent(); + guardResourceBlock(project, 'teams', params.team); + return { header: Header, breadcrumbs: Breadcrumbs, diff --git a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/+layout.ts b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/+layout.ts index a0144b07b3..5b01fa2d5a 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/+layout.ts @@ -3,9 +3,12 @@ import Breadcrumbs from './breadcrumbs.svelte'; import Header from './header.svelte'; import { sdk } from '$lib/stores/sdk'; import { Dependencies } from '$lib/constants'; +import { guardResourceBlock } from '$lib/helpers/project'; -export const load: LayoutLoad = async ({ params, depends }) => { +export const load: LayoutLoad = async ({ params, depends, parent }) => { depends(Dependencies.USER); + const { project } = await parent(); + guardResourceBlock(project, 'users', params.user); const [user, userFactors] = await Promise.all([ sdk.forProject(params.region, params.project).users.get({ userId: params.user }), 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 49b60c099b..f787f6e7f3 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 @@ -4,9 +4,12 @@ import type { LayoutLoad } from './$types'; import Breadcrumbs from './breadcrumbs.svelte'; import Header from './header.svelte'; import SubNavigation from './subNavigation.svelte'; +import { guardResourceBlock } from '$lib/helpers/project'; -export const load: LayoutLoad = async ({ params, depends }) => { +export const load: LayoutLoad = async ({ params, depends, parent }) => { depends(Dependencies.DATABASE); + const { project } = await parent(); + guardResourceBlock(project, 'databases', params.database); const database = await sdk.forProject(params.region, params.project).tablesDB.get({ databaseId: params.database diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.ts index e2f2dadbcd..cd5d75e6db 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.ts @@ -2,10 +2,12 @@ import Header from './header.svelte'; import type { LayoutLoad } from './$types'; import { Dependencies } from '$lib/constants'; import { Breadcrumbs, useDatabaseSdk } from '$database/(entity)'; +import { guardResourceBlock } from '$lib/helpers/project'; export const load: LayoutLoad = async ({ params, depends, parent }) => { - const { database } = await parent(); + const { database, project } = await parent(); depends(Dependencies.TABLE); + guardResourceBlock(project, ['tables', 'collections'], params.table); const databaseSdk = useDatabaseSdk(params.region, params.project, database.type); diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/+layout.ts b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/+layout.ts index 56f37dde3d..5e9ebd6c13 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/+layout.ts @@ -5,10 +5,13 @@ import Breadcrumbs from './breadcrumbs.svelte'; import Header from './header.svelte'; import { Query } from '@appwrite.io/console'; import { RuleType } from '$lib/stores/sdk'; +import { guardResourceBlock } from '$lib/helpers/project'; -export const load: LayoutLoad = async ({ params, depends }) => { +export const load: LayoutLoad = async ({ params, depends, parent }) => { depends(Dependencies.FUNCTION); depends(Dependencies.DEPLOYMENTS); + const { project } = await parent(); + guardResourceBlock(project, 'functions', params.function); const func = await sdk .forProject(params.region, params.project) diff --git a/src/routes/(console)/project-[region]-[project]/messaging/message-[message]/+layout.ts b/src/routes/(console)/project-[region]-[project]/messaging/message-[message]/+layout.ts index 75650f25b6..5842265174 100644 --- a/src/routes/(console)/project-[region]-[project]/messaging/message-[message]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/messaging/message-[message]/+layout.ts @@ -4,9 +4,12 @@ import Header from './header.svelte'; import { sdk } from '$lib/stores/sdk'; import { Dependencies } from '$lib/constants'; import type { Models } from '@appwrite.io/console'; +import { guardResourceBlock } from '$lib/helpers/project'; -export const load: LayoutLoad = async ({ params, depends }) => { +export const load: LayoutLoad = async ({ params, depends, parent }) => { depends(Dependencies.MESSAGING_MESSAGE); + const { project } = await parent(); + guardResourceBlock(project, 'messages', params.message); const message = await sdk .forProject(params.region, params.project) diff --git a/src/routes/(console)/project-[region]-[project]/messaging/providers/provider-[provider]/+layout.ts b/src/routes/(console)/project-[region]-[project]/messaging/providers/provider-[provider]/+layout.ts index a5b0e529b4..7fcb2eed6c 100644 --- a/src/routes/(console)/project-[region]-[project]/messaging/providers/provider-[provider]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/messaging/providers/provider-[provider]/+layout.ts @@ -3,9 +3,12 @@ import Breadcrumbs from './breadcrumbs.svelte'; import Header from './header.svelte'; import { sdk } from '$lib/stores/sdk'; import { Dependencies } from '$lib/constants'; +import { guardResourceBlock } from '$lib/helpers/project'; -export const load: LayoutLoad = async ({ params, depends }) => { +export const load: LayoutLoad = async ({ params, depends, parent }) => { depends(Dependencies.MESSAGING_PROVIDER); + const { project } = await parent(); + guardResourceBlock(project, 'providers', params.provider); return { header: Header, diff --git a/src/routes/(console)/project-[region]-[project]/messaging/topics/topic-[topic]/+layout.ts b/src/routes/(console)/project-[region]-[project]/messaging/topics/topic-[topic]/+layout.ts index 641c49623f..83524e89be 100644 --- a/src/routes/(console)/project-[region]-[project]/messaging/topics/topic-[topic]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/messaging/topics/topic-[topic]/+layout.ts @@ -3,9 +3,12 @@ import Breadcrumbs from './breadcrumbs.svelte'; import Header from './header.svelte'; import { sdk } from '$lib/stores/sdk'; import { Dependencies } from '$lib/constants'; +import { guardResourceBlock } from '$lib/helpers/project'; -export const load: LayoutLoad = async ({ params, depends }) => { +export const load: LayoutLoad = async ({ params, depends, parent }) => { depends(Dependencies.MESSAGING_TOPIC); + const { project } = await parent(); + guardResourceBlock(project, 'topics', params.topic); return { header: Header, diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/+layout.ts b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/+layout.ts index 67abddfdfe..fe2ffce658 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/+layout.ts @@ -2,9 +2,13 @@ import Breadcrumbs from './breadcrumbs.svelte'; import Header from './header.svelte'; import { Dependencies } from '$lib/constants'; import { sdk } from '$lib/stores/sdk'; +import { guardResourceBlock } from '$lib/helpers/project'; -export const load = async ({ depends, params }) => { +export const load = async ({ depends, params, parent }) => { depends(Dependencies.SITE); + const { project } = await parent(); + guardResourceBlock(project, 'sites', params.site); + const [site] = await Promise.all([ sdk.forProject(params.region, params.project).sites.get({ siteId: params.site }) ]); diff --git a/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/+layout.ts b/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/+layout.ts index f200e7eb68..7ee9ffe549 100644 --- a/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/+layout.ts @@ -3,9 +3,12 @@ import Breadcrumbs from './breadcrumbs.svelte'; import Header from './header.svelte'; import { sdk } from '$lib/stores/sdk'; import { Dependencies } from '$lib/constants'; +import { guardResourceBlock } from '$lib/helpers/project'; -export const load: LayoutLoad = async ({ params, depends }) => { +export const load: LayoutLoad = async ({ params, depends, parent }) => { depends(Dependencies.BUCKET); + const { project } = await parent(); + guardResourceBlock(project, 'buckets', params.bucket); return { header: Header, diff --git a/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/file-[file]/+layout.ts b/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/file-[file]/+layout.ts index 673b97e755..67cb42ea95 100644 --- a/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/file-[file]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/file-[file]/+layout.ts @@ -3,10 +3,13 @@ import Header from './header.svelte'; import { sdk } from '$lib/stores/sdk'; import { Dependencies } from '$lib/constants'; import type { LayoutLoad } from './$types'; +import { guardResourceBlock } from '$lib/helpers/project'; -export const load: LayoutLoad = async ({ params, depends }) => { +export const load: LayoutLoad = async ({ params, depends, parent }) => { depends(Dependencies.FILE); depends(Dependencies.FILE_TOKENS); + const { project } = await parent(); + guardResourceBlock(project, 'files', params.file); return { header: Header, From 0a89a98831e837ff0523e5055f030fd689cd9586 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Tue, 31 Mar 2026 18:30:38 +0530 Subject: [PATCH 2/2] can be projects too --- src/lib/helpers/project.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/lib/helpers/project.ts b/src/lib/helpers/project.ts index 6cd9860db0..96561daf8b 100644 --- a/src/lib/helpers/project.ts +++ b/src/lib/helpers/project.ts @@ -47,11 +47,16 @@ export function getProjectEndpoint(): string { export function isProjectBlocked(project: Models.Project | null | undefined): boolean { const hasGlobalProjectBlock = (project?.blocks ?? []).some((block) => { - const type = block.resourceType?.trim(); + const type = block.resourceType?.trim().toLowerCase(); const id = block.resourceId?.trim(); - // Global project block: no specific resourceType or resourceId set - return !type && !id; + // Global project block: + // - legacy: both type and id empty + // - new: resourceType === 'projects' with no specific resourceId + const isLegacyGlobal = !type && !id; + const isProjectsGlobal = type === 'projects' && !id; + + return isLegacyGlobal || isProjectsGlobal; }); return project?.status !== 'paused' && hasGlobalProjectBlock;