Skip to content

Commit 31c32d1

Browse files
committed
refactor: Simplifies the /table interface - column changes can exist in their own file
1 parent b79a4d4 commit 31c32d1

File tree

5 files changed

+170
-76
lines changed

5 files changed

+170
-76
lines changed

src/api/columns.ts

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,81 @@
11
import { Router } from 'express'
2-
32
import { RunQuery } from '../lib/connectionPool'
43
import sql = require('../lib/sql')
54
const { columns } = sql
5+
import { DEFAULT_SYSTEM_SCHEMAS } from '../lib/constants'
6+
import { Tables } from '../lib/interfaces'
67

78
const router = Router()
89
router.get('/', async (req, res) => {
910
try {
1011
const { data } = await RunQuery(req.headers.pg, columns)
11-
return res.status(200).json(data)
12+
const query: Fetch.QueryParams = req.query
13+
let payload: Tables.Column[] = data
14+
if (!query?.includeSystemSchemas) payload = removeSystemSchemas(data)
15+
return res.status(200).json(payload)
16+
} catch (error) {
17+
console.log('throwing error')
18+
res.status(500).send('Database error.')
19+
}
20+
})
21+
router.post('/', async (req, res) => {
22+
try {
23+
} catch (error) {
24+
console.log('throwing error')
25+
res.status(500).send('Database error.')
26+
}
27+
})
28+
router.patch('/:id', async (req, res) => {
29+
try {
30+
} catch (error) {
31+
console.log('throwing error')
32+
res.status(500).send('Database error.')
33+
}
34+
})
35+
router.delete('/:id', async (req, res) => {
36+
try {
1237
} catch (error) {
1338
console.log('throwing error')
1439
res.status(500).send('Database error.')
1540
}
1641
})
1742

1843
export = router
44+
45+
const removeSystemSchemas = (data: Tables.Column[]) => {
46+
return data.filter((x) => !DEFAULT_SYSTEM_SCHEMAS.includes(x.schema))
47+
}
48+
const newColumnSql = ({
49+
name,
50+
default_value,
51+
is_identity = false,
52+
is_nullable = true,
53+
is_primary_key = false,
54+
data_type,
55+
}: {
56+
name: string
57+
default_value?: string
58+
is_identity?: boolean
59+
is_nullable?: boolean
60+
is_primary_key?: boolean
61+
data_type: string
62+
}) => {
63+
return `
64+
${name} ${data_type}
65+
${default_value === undefined ? '' : `DEFAULT ${default_value}`}
66+
${is_identity ? 'GENERATED BY DEFAULT AS IDENTITY' : ''}
67+
${is_nullable ? '' : 'NOT NULL'}
68+
${is_primary_key ? 'PRIMARY KEY' : ''}`
69+
}
70+
71+
/**
72+
* Types
73+
*/
74+
namespace Fetch {
75+
/**
76+
* @param {boolean} [includeSystemSchemas=false] - Return system schemas as well as user schemas
77+
*/
78+
export interface QueryParams {
79+
includeSystemSchemas?: boolean
80+
}
81+
}

