Skip to content

Commit bc0d55e

Browse files
committed
Separates some of the basic schema updates
1 parent fcf5ab2 commit bc0d55e

File tree

4 files changed

+182
-114
lines changed

4 files changed

+182
-114
lines changed

src/api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ router.use('/extensions', addConnectionToRequest, require('./api/extensions'))
1010
router.use('/functions', addConnectionToRequest, require('./api/functions'))
1111
router.use('/policies', addConnectionToRequest, require('./api/policies'))
1212
router.use('/query', addConnectionToRequest, require('./api/query'))
13-
router.use('/schemas', addConnectionToRequest, require('./api/schemas'))
13+
router.use('/schemas', addConnectionToRequest, require('./routes/schemas'))
1414
router.use('/tables', addConnectionToRequest, require('./api/tables'))
1515
router.use('/types', addConnectionToRequest, require('./api/types'))
1616
router.use('/roles', addConnectionToRequest, require('./api/roles'))

src/api/schemas.ts

Lines changed: 51 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,72 @@
1-
import { Router } from 'express'
2-
import format from 'pg-format'
31
import SQL from 'sql-template-strings'
42
import sqlTemplates = require('../lib/sql')
53
import { RunQuery } from '../lib/connectionPool'
64
import { DEFAULT_SYSTEM_SCHEMAS } from '../lib/constants'
7-
import { Schemas } from '../lib/interfaces'
5+
import { Schemas as Interfaces } from '../lib/interfaces'
86

9-
const { schemas } = sqlTemplates
7+
const { schemas: allSchemasSql } = sqlTemplates
8+
const defaultSchemasList = DEFAULT_SYSTEM_SCHEMAS.map((x) => `'${x}'`).join(', ')
109

