Skip to content
This repository was archived by the owner on Feb 4, 2025. It is now read-only.

Commit ab98564

Browse files
committed
refactor(server): improve database typing
1 parent 3b53703 commit ab98564

File tree

8 files changed

+49
-27
lines changed

8 files changed

+49
-27
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"@types/mailgun-js": "0.22.10",
6464
"@types/mustache": "4.0.1",
6565
"@types/nodemailer": "6.4.0",
66+
"@types/pg": "7.14.4",
6667
"@typescript-eslint/eslint-plugin": "3.7.0",
6768
"@typescript-eslint/parser": "3.8.0",
6869
"ava": "3.11.1",

server/.eslintrc.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ module.exports = {
2121
],
2222
plugins: [
2323
'@typescript-eslint'
24-
]
24+
],
25+
rules: {
26+
'@typescript-eslint/no-unnecessary-condition': 'error'
27+
}
2528
}, {
2629
files: ['.eslintrc.js'],
2730
parser: 'espree'

server/database/challenges.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,24 @@ export interface DatabaseChallenge {
77
}
88

99
export const getAllChallenges = (): Promise<DatabaseChallenge[]> => {
10-
return db.query('SELECT * FROM challenges')
10+
return db.query<DatabaseChallenge>('SELECT * FROM challenges')
1111
.then(res => res.rows)
1212
}
1313

1414
export const getChallengeById = ({ id }: Pick<DatabaseChallenge, 'id'>): Promise<DatabaseChallenge | undefined> => {
15-
return db.query('SELECT * FROM challenges WHERE id = $1', [id])
15+
return db.query<DatabaseChallenge>('SELECT * FROM challenges WHERE id = $1', [id])
1616
.then(res => res.rows[0])
1717
}
1818

1919
export const createChallenge = ({ id, data }: DatabaseChallenge): Promise<DatabaseChallenge> => {
20-
return db.query('INSERT INTO challenges ($1, $2) RETURNING *',
20+
return db.query<DatabaseChallenge>('INSERT INTO challenges ($1, $2) RETURNING *',
2121
[id, data]
2222
)
2323
.then(res => res.rows[0])
2424
}
2525

2626
export const removeChallengeById = ({ id }: Pick<DatabaseChallenge, 'id'>): Promise<DatabaseChallenge | undefined> => {
27-
return db.query('DELETE FROM challenges WHERE id = $1 RETURNING *', [id])
27+
return db.query<DatabaseChallenge>('DELETE FROM challenges WHERE id = $1 RETURNING *', [id])
2828
.then(res => res.rows[0])
2929
}
3030

server/database/members.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
import db from './db'
2+
import { User } from './users'
23

34
export interface UserMember {
45
id: string;
5-
userid: string; // FIXME: type via User type
6+
userid: User['id'];
67
email: string;
78
}
89

910
export const getMembers = ({ userid }: Pick<UserMember, 'userid'>): Promise<UserMember[]> => {
10-
return db.query('SELECT * FROM user_members WHERE userid = $1', [userid])
11+
return db.query<UserMember>('SELECT * FROM user_members WHERE userid = $1', [userid])
1112
.then(res => res.rows)
1213
}
1314

1415
export const makeMember = ({ id, userid, email }: UserMember): Promise<UserMember> => {
15-
return db.query('INSERT INTO user_members (id, userid, email) VALUES ($1, $2, $3) RETURNING *',
16+
return db.query<UserMember>('INSERT INTO user_members (id, userid, email) VALUES ($1, $2, $3) RETURNING *',
1617
[id, userid, email]
1718
)
1819
.then(res => res.rows[0])
1920
}
2021

2122
export const removeMember = ({ id, userid }: Pick<UserMember, 'id' | 'userid'>): Promise<UserMember | undefined> => {
22-
return db.query('DELETE FROM user_members WHERE id = $1 and userid = $2 RETURNING *', [id, userid])
23+
return db.query<UserMember>('DELETE FROM user_members WHERE id = $1 and userid = $2 RETURNING *', [id, userid])
2324
.then(res => res.rows[0])
2425
}

server/database/solves.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import db from './db'
22
import { Challenge } from '../challenges/types'
33
import { User } from './users'
4+
import { ExtractQueryType } from './util'
45

56
export interface Solve {
67
id: string;
@@ -10,27 +11,27 @@ export interface Solve {
1011
}
1112

1213
export const getAllSolves = (): Promise<Solve[]> => {
13-
return db.query('SELECT * FROM solves ORDER BY createdat ASC')
14+
return db.query<Solve>('SELECT * FROM solves ORDER BY createdat ASC')
1415
.then(res => res.rows)
1516
}
1617

1718
export const getSolvesByUserId = ({ userid }: Pick<Solve, 'userid'>): Promise<Solve[]> => {
18-
return db.query('SELECT * FROM solves WHERE userid = $1 ORDER BY createdat DESC', [userid])
19+
return db.query<Solve>('SELECT * FROM solves WHERE userid = $1 ORDER BY createdat DESC', [userid])
1920
.then(res => res.rows)
2021
}
2122

2223
export const getSolvesByChallId = ({ challengeid, limit, offset }: Pick<Solve, 'challengeid'> & { limit: number; offset: number; }): Promise<(Solve & Pick<User, 'name'>)[]> => {
23-
return db.query('SELECT solves.id, solves.userid, solves.createdat, users.name FROM solves INNER JOIN users ON solves.userid = users.id WHERE solves.challengeid=$1 ORDER BY solves.createdat ASC LIMIT $2 OFFSET $3', [challengeid, limit, offset])
24+
return db.query<ExtractQueryType<typeof getSolvesByChallId>>('SELECT solves.id, solves.userid, solves.createdat, users.name FROM solves INNER JOIN users ON solves.userid = users.id WHERE solves.challengeid=$1 ORDER BY solves.createdat ASC LIMIT $2 OFFSET $3', [challengeid, limit, offset])
2425
.then(res => res.rows)
2526
}
2627

2728
export const getSolveByUserIdAndChallId = ({ userid, challengeid }: Pick<Solve, 'userid' | 'challengeid'>): Promise<Solve | undefined> => {
28-
return db.query('SELECT * FROM solves WHERE userid = $1 AND challengeid = $2 ORDER BY createdat DESC', [userid, challengeid])
29+
return db.query<Solve>('SELECT * FROM solves WHERE userid = $1 AND challengeid = $2 ORDER BY createdat DESC', [userid, challengeid])
2930
.then(res => res.rows[0])
3031
}
3132

3233
export const newSolve = ({ id, userid, challengeid, createdat }: Solve): Promise<Solve> => {
33-
return db.query('INSERT INTO solves (id, challengeid, userid, createdat) VALUES ($1, $2, $3, $4) RETURNING *', [id, challengeid, userid, createdat])
34+
return db.query<Solve>('INSERT INTO solves (id, challengeid, userid, createdat) VALUES ($1, $2, $3, $4) RETURNING *', [id, challengeid, userid, createdat])
3435
.then(res => res.rows[0])
3536
}
3637

server/database/users.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import db from './db'
22
import * as util from '../util'
33
import config, { ServerConfig } from '../config/server'
44
import { DivisionACLError } from '../errors'
5+
import { ExtractQueryType } from './util'
56

67
export interface User {
78
id: string;
@@ -13,67 +14,67 @@ export interface User {
1314
}
1415

1516
export const getAllUsers = (): Promise<Pick<User, 'id' | 'name' | 'division'>[]> => {
16-
return db.query('SELECT id, name, division FROM users')
17+
return db.query<ExtractQueryType<typeof getAllUsers>>('SELECT id, name, division FROM users')
1718
.then(res => res.rows)
1819
}
1920

2021
export const getUserById = ({ id }: Pick<User, 'id'>): Promise<User | undefined> => {
21-
return db.query('SELECT * FROM users WHERE id = $1', [id])
22+
return db.query<User>('SELECT * FROM users WHERE id = $1', [id])
2223
.then(res => res.rows[0])
2324
}
2425

2526
export const getUserByEmail = ({ email }: Pick<User, 'email'>): Promise<User | undefined> => {
26-
return db.query('SELECT * FROM users WHERE email = $1', [email])
27+
return db.query<User>('SELECT * FROM users WHERE email = $1', [email])
2728
.then(res => res.rows[0])
2829
}
2930

3031
export const getUserByCtftimeId = ({ ctftimeId }: Pick<User, 'ctftimeId'>): Promise<User | undefined> => {
31-
return db.query('SELECT * FROM users WHERE ctftime_id = $1', [ctftimeId])
32+
return db.query<User>('SELECT * FROM users WHERE ctftime_id = $1', [ctftimeId])
3233
.then(res => res.rows[0])
3334
}
3435

3536
export const getUserByIdAndEmail = ({ id, email }: Pick<User, 'id' | 'email'>): Promise<User | undefined> => {
36-
return db.query('SELECT * FROM users WHERE id = $1 AND email = $2', [id, email])
37+
return db.query<User>('SELECT * FROM users WHERE id = $1 AND email = $2', [id, email])
3738
.then(res => res.rows[0])
3839
}
3940

4041
export const getUserByNameOrEmail = ({ name, email }: Pick<User, 'name' | 'email'>): Promise<User | undefined> => {
41-
return db.query('SELECT * FROM users WHERE name = $1 OR email = $2', [name, email])
42+
return db.query<User>('SELECT * FROM users WHERE name = $1 OR email = $2', [name, email])
4243
.then(res => res.rows[0])
4344
}
4445

4546
export const getUserByNameOrCtftimeId = ({ name, ctftimeId }: Pick<User, 'name' | 'ctftimeId'>): Promise<User | undefined> => {
46-
return db.query('SELECT * FROM users WHERE name = $1 OR ctftime_id = $2', [name, ctftimeId])
47+
return db.query<User>('SELECT * FROM users WHERE name = $1 OR ctftime_id = $2', [name, ctftimeId])
4748
.then(res => res.rows[0])
4849
}
4950

5051
export const removeUserByEmail = ({ email }: Pick<User, 'email'>): Promise<User | undefined> => {
51-
return db.query('DELETE FROM users WHERE email = $1 RETURNING *', [email])
52+
return db.query<User>('DELETE FROM users WHERE email = $1 RETURNING *', [email])
5253
.then(res => res.rows[0])
5354
}
5455

5556
export const removeUserById = ({ id }: Pick<User, 'id'>): Promise<User | undefined> => {
56-
return db.query('DELETE FROM users WHERE id = $1 RETURNING *', [id])
57+
return db.query<User>('DELETE FROM users WHERE id = $1 RETURNING *', [id])
5758
.then(res => res.rows[0])
5859
}
5960

6061
export const makeUser = ({ id, name, email, division, ctftimeId, perms }: User): Promise<User> => {
6162
if (config.email && config.divisionACLs && !util.restrict.divisionAllowed(email, division)) {
6263
throw new DivisionACLError()
6364
}
64-
return db.query('INSERT INTO users (id, name, email, division, ctftime_id, perms) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *',
65+
return db.query<User>('INSERT INTO users (id, name, email, division, ctftime_id, perms) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *',
6566
[id, name, email, division, ctftimeId, perms]
6667
)
6768
.then(res => res.rows[0])
6869
}
6970

7071
export const removeCtftimeId = ({ id }: Pick<User, 'id'>): Promise<User | undefined> => {
71-
return db.query('UPDATE users SET ctftime_id = NULL WHERE id = $1 AND ctftime_id IS NOT NULL RETURNING *', [id])
72+
return db.query<User>('UPDATE users SET ctftime_id = NULL WHERE id = $1 AND ctftime_id IS NOT NULL RETURNING *', [id])
7273
.then(res => res.rows[0])
7374
}
7475

7576
export const removeEmail = ({ id }: Pick<User, 'id'>): Promise<User | undefined> => {
76-
return db.query('UPDATE users SET email = NULL WHERE id = $1 AND email IS NOT NULL RETURNING *', [id])
77+
return db.query<User>('UPDATE users SET email = NULL WHERE id = $1 AND email IS NOT NULL RETURNING *', [id])
7778
.then(res => res.rows[0])
7879
}
7980

@@ -92,7 +93,7 @@ export const updateUser = async ({ id, name, email, division, ctftimeId, perms }
9293
throw new DivisionACLError()
9394
}
9495
}
95-
return db.query(`
96+
return db.query<User>(`
9697
UPDATE users SET
9798
name = COALESCE($1, name),
9899
email = COALESCE($2, email),

server/database/util.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { PromiseValue } from 'type-fest'
2+
3+
type ArrayValue<Arr> = Arr extends (infer Val)[] ? Val : Arr
4+
5+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
6+
export type ExtractQueryType<FuncType extends (...args: any) => any> =
7+
NonNullable<ArrayValue<PromiseValue<ReturnType<FuncType>>>>

yarn.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1279,6 +1279,14 @@
12791279
resolved "https://registry.yarnpkg.com/@types/pg-types/-/pg-types-1.11.5.tgz#1eebbe62b6772fcc75c18957a90f933d155e005b"
12801280
integrity sha512-L8ogeT6vDzT1vxlW3KITTCt+BVXXVkLXfZ/XNm6UqbcJgxf+KPO7yjWx7dQQE8RW07KopL10x2gNMs41+IkMGQ==
12811281

1282+
1283+
version "7.14.4"
1284+
resolved "https://registry.yarnpkg.com/@types/pg/-/pg-7.14.4.tgz#15cfcfd9300f94fd44e6191a1b0ba18d2de209f6"
1285+
integrity sha512-yCKVMCcFPZSFHGg+8qjY368uf3ruyDBPjxvOU2ZcGa/vRFo5Ti5Y6z6vl+2hxtwm9VMWUGb6TWkIk3cIV8C0Cw==
1286+
dependencies:
1287+
"@types/node" "*"
1288+
"@types/pg-types" "*"
1289+
12821290
"@types/pg@^7.4.0":
12831291
version "7.14.3"
12841292
resolved "https://registry.yarnpkg.com/@types/pg/-/pg-7.14.3.tgz#eb166e4f4287923890b10ed20371f937938cb995"

0 commit comments

Comments
 (0)