Skip to content

Commit 72ba8e9

Browse files
committed
feat: adds the ability to create and update a schema
1 parent 0b30a56 commit 72ba8e9

File tree

5 files changed

+99
-6
lines changed

5 files changed

+99
-6
lines changed

package-lock.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
"cors": "^2.8.5",
2525
"crypto-js": "^4.0.0",
2626
"express": "^4.17.1",
27-
"pg": "^7.18.2"
27+
"pg": "^7.18.2",
28+
"sql-template-strings": "^2.2.2"
2829
},
2930
"devDependencies": {
3031
"@types/cors": "^2.8.6",

src/api/schemas.ts

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { Router } from 'express'
2-
3-
import sql = require('../lib/sql')
4-
const { schemas } = sql
2+
import SQL from 'sql-template-strings'
3+
import sqlTemplates = require('../lib/sql')
54
import { RunQuery } from '../lib/connectionPool'
65
import { DEFAULT_SYSTEM_SCHEMAS } from '../lib/constants'
76
import { Schemas } from '../lib/interfaces'
87

8+
const { schemas } = sqlTemplates
9+
910
/**
1011
* @param {boolean} [includeSystemSchemas=false] - Return system schemas as well as user schemas
1112
*/
@@ -14,6 +15,7 @@ interface GetSchemasQueryParams {
1415
}
1516

1617
const router = Router()
18+
1719
router.get('/', async (req, res) => {
1820
try {
1921
const { data } = await RunQuery(req.headers.pg, schemas)
@@ -23,11 +25,81 @@ router.get('/', async (req, res) => {
2325

2426
return res.status(200).json(payload)
2527
} catch (error) {
26-
console.log('throwing error')
28+
console.log('throwing error', error)
29+
res.status(500).json({ error: 'Database error', status: 500 })
30+
}
31+
})
32+
router.post('/', async (req, res) => {
33+
try {
34+
const name: string = req.body.name
35+
const owner: string = req.body.owner
36+
37+
// Create the schema
38+
const schemqQuery = createSchema(name, owner)
39+
await RunQuery(req.headers.pg, schemqQuery)
40+
41+
// Return fresh details
42+
const getSchema = selectSingleByName(name)
43+
const { data } = await RunQuery(req.headers.pg, getSchema)
44+
let schema: Schemas.Schema = data[0]
45+
return res.status(200).json(schema)
46+
} catch (error) {
47+
console.log('throwing error', error)
48+
res.status(500).json({ error: 'Database error', status: 500 })
49+
}
50+
})
51+
router.patch('/:id', async (req, res) => {
52+
try {
53+
const id: number = parseInt(req.params.id)
54+
const name: string = req.body.name
55+
const owner: string = req.body.owner
56+
57+
// Get schema name
58+
const getSchema = selectSingleSql(id)
59+
const { data: getSchemaResults } = await RunQuery(req.headers.pg, getSchema)
60+
let previousSchema: Schemas.Schema = getSchemaResults[0]
61+
62+
// Update fields
63+
if (owner) {
64+
const updateOwner = alterSchemaOwner(previousSchema.name, owner)
65+
await RunQuery(req.headers.pg, updateOwner)
66+
}
67+
if (name) {
68+
const updateName = alterSchemaName(previousSchema.name, name)
69+
await RunQuery(req.headers.pg, updateName)
70+
}
71+
72+
// Return fresh details
73+
const { data: updatedSchemaResults } = await RunQuery(req.headers.pg, getSchema)
74+
let updatedSchema: Schemas.Schema = updatedSchemaResults[0]
75+
return res.status(200).json(updatedSchema)
76+
} catch (error) {
77+
console.log('throwing error', error)
2778
res.status(500).json({ error: 'Database error', status: 500 })
2879
}
2980
})
3081

82+
// Helpers
83+
const selectSingleSql = (id: number) => {
84+
const query = SQL``.append(schemas).append(SQL` where nsp.oid = ${id}`)
85+
return query
86+
}
87+
const selectSingleByName = (name: string) => {
88+
const query = SQL``.append(schemas).append(SQL` where schema_name = ${name}`)
89+
return query
90+
}
91+
const createSchema = (name: string, owner: string = 'postgres') => {
92+
const query = SQL``.append(`CREATE SCHEMA IF NOT EXISTS ${name} AUTHORIZATION ${owner}`)
93+
return query
94+
}
95+
const alterSchemaName = (previousName: string, newName: string) => {
96+
const query = SQL``.append(`ALTER SCHEMA ${previousName} RENAME TO ${newName}`)
97+
return query
98+
}
99+
const alterSchemaOwner = (schemaName: string, newOwner: string) => {
100+
const query = SQL``.append(`ALTER SCHEMA ${schemaName} OWNER TO ${newOwner}`)
101+
return query
102+
}
31103
const removeSystemSchemas = (data: Schemas.Schema[]) => {
32104
return data.filter((x) => !DEFAULT_SYSTEM_SCHEMAS.includes(x.name))
33105
}

src/lib/connectionPool.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import pg = require('pg')
2+
import { SQLStatement } from 'sql-template-strings'
23
// HACK: Number has 53 bits of precision, may overflow with bigint (64 bits).
34
// Maybe use BigInt?
45
// TODO: Use Date object for timestamptz?
56
pg.types.setTypeParser(20, 'text', parseInt)
67
const { Pool } = pg
78

8-
export const RunQuery = async (connectionString: any, sql: string) => {
9+
export const RunQuery = async (connectionString: any, sql: string|SQLStatement) => {
910
const pool = new Pool({ connectionString })
1011
try {
1112
const results = await pool.query(sql)

test/integration/index.spec.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const STATUS = {
88
SUCCESS: 200,
99
ERROR: 500,
1010
}
11+
const PUBLIC_SCHEMA_ID = 2200
1112

1213
console.log('Running tests on ', URL)
1314

@@ -98,6 +99,18 @@ describe('/schemas', () => {
9899
assert.equal(true, !!datum)
99100
assert.equal(true, !!included)
100101
})
102+
it('POST & PATCH', async () => {
103+
const res = await axios.post(`${URL}/schemas`, { name: 'api' })
104+
assert.equal('api', res.data.name)
105+
const newSchemaId = res.data.id
106+
const res2 = await axios.patch(`${URL}/schemas/${newSchemaId}`, { name: 'api_updated' })
107+
assert.equal('api_updated', res2.data.name)
108+
const res3 = await axios.patch(`${URL}/schemas/${newSchemaId}`, {
109+
name: 'api',
110+
owner: 'postgres',
111+
})
112+
assert.equal('api', res3.data.name)
113+
})
101114
})
102115
describe('/types', () => {
103116
it('GET', async () => {
@@ -197,6 +210,7 @@ describe('/extensions', () => {
197210
describe('/roles', () => {
198211
it('GET', async () => {
199212
const res = await axios.get(`${URL}/roles`)
213+
// console.log('res', res)
200214
const datum = res.data.find((x) => x.name == 'postgres')
201215
const hasSystemSchema = datum.grants.some((x) => x.schema == 'information_schema')
202216
const hasPublicSchema = datum.grants.some((x) => x.schema == 'public')

0 commit comments

Comments
 (0)