@@ -2,7 +2,22 @@ import { ident, literal } from 'pg-format'
2
2
import { DEFAULT_ROLES } from './constants'
3
3
import { rolesSql } from './sql'
4
4
import { PostgresMetaResult , PostgresRole } from './types'
5
-
5
+ export interface PostgresMetaRoleConfig {
6
+ // https://www.rfc-editor.org/rfc/rfc6902
7
+ op : 'remove' | 'add' | 'replace'
8
+ path : string
9
+ value ?: string
10
+ }
11
+ export function changeRoleConfig2Object ( config : string [ ] ) {
12
+ if ( ! config ) {
13
+ return null
14
+ }
15
+ return config . reduce ( ( acc : any , cur ) => {
16
+ const [ key , value ] = cur . split ( '=' )
17
+ acc [ key ] = value
18
+ return acc
19
+ } , { } )
20
+ }
6
21
export default class PostgresMetaRoles {
7
22
query : ( sql : string ) => Promise < PostgresMetaResult < any > >
8
23
37
52
if ( offset ) {
38
53
sql += ` OFFSET ${ offset } `
39
54
}
40
- return await this . query ( sql )
55
+ const result = await this . query ( sql )
56
+ if ( result . data ) {
57
+ result . data = result . data . map ( ( role : any ) => {
58
+ role . config = changeRoleConfig2Object ( role . config )
59
+ return role
60
+ } )
61
+ }
62
+ return result
41
63
}
42
64
43
65
async retrieve ( { id } : { id : number } ) : Promise < PostgresMetaResult < PostgresRole > >
@@ -52,11 +74,13 @@ WHERE
52
74
if ( id ) {
53
75
const sql = `${ rolesSql } WHERE oid = ${ literal ( id ) } ;`
54
76
const { data, error } = await this . query ( sql )
77
+
55
78
if ( error ) {
56
79
return { data, error }
57
80
} else if ( data . length === 0 ) {
58
81
return { data : null , error : { message : `Cannot find a role with ID ${ id } ` } }
59
82
} else {
83
+ data [ 0 ] . config = changeRoleConfig2Object ( data [ 0 ] . config )
60
84
return { data : data [ 0 ] , error }
61
85
}
62
86
} else if ( name ) {
67
91
} else if ( data . length === 0 ) {
68
92
return { data : null , error : { message : `Cannot find a role named ${ name } ` } }
69
93
} else {
94
+ data [ 0 ] . config = changeRoleConfig2Object ( data [ 0 ] . config )
70
95
return { data : data [ 0 ] , error }
71
96
}
72
97
} else {
89
114
member_of,
90
115
members,
91
116
admins,
117
+ config,
92
118
} : {
93
119
name : string
94
120
is_superuser ?: boolean
@@ -104,6 +130,7 @@ WHERE
104
130
member_of ?: string [ ]
105
131
members ?: string [ ]
106
132
admins ?: string [ ]
133
+ config ?: Record < string , string >
107
134
} ) : Promise < PostgresMetaResult < PostgresRole > > {
108
135
const isSuperuserClause = is_superuser ? 'SUPERUSER' : 'NOSUPERUSER'
109
136
const canCreateDbClause = can_create_db ? 'CREATEDB' : 'NOCREATEDB'
@@ -118,8 +145,20 @@ WHERE
118
145
const memberOfClause = member_of === undefined ? '' : `IN ROLE ${ member_of . join ( ',' ) } `
119
146
const membersClause = members === undefined ? '' : `ROLE ${ members . join ( ',' ) } `
120
147
const adminsClause = admins === undefined ? '' : `ADMIN ${ admins . join ( ',' ) } `
121
-
148
+ let configClause = ''
149
+ if ( config !== undefined ) {
150
+ configClause = Object . keys ( config )
151
+ . map ( ( k ) => {
152
+ const v = config [ k ]
153
+ if ( ! k || ! v ) {
154
+ return ''
155
+ }
156
+ return `ALTER ROLE ${ name } SET ${ k } = ${ v } ;`
157
+ } )
158
+ . join ( '\n' )
159
+ }
122
160
const sql = `
161
+ BEGIN;
123
162
CREATE ROLE ${ ident ( name ) }
124
163
WITH
125
164
${ isSuperuserClause }
134
173
${ validUntilClause }
135
174
${ memberOfClause }
136
175
${ membersClause }
137
- ${ adminsClause } ;`
176
+ ${ adminsClause } ;
177
+ ${ configClause ? configClause : '' }
178
+ COMMIT;`
138
179
const { error } = await this . query ( sql )
139
180
if ( error ) {
140
181
return { data : null , error }
156
197
connection_limit,
157
198
password,
158
199
valid_until,
200
+ config,
159
201
} : {
160
202
name ?: string
161
203
is_superuser ?: boolean
168
210
connection_limit ?: number
169
211
password ?: string
170
212
valid_until ?: string
213
+ config ?: PostgresMetaRoleConfig [ ]
171
214
}
172
215
) : Promise < PostgresMetaResult < PostgresRole > > {
173
216
const { data : old , error } = await this . retrieve ( { id } )
@@ -209,7 +252,27 @@ WITH
209
252
connection_limit === undefined ? '' : `CONNECTION LIMIT ${ connection_limit } `
210
253
const passwordClause = password === undefined ? '' : `PASSWORD ${ literal ( password ) } `
211
254
const validUntilClause = valid_until === undefined ? '' : `VALID UNTIL ${ literal ( valid_until ) } `
212
-
255
+ let configClause = ''
256
+ if ( config !== undefined ) {
257
+ const configSql = config . map ( ( c ) => {
258
+ const { op, path, value } = c
259
+ const k = path
260
+ const v = value || null
261
+ if ( ! k ) {
262
+ throw new Error ( `Invalid config value ${ value } ` )
263
+ }
264
+ switch ( op ) {
265
+ case 'add' :
266
+ case 'replace' :
267
+ return `ALTER ROLE ${ ident ( old ! . name ) } SET ${ ident ( k ) } = ${ literal ( v ) } ;`
268
+ case 'remove' :
269
+ return `ALTER ROLE ${ ident ( old ! . name ) } RESET ${ ident ( k ) } ;`
270
+ default :
271
+ throw new Error ( `Invalid config op ${ op } ` )
272
+ }
273
+ } )
274
+ configClause = configSql . filter ( Boolean ) . join ( '' )
275
+ }
213
276
// nameSql must be last
214
277
const sql = `
215
278
BEGIN;
@@ -224,6 +287,7 @@ BEGIN;
224
287
${ connectionLimitClause }
225
288
${ passwordClause }
226
289
${ validUntilClause } ;
290
+ ${ configClause ? configClause : '' }
227
291
${ nameSql }
228
292
COMMIT;`
229
293
{
0 commit comments