Skip to content

Commit 9c1501d

Browse files
committed
feat: CRUD on /extensions
1 parent 9ec8b43 commit 9c1501d

File tree

3 files changed

+142
-6
lines changed

3 files changed

+142
-6
lines changed

src/api/extensions.ts

Lines changed: 109 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,123 @@
11
import { Router } from 'express'
2-
2+
import SQL from 'sql-template-strings'
3+
import sqlTemplates = require('../lib/sql')
4+
const { extensions } = sqlTemplates
35
import { RunQuery } from '../lib/connectionPool'
4-
import sql = require('../lib/sql')
5-
const { extensions } = sql
66

77
const router = Router()
8+
89
router.get('/', async (req, res) => {
910
try {
10-
const { data } = await RunQuery(req.headers.pg, extensions)
11+
const getExtensionsQuery = getExtensionsSqlize(extensions)
12+
const { data } = await RunQuery(req.headers.pg, getExtensionsQuery)
1113
return res.status(200).json(data)
1214
} catch (error) {
1315
console.log('throwing error')
1416
res.status(500).json({ error: 'Database error', status: 500 })
1517
}
1618
})
1719

20+
router.post('/', async (req, res) => {
21+
try {
22+
const query = createExtensionSqlize(req.body)
23+
await RunQuery(req.headers.pg, query)
24+
25+
const getExtensionQuery = singleExtensionSqlize(extensions, req.body.name)
26+
const extension = (await RunQuery(req.headers.pg, getExtensionQuery)).data[0]
27+
28+
return res.status(200).json(extension)
29+
} catch (error) {
30+
console.log('throwing error')
31+
res.status(500).json({ error: 'Database error', status: 500 })
32+
}
33+
})
34+
35+
router.patch('/:name', async (req, res) => {
36+
try {
37+
const name = req.params.name
38+
req.body.name = name
39+
40+
const alterExtensionQuery = alterExtensionSqlize(req.body)
41+
await RunQuery(req.headers.pg, alterExtensionQuery)
42+
43+
const getExtensionQuery = singleExtensionSqlize(extensions, name)
44+
const updated = (await RunQuery(req.headers.pg, getExtensionQuery)).data[0]
45+
46+
return res.status(200).json(updated)
47+
} catch (error) {
48+
console.log('throwing error')
49+
res.status(500).json({ error: 'Database error', status: 500 })
50+
}
51+
})
52+
53+
router.delete('/:name', async (req, res) => {
54+
try {
55+
const name = req.params.name
56+
const cascade = req.query.cascade === 'true'
57+
58+
const getExtensionQuery = singleExtensionSqlize(extensions, name)
59+
const deleted = (await RunQuery(req.headers.pg, getExtensionQuery)).data[0]
60+
61+
const query = dropExtensionSqlize(name, cascade)
62+
await RunQuery(req.headers.pg, query)
63+
64+
return res.status(200).json(deleted)
65+
} catch (error) {
66+
console.log('throwing error')
67+
res.status(500).json({ error: 'Database error', status: 500 })
68+
}
69+
})
70+
71+
const getExtensionsSqlize = (extensions: string) => {
72+
return `${extensions} ORDER BY name ASC`
73+
}
74+
const createExtensionSqlize = ({
75+
name,
76+
schema,
77+
version,
78+
cascade = false,
79+
}: {
80+
name: string
81+
schema?: string
82+
version?: string
83+
cascade?: boolean
84+
}) => {
85+
return `
86+
CREATE EXTENSION "${name}"
87+
${schema === undefined ? '' : `SCHEMA ${schema}`}
88+
${version === undefined ? '' : `VERSION '${version}'`}
89+
${cascade ? 'CASCADE' : ''}`
90+
}
91+
const singleExtensionSqlize = (extensions: string, name: string) => {
92+
return SQL``.append(extensions).append(SQL` WHERE name = ${name}`)
93+
}
94+
const alterExtensionSqlize = ({
95+
name,
96+
update = false,
97+
version,
98+
schema,
99+
}: {
100+
name: string
101+
update?: boolean
102+
version?: string
103+
schema?: string
104+
}) => {
105+
let updateSql = ''
106+
if (update) {
107+
updateSql = `ALTER EXTENSION "${name}" UPDATE ${version === undefined ? '' : version};`
108+
}
109+
const schemaSql = schema === undefined ? '' : `ALTER EXTENSION "${name}" SET SCHEMA "${schema}";`
110+
111+
return `
112+
BEGIN;
113+
${updateSql}
114+
${schemaSql}
115+
COMMIT;`
116+
}
117+
const dropExtensionSqlize = (name: string, cascade: boolean) => {
118+
return `
119+
DROP EXTENSION ${name}
120+
${cascade ? 'CASCADE' : 'RESTRICT'}`
121+
}
122+
18123
export = router

src/lib/sql/extensions.sql

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,3 @@ SELECT
55
installed_version
66
FROM
77
pg_available_extensions
8-
ORDER BY
9-
name ASC

test/integration/index.spec.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ describe('/tables', async () => {
321321
await axios.delete(`${URL}/tables/${newTable.id}`)
322322
})
323323
})
324+
// TODO: Test for schema (currently checked manually). Need a different SQL template.
324325
describe('/extensions', () => {
325326
it('GET', async () => {
326327
const res = await axios.get(`${URL}/extensions`)
@@ -329,6 +330,38 @@ describe('/extensions', () => {
329330
assert.equal(res.status, STATUS.SUCCESS)
330331
assert.equal(true, !!datum)
331332
})
333+
it('POST', async () => {
334+
const { data: extSchema } = await axios.post(`${URL}/schemas`, { name: 'extensions' })
335+
await axios.post(`${URL}/extensions`, { name: 'hstore', schema: 'extensions', version: '1.4' })
336+
337+
const { data: extensions } = await axios.get(`${URL}/extensions`)
338+
const newExtension = extensions.find((ext) => ext.name === 'hstore')
339+
assert.equal(newExtension.installed_version, '1.4')
340+
341+
await axios.delete(`${URL}/extensions/hstore`)
342+
await axios.delete(`${URL}/schemas/${extSchema.id}`)
343+
})
344+
it('PATCH', async () => {
345+
const { data: extSchema } = await axios.post(`${URL}/schemas`, { name: 'extensions' })
346+
await axios.post(`${URL}/extensions`, { name: 'hstore', version: '1.4' })
347+
348+
await axios.patch(`${URL}/extensions/hstore`, { update: true, schema: 'extensions' })
349+
350+
const { data: extensions } = await axios.get(`${URL}/extensions`)
351+
const updatedExtension = extensions.find((ext) => ext.name === 'hstore')
352+
assert.equal(updatedExtension.installed_version, updatedExtension.default_version)
353+
354+
await axios.delete(`${URL}/extensions/hstore`)
355+
await axios.delete(`${URL}/schemas/${extSchema.id}`)
356+
})
357+
it('DELETE', async () => {
358+
await axios.post(`${URL}/extensions`, { name: 'hstore', version: '1.4' })
359+
360+
await axios.delete(`${URL}/extensions/hstore`)
361+
const { data: extensions } = await axios.get(`${URL}/extensions`)
362+
const deletedExtension = extensions.find((ext) => ext.name === 'hstore')
363+
assert.equal(deletedExtension.installed_version, null)
364+
})
332365
})
333366
describe('/roles', () => {
334367
it('GET', async () => {

0 commit comments

Comments
 (0)