1110
/**
12-
* @param {boolean} [include_system_schemas=false] - Return system schemas as well as user schemas
11+
* Get a list of schemas in the database
1312
*/
14-
interface QueryParams {
15-
include_system_schemas?: string
16-
}
17-
18-
const router = Router()
19-
20-
router.get('/', async (req, res) => {
21-
try {
22-
const { data } = await RunQuery(req.headers.pg, schemas)
23-
const query: QueryParams = req.query
24-
const include_system_schemas = query?.include_system_schemas === 'true'
25-
let payload: Schemas.Schema[] = data
26-
if (!include_system_schemas) payload = removeSystemSchemas(data)
27-
28-
return res.status(200).json(payload)
29-
} catch (error) {
30-
console.log('throwing error', error)
31-
res.status(500).json({ error: 'Database error', status: 500 })
13+
export async function list(
14+
/** A Postgres connection string */
15+
connection: string,
16+
{
17+
/** If true, will include the system schemas */
18+
include_system_schemas = false,
19+
}: {
20+
include_system_schemas?: boolean
3221
}
33-
})
34-
35-
router.post('/', async (req, res) => {
22+
): /** Returns a list of schemas */
23+
Promise<{ data: Interfaces.Schema[]; error: null | Error }> {
3624
try {
37-
const name: string = req.body.name
38-
const owner: string = req.body.owner
39-
40-
// Create the schema
41-
const schemqQuery = createSchema(name, owner)
42-
await RunQuery(req.headers.pg, schemqQuery)
43-
44-
// Return fresh details
45-
const getSchema = selectSingleByName(name)
46-
const { data } = await RunQuery(req.headers.pg, getSchema)
47-
let schema: Schemas.Schema = data[0]
48-
return res.status(200).json(schema)
25+
let query = SQL``.append(allSchemasSql)
26+
if (!include_system_schemas) {
27+
query.append(`where schema_name not in (${defaultSchemasList})`)
28+
}
29+
const { data } = await RunQuery<Interfaces.Schema>(connection, query)
30+
return { data, error: null }
4931
} catch (error) {
50-
console.log('throwing error', error)
51-
res.status(500).json({ error: 'Database error', status: 500 })
32+
return { data: null, error }
5233
}
53-
})
34+
}
5435

55-
router.patch('/:id', async (req, res) => {
36+
/**
37+
* Get a single schema by its `oid`
38+
*/
39+
export async function byId(
40+
/** A Postgres connection string */
41+
connection: string,
42+
/** The schema `oid` */
43+
id: number
44+
): /** Returns a single schemas */
45+
Promise<{ data: Interfaces.Schema; error: null | Error }> {
5646
try {
57-
const id: number = parseInt(req.params.id)
58-
const name: string = req.body.name
59-
const owner: string = req.body.owner
60-
61-
// Get schema name
62-
const getSchema = selectSingleSql(id)
63-
const { data: getSchemaResults } = await RunQuery(req.headers.pg, getSchema)
64-
let previousSchema: Schemas.Schema = getSchemaResults[0]
65-
66-
// Update fields
67-
if (owner) {
68-
const updateOwner = alterSchemaOwner(previousSchema.name, owner)
69-
await RunQuery(req.headers.pg, updateOwner)
70-
}
71-
// NB: Run name updates last
72-
if (name) {
73-
const updateName = alterSchemaName(previousSchema.name, name)
74-
await RunQuery(req.headers.pg, updateName)
75-
}
76-
77-
// Return fresh details
78-
const { data: updatedResults } = await RunQuery(req.headers.pg, getSchema)
79-
let updated: Schemas.Schema = updatedResults[0]
80-
return res.status(200).json(updated)
47+
const query = SQL``.append(allSchemasSql).append(SQL` where nsp.oid = ${id}`)
48+
const { data } = await RunQuery<Interfaces.Schema>(connection, query)
49+
return { data: data[0], error: null }
8150
} catch (error) {
82-
console.log('throwing error', error)
83-
res.status(500).json({ error: 'Database error', status: 500 })
51+
return { data: null, error }
8452
}
85-
})
53+
}
8654

87-
router.delete('/:id', async (req, res) => {
55+
/**
56+
* Get a single schema by its name
57+
*/
58+
export async function byName (
59+
/** A Postgres connection string */
60+
connection: string,
61+
/** The schema name */
62+
name: string
63+
): /** Returns a single schemas */
64+
Promise<{ data: Interfaces.Schema; error: null | Error }> {
8865
try {
89-
const id = parseInt(req.params.id)
90-
const getNameQuery = selectSingleSql(id)
91-
const schema = (await RunQuery(req.headers.pg, getNameQuery)).data[0]
92-
93-
const cascade = req.query.cascade === 'true'
94-
const query = dropSchemaSqlize(schema.name, cascade)
95-
await RunQuery(req.headers.pg, query)
96-
97-
return res.status(200).json(schema)
66+
const query = SQL``.append(allSchemasSql).append(SQL` where schema_name = ${name}`)
67+
const { data } = await RunQuery<Interfaces.Schema>(connection, query)
68+
return { data: data[0], error: null }
9869
} catch (error) {
99-
console.log('throwing error', error)
100-
res.status(500).json({ error: 'Database error', status: 500 })
70+
return { data: null, error }
10171
}
102-
})
103-
104-
// Helpers
105-
const selectSingleSql = (id: number) => {
106-
const query = SQL``.append(schemas).append(SQL` where nsp.oid = ${id}`)
107-
return query
10872
}
109-
const selectSingleByName = (name: string) => {
110-
const query = SQL``.append(schemas).append(SQL` where schema_name = ${name}`)
111-
return query
112-
}
113-
const createSchema = (name: string, owner: string = 'postgres') => {
114-
const query = SQL``.append(`CREATE SCHEMA IF NOT EXISTS ${name} AUTHORIZATION ${owner}`)
115-
return query
116-
}
117-
const alterSchemaName = (previousName: string, newName: string) => {
118-
const query = SQL``.append(`ALTER SCHEMA ${previousName} RENAME TO ${newName}`)
119-
return query
120-
}
121-
const alterSchemaOwner = (schemaName: string, newOwner: string) => {
122-
const query = SQL``.append(`ALTER SCHEMA ${schemaName} OWNER TO ${newOwner}`)
123-
return query
124-
}
125-
const dropSchemaSqlize = (name: string, cascade: boolean) => {
126-
const query = `DROP SCHEMA ${format.ident(name)} ${cascade ? 'CASCADE' : 'RESTRICT'}`
127-
return query
128-
}
129-
const removeSystemSchemas = (data: Schemas.Schema[]) => {
130-
return data.filter((x) => !DEFAULT_SYSTEM_SCHEMAS.includes(x.name))
131-
}
132-
133-
export = router

src/lib/connectionPool.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ import { SQLStatement } from 'sql-template-strings'
66
pg.types.setTypeParser(20, 'text', parseInt)
77
const { Pool } = pg
88

9-
export const RunQuery = async (connectionString: any, sql: string|SQLStatement) => {
9+
export async function RunQuery<T>(
10+
connectionString: any,
11+
sql: string | SQLStatement
12+
): // Note once the refactor is completed we should remove "any" return
13+
/** Returns an array of table data */
14+
Promise<{ data: T[] | any; error: null | Error }> {
1015
const pool = new Pool({ connectionString })
1116
try {
1217
const results = await pool.query(sql)

src/routes/schemas.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { Router } from 'express'
2+
import format from 'pg-format'
3+
import SQL from 'sql-template-strings'
4+
import sqlTemplates = require('../lib/sql')
5+
import { RunQuery } from '../lib/connectionPool'
6+
import { Schemas } from '../lib/interfaces'
7+
8+
import { list, byId, byName } from '../api/schemas'
9+
10+
const { schemas } = sqlTemplates
11+
12+
/**
13+
* @param {boolean} [include_system_schemas=false] - Return system schemas as well as user schemas
14+
*/
15+
interface QueryParams {
16+
include_system_schemas?: string
17+
}
18+
19+
const router = Router()
20+
21+
router.get('/', async (req, res) => {
22+
try {
23+
let conn: string = req.headers.pg.toString()
24+
const query: QueryParams = req.query
25+
const include_system_schemas = query?.include_system_schemas === 'true'
26+
const { data, error } = await list(conn, { include_system_schemas })
27+
if (error) throw error
28+
else return res.status(200).json(data)
29+
} catch (error) {
30+
console.log('throwing error', error)
31+
res.status(500).json({ error: 'Database error', status: 500 })
32+
}
33+
})
34+
35+
router.post('/', async (req, res) => {
36+
try {
37+
let conn: string = req.headers.pg.toString()
38+
const name: string = req.body.name
39+
const owner: string = req.body.owner
40+
41+
// Create the schema
42+
const schemqQuery = createSchema(name, owner)
43+
await RunQuery(req.headers.pg, schemqQuery)
44+
45+
// Return fresh details
46+
let { data: schema, error } = await byName(conn, name)
47+
if (error) throw error
48+
else return res.status(200).json(schema)
49+
} catch (error) {
50+
console.log('throwing error', error)
51+
res.status(500).json({ error: 'Database error', status: 500 })
52+
}
53+
})
54+
55+
router.patch('/:id', async (req, res) => {
56+
try {
57+
let conn: string = req.headers.pg.toString()
58+
const id: number = +req.params.id
59+
const name: string = req.body.name
60+
const owner: string = req.body.owner
61+
62+
// Get schema name
63+
let { data: previousSchema, error } = await byId(conn, id)
64+
if (error) throw error
65+
66+
// Update fields
67+
if (owner) {
68+
const updateOwner = alterSchemaOwner(previousSchema.name, owner)
69+
await RunQuery(req.headers.pg, updateOwner)
70+
}
71+
// NB: Run name updates last
72+
if (name) {
73+
const updateName = alterSchemaName(previousSchema.name, name)
74+
await RunQuery(req.headers.pg, updateName)
75+
}
76+
77+
// Return fresh details
78+
let { data: updated, error: updateError } = await byId(conn, id)
79+
if (updateError) throw error
80+
return res.status(200).json(updated)
81+
} catch (error) {
82+
console.log('throwing error', error)
83+
res.status(500).json({ error: 'Database error', status: 500 })
84+
}
85+
})
86+
87+
router.delete('/:id', async (req, res) => {
88+
try {
89+
let conn: string = req.headers.pg.toString()
90+
const id: number = +req.params.id
91+
92+
let { data: schema, error } = await byId(conn, id)
93+
if (error) throw error
94+
95+
const cascade = req.query.cascade === 'true'
96+
const query = dropSchemaSqlize(schema.name, cascade)
97+
await RunQuery(req.headers.pg, query)
98+
99+
return res.status(200).json(schema)
100+
} catch (error) {
101+
console.log('throwing error', error)
102+
res.status(500).json({ error: 'Database error', status: 500 })
103+
}
104+
})
105+
106+
// Helpers
107+
const createSchema = (name: string, owner: string = 'postgres') => {
108+
const query = SQL``.append(`CREATE SCHEMA IF NOT EXISTS ${name} AUTHORIZATION ${owner}`)
109+
return query
110+
}
111+
const alterSchemaName = (previousName: string, newName: string) => {
112+
const query = SQL``.append(`ALTER SCHEMA ${previousName} RENAME TO ${newName}`)
113+
return query
114+
}
115+
const alterSchemaOwner = (schemaName: string, newOwner: string) => {
116+
const query = SQL``.append(`ALTER SCHEMA ${schemaName} OWNER TO ${newOwner}`)
117+
return query
118+
}
119+
const dropSchemaSqlize = (name: string, cascade: boolean) => {
120+
const query = `DROP SCHEMA ${format.ident(name)} ${cascade ? 'CASCADE' : 'RESTRICT'}`
121+
return query
122+
}
123+
124+
export = router

0 commit comments

Comments
 (0)