diff --git a/apps/studio/components/interfaces/Auth/Policies/Policies.tsx b/apps/studio/components/interfaces/Auth/Policies/Policies.tsx index aaff83478e4..49c0947073a 100644 --- a/apps/studio/components/interfaces/Auth/Policies/Policies.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/Policies.tsx @@ -110,7 +110,7 @@ const Policies = ({ deleteDatabasePolicy({ projectRef: project.ref, connectionString: project.connectionString, - id: selectedPolicyToDelete.id, + originalPolicy: selectedPolicyToDelete, }) } diff --git a/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/index.tsx b/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/index.tsx index 223d54076d2..1684ca6286d 100644 --- a/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/index.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/index.tsx @@ -225,9 +225,9 @@ export const PolicyEditorPanel = memo(function ({ if (Object.keys(payload).length === 0) return onSelectCancel() updatePolicy({ - id: selectedPolicy.id, projectRef: selectedProject.ref, connectionString: selectedProject?.connectionString, + originalPolicy: selectedPolicy, payload, }) } diff --git a/apps/studio/components/to-be-cleaned/Storage/DeleteBucketModal.tsx b/apps/studio/components/to-be-cleaned/Storage/DeleteBucketModal.tsx index 70ec0ddb976..a02f950e8e2 100644 --- a/apps/studio/components/to-be-cleaned/Storage/DeleteBucketModal.tsx +++ b/apps/studio/components/to-be-cleaned/Storage/DeleteBucketModal.tsx @@ -52,7 +52,7 @@ const DeleteBucketModal = ({ visible = false, bucket, onClose }: DeleteBucketMod deletePolicy({ projectRef: project?.ref, connectionString: project?.connectionString, - id: policy.id, + originalPolicy: policy, }) ) ) diff --git a/apps/studio/components/to-be-cleaned/Storage/StoragePolicies/StoragePolicies.tsx b/apps/studio/components/to-be-cleaned/Storage/StoragePolicies/StoragePolicies.tsx index 483cf69dc6a..1c29a199b90 100644 --- a/apps/studio/components/to-be-cleaned/Storage/StoragePolicies/StoragePolicies.tsx +++ b/apps/studio/components/to-be-cleaned/Storage/StoragePolicies/StoragePolicies.tsx @@ -24,7 +24,7 @@ const StoragePolicies = () => { const { data, isLoading: isLoadingBuckets } = useBucketsQuery({ projectRef }) const buckets = data ?? [] - const [selectedPolicyToEdit, setSelectedPolicyToEdit] = useState({}) + const [selectedPolicyToEdit, setSelectedPolicyToEdit] = useState({}) const [selectedPolicyToDelete, setSelectedPolicyToDelete] = useState({}) const [isEditingPolicyForBucket, setIsEditingPolicyForBucket] = useState({}) @@ -155,7 +155,7 @@ const StoragePolicies = () => { await updateDatabasePolicy({ projectRef: project?.ref, connectionString: project?.connectionString, - id: payload.id, + originalPolicy: selectedPolicyToEdit, payload, }) return false @@ -170,7 +170,7 @@ const StoragePolicies = () => { deleteDatabasePolicy({ projectRef: project?.ref, connectionString: project?.connectionString, - id: selectedPolicyToDelete.id, + originalPolicy: selectedPolicyToDelete, }) } diff --git a/apps/studio/data/database-policies/database-policy-create-mutation.ts b/apps/studio/data/database-policies/database-policy-create-mutation.ts index 4a8579b7f2f..ec458a81e84 100644 --- a/apps/studio/data/database-policies/database-policy-create-mutation.ts +++ b/apps/studio/data/database-policies/database-policy-create-mutation.ts @@ -1,8 +1,9 @@ +import pgMeta from '@supabase/pg-meta' import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' import type { components } from 'data/api' -import { handleError, post } from 'data/fetchers' +import { executeSql } from 'data/sql/execute-sql-query' import type { ResponseError } from 'types' import { databasePoliciesKeys } from './keys' @@ -22,17 +23,15 @@ export async function createDatabasePolicy({ let headers = new Headers() if (connectionString) headers.set('x-connection-encrypted', connectionString) - const { data, error } = await post('/platform/pg-meta/{ref}/policies', { - params: { - header: { 'x-connection-encrypted': connectionString! }, - path: { ref: projectRef }, - }, - body: payload, - headers, + const { sql } = pgMeta.policies.create(payload) + const { result } = await executeSql({ + projectRef, + connectionString, + sql, + queryKey: ['policy', 'create'], }) - if (error) handleError(error) - return data + return result } type DatabasePolicyCreateData = Awaited> diff --git a/apps/studio/data/database-policies/database-policy-delete-mutation.ts b/apps/studio/data/database-policies/database-policy-delete-mutation.ts index ff2877a4716..e6acc542eb2 100644 --- a/apps/studio/data/database-policies/database-policy-delete-mutation.ts +++ b/apps/studio/data/database-policies/database-policy-delete-mutation.ts @@ -1,35 +1,39 @@ +import pgMeta from '@supabase/pg-meta' import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' -import { del, handleError } from 'data/fetchers' +import { executeSql } from 'data/sql/execute-sql-query' import type { ResponseError } from 'types' import { databasePoliciesKeys } from './keys' export type DatabasePolicyDeleteVariables = { projectRef: string connectionString?: string | null - id: number + originalPolicy: { + id: number + name: string + schema: string + table: string + } } export async function deleteDatabasePolicy({ projectRef, connectionString, - id, + originalPolicy, }: DatabasePolicyDeleteVariables) { let headers = new Headers() if (connectionString) headers.set('x-connection-encrypted', connectionString) - const { data, error } = await del('/platform/pg-meta/{ref}/policies', { - params: { - header: { 'x-connection-encrypted': connectionString! }, - path: { ref: projectRef }, - query: { id }, - }, - headers, + const { sql } = pgMeta.policies.remove(originalPolicy) + const { result } = await executeSql({ + projectRef, + connectionString, + sql, + queryKey: ['policy', 'delete', originalPolicy.id], }) - if (error) handleError(error) - return data + return result } type DatabasePolicyDeleteData = Awaited> diff --git a/apps/studio/data/database-policies/database-policy-update-mutation.ts b/apps/studio/data/database-policies/database-policy-update-mutation.ts index 219df047af5..b07e82406b1 100644 --- a/apps/studio/data/database-policies/database-policy-update-mutation.ts +++ b/apps/studio/data/database-policies/database-policy-update-mutation.ts @@ -1,14 +1,20 @@ +import pgMeta from '@supabase/pg-meta' import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' -import { handleError, patch } from 'data/fetchers' +import { executeSql } from 'data/sql/execute-sql-query' import type { ResponseError } from 'types' import { databasePoliciesKeys } from './keys' export type DatabasePolicyUpdateVariables = { projectRef: string connectionString?: string | null - id: number + originalPolicy: { + id: number + name: string + schema: string + table: string + } payload: { name?: string definition?: string @@ -20,24 +26,21 @@ export type DatabasePolicyUpdateVariables = { export async function updateDatabasePolicy({ projectRef, connectionString, - id, + originalPolicy, payload, }: DatabasePolicyUpdateVariables) { let headers = new Headers() if (connectionString) headers.set('x-connection-encrypted', connectionString) - const { data, error } = await patch('/platform/pg-meta/{ref}/policies', { - params: { - header: { 'x-connection-encrypted': connectionString! }, - path: { ref: projectRef }, - query: { id }, - }, - body: payload, - headers, + const { sql } = pgMeta.policies.update(originalPolicy, payload) + const { result } = await executeSql({ + projectRef, + connectionString, + sql, + queryKey: ['policy', 'update', originalPolicy.id], }) - if (error) handleError(error) - return data + return result } type DatabasePolicyUpdateData = Awaited> diff --git a/packages/pg-meta/src/pg-meta-policies.ts b/packages/pg-meta/src/pg-meta-policies.ts index d154c1c17cf..1951c37f0cd 100644 --- a/packages/pg-meta/src/pg-meta-policies.ts +++ b/packages/pg-meta/src/pg-meta-policies.ts @@ -128,46 +128,28 @@ type PolicyUpdateParams = { roles?: string[] } -function update(identifier: PolicyIdentifier, params: PolicyUpdateParams): { sql: string } { +function update( + identifier: Pick, + params: PolicyUpdateParams +): { sql: string } { const { name, definition, check, roles } = params - const indentifyClause = getIdentifierWhereClause(identifier) - const sql = ` -do $$ -declare - old record; -begin - with policies as (${POLICIES_SQL}) - select * into old from policies where ${indentifyClause}; - if old is null then - raise exception 'Cannot find policy with id %', ${literal(indentifyClause)}; - end if; - ${definition ? `execute(format('alter policy %I on %I.%I using (%s);', old.name, old.schema, old.table, ${literal(definition)}));` : ''} - ${check ? `execute(format('alter policy %I on %I.%I with check (%s);', old.name, old.schema, old.table, ${literal(check)}));` : ''} - ${roles ? `execute(format('alter policy %I on %I.%I to %s;', old.name, old.schema, old.table, ${literal(roles.map(ident).join(','))}));` : ''} - ${name ? `execute(format('alter policy %I on %I.%I rename to %I;', old.name, old.schema, old.table, ${literal(name)}));` : ''} -end -$$;` + const alter = `ALTER POLICY ${ident(identifier.name)} ON ${ident(identifier.schema)}.${ident(identifier.table)}` + const nameSql = name === undefined ? '' : `${alter} RENAME TO ${ident(name)};` + const definitionSql = definition === undefined ? '' : `${alter} USING (${definition});` + const checkSql = check === undefined ? '' : `${alter} WITH CHECK (${check});` + const rolesSql = roles === undefined ? '' : `${alter} TO ${roles.map(ident).join(',')};` + + // nameSql must be last + const sql = `BEGIN; ${definitionSql} ${checkSql} ${rolesSql} ${nameSql} COMMIT;` + return { sql } } -function remove(identifier: PolicyIdentifier): { sql: string } { - const indentifyClause = getIdentifierWhereClause(identifier) - - const sql = ` -do $$ -declare - old record; -begin - with policies as (${POLICIES_SQL}) - select * into old from policies where ${indentifyClause}; - if old is null then - raise exception 'Cannot find policy with id %', ${literal(indentifyClause)}; - end if; - - execute(format('drop policy %I on %I.%I;', old.name, old.schema, old.table)); -end -$$;` +function remove(identifier: Pick): { sql: string } { + const sql = `DROP POLICY ${ident(identifier.name)} ON ${ident(identifier.schema)}.${ident( + identifier.table + )};` return { sql } } diff --git a/packages/pg-meta/test/policies.test.ts b/packages/pg-meta/test/policies.test.ts index 9ac252ca1d3..00b8c97cdaf 100644 --- a/packages/pg-meta/test/policies.test.ts +++ b/packages/pg-meta/test/policies.test.ts @@ -22,7 +22,7 @@ const withTestDatabase = ( } withTestDatabase('list policies', async ({ executeQuery }) => { - const { sql, zod } = await pgMeta.policies.list() + const { sql, zod } = pgMeta.policies.list() const res = zod.parse(await executeQuery(sql)) const policy = res.find(({ name }) => name === 'categories_update_policy') @@ -48,7 +48,7 @@ withTestDatabase('list policies', async ({ executeQuery }) => { }) withTestDatabase('list policies with included schemas', async ({ executeQuery }) => { - const { sql, zod } = await pgMeta.policies.list({ + const { sql, zod } = pgMeta.policies.list({ includedSchemas: ['public'], }) const res = zod.parse(await executeQuery(sql)) @@ -61,7 +61,7 @@ withTestDatabase('list policies with included schemas', async ({ executeQuery }) withTestDatabase('retrieve, create, update, delete policies', async ({ executeQuery }) => { // Create policy - const { sql: createSql } = await pgMeta.policies.create({ + const { sql: createSql } = pgMeta.policies.create({ name: 'test_policy', schema: 'public', table: 'memes', @@ -70,7 +70,7 @@ withTestDatabase('retrieve, create, update, delete policies', async ({ executeQu await executeQuery(createSql) // List to get the created policy - const { sql: listSql, zod: listZod } = await pgMeta.policies.list() + const { sql: listSql, zod: listZod } = pgMeta.policies.list() const policies = listZod.parse(await executeQuery(listSql)) const createdPolicy = policies.find((p) => p.name === 'test_policy') @@ -95,19 +95,16 @@ withTestDatabase('retrieve, create, update, delete policies', async ({ executeQu ) // Update policy - const { sql: updateSql } = await pgMeta.policies.update( - { id: createdPolicy!.id }, - { - name: 'updated_policy', - definition: "current_setting('my.username') IN (name)", - check: "current_setting('my.username') IN (name)", - roles: ['postgres'], - } - ) + const { sql: updateSql } = pgMeta.policies.update(createdPolicy!, { + name: 'updated_policy', + definition: "current_setting('my.username') IN (name)", + check: "current_setting('my.username') IN (name)", + roles: ['postgres'], + }) await executeQuery(updateSql) // Retrieve updated policy - const { sql: retrieveSql, zod: retrieveZod } = await pgMeta.policies.retrieve({ + const { sql: retrieveSql, zod: retrieveZod } = pgMeta.policies.retrieve({ id: createdPolicy!.id, }) const updatedPolicy = retrieveZod.parse((await executeQuery(retrieveSql))[0]) @@ -133,11 +130,11 @@ withTestDatabase('retrieve, create, update, delete policies', async ({ executeQu ) // Remove policy - const { sql: removeSql } = await pgMeta.policies.remove({ id: updatedPolicy!.id }) + const { sql: removeSql } = pgMeta.policies.remove(updatedPolicy!) await executeQuery(removeSql) // Verify policy is removed - const { sql: verifyRemoveSql } = await pgMeta.policies.retrieve({ + const { sql: verifyRemoveSql } = pgMeta.policies.retrieve({ id: updatedPolicy!.id, }) const result = await executeQuery(verifyRemoveSql)