src/api/schemas.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,16 @@ router.patch('/:id', async (req, res) => {
6464
const updateOwner = alterSchemaOwner(previousSchema.name, owner)
6565
await RunQuery(req.headers.pg, updateOwner)
6666
}
67+
// NB: Run name updates last
6768
if (name) {
6869
const updateName = alterSchemaName(previousSchema.name, name)
6970
await RunQuery(req.headers.pg, updateName)
7071
}
7172

7273
// 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)
74+
const { data: updatedResults } = await RunQuery(req.headers.pg, getSchema)
75+
let updated: Schemas.Schema = updatedResults[0]
76+
return res.status(200).json(updated)
7677
} catch (error) {
7778
console.log('throwing error', error)
7879
res.status(500).json({ error: 'Database error', status: 500 })

src/api/tables.ts

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Router } from 'express'
2-
3-
import sql = require('../lib/sql')
4-
const { columns, grants, primary_keys, relationships, tables } = sql
5-
import { coalesceRowsToArray, formatColumns } from '../lib/helpers'
2+
import SQL from 'sql-template-strings'
3+
import sqlTemplates = require('../lib/sql')
4+
const { columns, grants, primary_keys, relationships, tables } = sqlTemplates
5+
import { coalesceRowsToArray } from '../lib/helpers'
66
import { RunQuery } from '../lib/connectionPool'
77
import { DEFAULT_SYSTEM_SCHEMAS } from '../lib/constants'
88
import { Tables } from '../lib/interfaces'
@@ -48,18 +48,47 @@ FROM
4848
})
4949
router.post('/', async (req, res) => {
5050
try {
51-
const { schema = 'public', name, columns, primary_keys = [] } = req.body as {
51+
const { schema = 'public', name } = req.body as {
5252
schema?: string
5353
name: string
54-
columns: Tables.Column[]
55-
primary_keys?: Tables.PrimaryKey[]
5654
}
57-
const sql = `
58-
CREATE TABLE ${schema}.${name} (
59-
${formatColumns({ columns, primary_keys })}
60-
)`
61-
const { data } = await RunQuery(req.headers.pg, sql)
62-
return res.status(200).json(data)
55+
56+
// Create the table
57+
const createTableSql = createTable(name, schema)
58+
await RunQuery(req.headers.pg, createTableSql)
59+
60+
// Return fresh details
61+
const getTable = selectSingleByName(schema, name)
62+
const { data: newTableResults } = await RunQuery(req.headers.pg, getTable)
63+
let newTable: Tables.Table = newTableResults[0]
64+
return res.status(200).json(newTable)
65+
} catch (error) {
66+
// For this one, we always want to give back the error to the customer
67+
console.log('Soft error!', error)
68+
res.status(200).json([{ error: error.toString() }])
69+
}
70+
})
71+
router.patch('/:id', async (req, res) => {
72+
try {
73+
const id: number = parseInt(req.params.id)
74+
const name: string = req.body.name
75+
76+
// Get table
77+
const getTableSql = selectSingleSql(id)
78+
const { data: getTableResults } = await RunQuery(req.headers.pg, getTableSql)
79+
let previousTable: Tables.Table = getTableResults[0]
80+
81+
// Update fields
82+
// NB: Run name updates last
83+
if (name) {
84+
const updateName = alterTableName(previousTable.name, name, previousTable.schema)
85+
await RunQuery(req.headers.pg, updateName)
86+
}
87+
88+
// Return fresh details
89+
const { data: updatedResults } = await RunQuery(req.headers.pg, getTableSql)
90+
let updated: Tables.Table = updatedResults[0]
91+
return res.status(200).json(updated)
6392
} catch (error) {
6493
// For this one, we always want to give back the error to the customer
6594
console.log('Soft error!', error)
@@ -69,14 +98,27 @@ CREATE TABLE ${schema}.${name} (
6998

7099
export = router
71100

101+
const selectSingleSql = (id: number) => {
102+
return SQL``.append(tables).append(SQL` and c.oid = ${id}`)
103+
}
104+
const selectSingleByName = (schema: string, name: string) => {
105+
return SQL``.append(tables).append(SQL` and table_schema = ${schema} and table_name = ${name}`)
106+
}
107+
const createTable = (name: string, schema: string = 'postgres') => {
108+
const query = SQL``.append(`CREATE TABLE ${schema}.${name} ()`)
109+
return query
110+
}
111+
const alterTableName = (previousName: string, newName: string, schema: string) => {
112+
const query = SQL``.append(`ALTER SCHEMA ${previousName} RENAME TO ${newName}`)
113+
return query
114+
}
72115
const removeSystemSchemas = (data: Tables.Table[]) => {
73116
return data.filter((x) => !DEFAULT_SYSTEM_SCHEMAS.includes(x.schema))
74117
}
75118

76119
/**
77120
* Types
78121
*/
79-
80122
namespace Fetch {
81123
/**
82124
* @param {boolean} [includeSystemSchemas=false] - Return system schemas as well as user schemas

src/lib/helpers.ts

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { Tables } from '../lib/interfaces'
21

32
export const coalesceRowsToArray = (source: string, joinQuery: string) => {
43
return `
@@ -12,36 +11,3 @@ COALESCE(
1211
'[]'
1312
) AS ${source}`
1413
}
15-
16-
export const formatColumns = ({
17-
columns,
18-
primary_keys,
19-
}: {
20-
columns: Tables.Column[]
21-
primary_keys: Tables.PrimaryKey[]
22-
}) => {
23-
const pkey_columns = primary_keys.map((primary_key) => primary_key.name)
24-
return columns
25-
.map((column) => {
26-
const {
27-
name,
28-
default_value,
29-
is_identity = false,
30-
is_nullable = true,
31-
data_type,
32-
} = column as {
33-
name: string
34-
default_value?: string
35-
is_identity?: boolean
36-
is_nullable?: boolean
37-
data_type: string
38-
}
39-
return `
40-
${name} ${data_type}
41-
${default_value === undefined ? '' : `DEFAULT ${default_value}`}
42-
${is_identity ? 'GENERATED BY DEFAULT AS IDENTITY' : ''}
43-
${is_nullable ? '' : 'NOT NULL'}
44-
${pkey_columns.includes(name) ? 'PRIMARY KEY' : ''}`
45-
})
46-
.join(',')
47-
}

test/integration/index.spec.js

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -132,16 +132,16 @@ describe('/types', () => {
132132
assert.equal(true, !!included)
133133
})
134134
})
135-
describe('/tables', async () => {
136-
it('GET', async () => {
135+
describe('/tables & /columns', async () => {
136+
it('GET /tables', async () => {
137137
const tables = await axios.get(`${URL}/tables`)
138138
const datum = tables.data.find((x) => `${x.schema}.${x.name}` === 'public.users')
139139
const notIncluded = tables.data.find((x) => `${x.schema}.${x.name}` === 'pg_catalog.pg_type')
140140
assert.equal(tables.status, STATUS.SUCCESS)
141141
assert.equal(true, !!datum)
142142
assert.equal(true, !notIncluded)
143143
})
144-
it('should return the columns', async () => {
144+
it('/tables should return the columns', async () => {
145145
const tables = await axios.get(`${URL}/tables`)
146146
const datum = tables.data.find((x) => `${x.schema}.${x.name}` === 'public.users')
147147
const idColumn = datum.columns.find((x) => x.name === 'id')
@@ -153,12 +153,12 @@ describe('/tables', async () => {
153153
assert.equal(idColumn.is_identity, true)
154154
assert.equal(nameColumn.is_identity, false)
155155
})
156-
it('should return the grants', async () => {
156+
it('/tables should return the grants', async () => {
157157
const tables = await axios.get(`${URL}/tables`)
158158
const datum = tables.data.find((x) => `${x.schema}.${x.name}` === 'public.users')
159159
assert.equal(datum.grants.length > 0, true)
160160
})
161-
it('should return the relationships', async () => {
161+
it('/tables should return the relationships', async () => {
162162
const tables = await axios.get(`${URL}/tables`)
163163
const datum = tables.data.find((x) => `${x.schema}.${x.name}` === 'public.users')
164164
const relationships = datum.relationships
@@ -169,32 +169,54 @@ describe('/tables', async () => {
169169
assert.equal(true, relationship.target_table_schema === 'public')
170170
assert.equal(true, relationship.target_table_name === 'users')
171171
})
172-
it('GET with system tables', async () => {
172+
it('GET /tabls with system tables', async () => {
173173
const res = await axios.get(`${URL}/tables?includeSystemSchemas=true`)
174174
const included = res.data.find((x) => `${x.schema}.${x.name}` === 'pg_catalog.pg_type')
175175
assert.equal(res.status, STATUS.SUCCESS)
176176
assert.equal(true, !!included)
177177
})
178-
it('POST', async () => {
179-
await axios.post(`${URL}/tables`, {
178+
it('GET /columns', async () => {
179+
const res = await axios.get(`${URL}/columns`)
180+
// console.log('res.data', res.data)
181+
const datum = res.data.find((x) => x.schema == 'public')
182+
const notIncluded = res.data.find((x) => x.schema == 'pg_catalog')
183+
assert.equal(res.status, STATUS.SUCCESS)
184+
assert.equal(true, !!datum)
185+
assert.equal(true, !notIncluded)
186+
})
187+
it('GET /columns with system types', async () => {
188+
const res = await axios.get(`${URL}/columns?includeSystemSchemas=true`)
189+
// console.log('res.data', res.data)
190+
const datum = res.data.find((x) => x.schema == 'public')
191+
const included = res.data.find((x) => x.schema == 'pg_catalog')
192+
assert.equal(res.status, STATUS.SUCCESS)
193+
assert.equal(true, !!datum)
194+
assert.equal(true, !!included)
195+
})
196+
it('POST /tables should create a table', async () => {
197+
await axios.post(`${URL}/query`, { query: 'DROP TABLE IF EXISTS public.test' })
198+
let {data: newTable} = await axios.post(`${URL}/tables`, {
180199
schema: 'public',
181200
name: 'test',
182-
columns: [
183-
{ name: 'id', is_identity: true, is_nullable: false, data_type: 'bigint' },
184-
{ name: 'data', data_type: 'text' },
185-
],
186-
primary_keys: ['id'],
201+
// columns: [
202+
// { name: 'id', is_identity: true, is_nullable: false, data_type: 'bigint' },
203+
// { name: 'data', data_type: 'text' },
204+
// ],
205+
// primary_keys: ['id'],
187206
})
188-
const { data: tables } = await axios.get(`${URL}/tables`)
189-
const test = tables.find((table) => `${table.schema}.${table.name}` === 'public.test')
190-
const id = test.columns.find((column) => column.name === 'id')
191-
const data = test.columns.find((column) => column.name === 'data')
192-
assert.equal(id.is_identity, true)
193-
assert.equal(id.is_nullable, false)
194-
assert.equal(id.data_type, 'bigint')
195-
assert.equal(data.is_identity, false)
196-
assert.equal(data.is_nullable, true)
197-
assert.equal(data.data_type, 'text')
207+
// console.log('newTable', newTable)
208+
const newTableId = newTable.id
209+
assert.equal(newTableId > 0, true)
210+
// const { data: tables } = await axios.get(`${URL}/tables`)
211+
// const test = tables.find((table) => `${table.schema}.${table.name}` === 'public.test')
212+
// const id = test.columns.find((column) => column.name === 'id')
213+
// const data = test.columns.find((column) => column.name === 'data')
214+
// assert.equal(id.is_identity, true)
215+
// assert.equal(id.is_nullable, false)
216+
// assert.equal(id.data_type, 'bigint')
217+
// assert.equal(data.is_identity, false)
218+
// assert.equal(data.is_nullable, true)
219+
// assert.equal(data.data_type, 'text')
198220
await axios.post(`${URL}/query`, { query: 'DROP TABLE public.test' })
199221
})
200222
})

0 commit comments

Comments
 (0)