Skip to content

Commit 400c12a

Browse files
authored
Release v3.0.0-alpha.8 (#35)
- lib/zone: add limit option - lib/nameserver.js: handle null fields from DB - routes/zone: report zone name on validation failure
1 parent 23ce6e2 commit 400c12a

10 files changed

Lines changed: 98 additions & 17 deletions

File tree

.release

Submodule .release updated 1 file

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
66

77
### Unreleased
88

9+
### [3.0.0-alpha.8] - 2026-03-14
10+
11+
- lib/zone: add limit option
12+
- lib/nameserver.js: handle null fields from DB
13+
- routes/zone: report zone name on validation failure
14+
915
### [3.0.0-alpha.7] - 2026-03-13
1016

1117
- fixes
@@ -36,7 +42,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
3642
- feat(lib/nameserver): added, with tests
3743
- feat(routes/nameserver): added, with tests
3844

39-
### 3.0.0-alpha.3
45+
### [3.0.0-alpha.3]
4046

4147
- routes/permission: added GET, POST, DELETE
4248
- permission.get: default search with deleted=0
@@ -55,3 +61,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
5561
[3.0.0-alpha.5]: https://github.com/NicTool/api/releases/tag/v3.0.0-alpha.5
5662
[3.0.0-alpha.6]: https://github.com/NicTool/api/releases/tag/v3.0.0-alpha.6
5763
[3.0.0-alpha.7]: https://github.com/NicTool/api/releases/tag/v3.0.0-alpha.7
64+
[3.0.0-alpha.8]: https://github.com/NicTool/api/releases/tag/v3.0.0-alpha.8

CONTRIBUTORS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
This handcrafted artisanal software is brought to you by:
44

5-
| <img height="80" src="https://avatars.githubusercontent.com/u/261635?v=4"><br><a href="https://github.com/msimerson">msimerson</a> (<a href="https://github.com/NicTool/api/commits?author=msimerson">14</a>)|
5+
| <img height="80" src="https://avatars.githubusercontent.com/u/261635?v=4"><br><a href="https://github.com/msimerson">msimerson</a> (<a href="https://github.com/NicTool/api/commits?author=msimerson">15</a>)|
66
| :---: |
77

88
<sub>this file is generated by [.release](https://github.com/msimerson/.release).

lib/nameserver.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,11 @@ function dbToObject(rows) {
103103
}
104104
for (const f of ['export']) {
105105
for (const p of ['type', 'interval', 'serials', 'status']) {
106-
if (row[`${f}_${p}`] !== undefined) {
106+
if (![null, undefined].includes(row[`${f}_${p}`])) {
107107
if (row[f] === undefined) row[f] = {}
108108
row[f][p] = row[`${f}_${p}`]
109-
delete row[`${f}_${p}`]
110109
}
110+
delete row[`${f}_${p}`]
111111
}
112112
}
113113
}

lib/nameserver.test.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,21 @@ describe('nameserver', function () {
3333
assert.ok(await Nameserver.put({ id: testCase.id, name: testCase.name }))
3434
})
3535

36+
it('handles null export interval gracefully', async () => {
37+
await Nameserver.mysql.execute(
38+
'UPDATE nt_nameserver SET export_interval = NULL WHERE nt_nameserver_id = ?',
39+
[testCase.id],
40+
)
41+
42+
const ns = await Nameserver.get({ id: testCase.id })
43+
assert.equal(ns[0].export.interval, undefined)
44+
45+
await Nameserver.mysql.execute(
46+
'UPDATE nt_nameserver SET export_interval = ? WHERE nt_nameserver_id = ?',
47+
[0, testCase.id],
48+
)
49+
})
50+
3651
it('deletes a nameserver', async () => {
3752
assert.ok(await Nameserver.delete({ id: testCase.id }))
3853
let g = await Nameserver.get({ id: testCase.id, deleted: 1 })

lib/util.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ function setEnv() {
77

88
/* c8 ignore next 9 */
99
switch (os.hostname()) {
10-
case 'mbp.simerson.net':
11-
case 'imac27.simerson.net':
10+
case 'mattbook-m3.home.simerson.net':
11+
case 'imac27.home.simerson.net':
1212
process.env.NODE_ENV = 'development'
1313
break
1414
default:

lib/zone.js

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@ class Zone {
2222
args = JSON.parse(JSON.stringify(args))
2323
if (args.deleted === undefined) args.deleted = false
2424

25-
const rows = await Mysql.execute(
26-
...Mysql.select(
27-
`SELECT nt_zone_id AS id
25+
const limit = Number.isInteger(args.limit) ? args.limit : undefined
26+
delete args.limit
27+
28+
const sqlLimit = limit === undefined ? '' : ` LIMIT ${Math.max(1, limit)}`
29+
30+
const [query, params] = Mysql.select(
31+
`SELECT nt_zone_id AS id
2832
, nt_group_id AS gid
2933
, zone
3034
, mailaddr
@@ -39,16 +43,32 @@ class Zone {
3943
, last_publish
4044
, deleted
4145
FROM nt_zone`,
42-
mapToDbColumn(args, zoneDbMap),
43-
),
46+
mapToDbColumn(args, zoneDbMap),
4447
)
48+
49+
const rows = await Mysql.execute(`${query}${sqlLimit}`, params)
4550
for (const row of rows) {
4651
for (const b of boolFields) {
4752
row[b] = row[b] === 1
4853
}
4954
for (const f of ['description', 'location']) {
5055
if ([null].includes(row[f])) row[f] = ''
5156
}
57+
58+
// Coerce legacy DB NULLs to sane defaults so responses validate
59+
const zoneDefaults = {
60+
minimum: 3600,
61+
ttl: 3600,
62+
refresh: 86400,
63+
retry: 7200,
64+
expire: 1209600,
65+
}
66+
for (const [f, val] of Object.entries(zoneDefaults)) {
67+
if ([null, undefined].includes(row[f])) row[f] = val
68+
}
69+
70+
if ([null, undefined].includes(row.serial)) row.serial = 0
71+
5272
if (row['last_publish'] === undefined) delete row['last_publish']
5373
if (/00:00:00/.test(row['last_publish'])) row['last_publish'] = null
5474
if (args.deleted === false) delete row.deleted

lib/zone.test.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@ describe('zone', function () {
3535
assert.ok(await Zone.put({ id: testCase.id, mailaddr: testCase.mailaddr }))
3636
})
3737

38+
it('handles null minimum gracefully', async () => {
39+
await Zone.mysql.execute('UPDATE nt_zone SET minimum = NULL WHERE nt_zone_id = ?', [testCase.id])
40+
41+
const z = await Zone.get({ id: testCase.id })
42+
assert.equal(z[0].minimum, 3600)
43+
44+
await Zone.mysql.execute('UPDATE nt_zone SET minimum = ? WHERE nt_zone_id = ?', [
45+
testCase.minimum,
46+
testCase.id,
47+
])
48+
})
49+
3850
describe('deletes a zone', async () => {
3951
it('can delete a zone', async () => {
4052
assert.ok(await Zone.delete({ id: testCase.id }))

package.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
{
22
"name": "@nictool/api",
3-
"version": "3.0.0-alpha.7",
3+
"version": "3.0.0-alpha.8",
44
"description": "NicTool API",
55
"main": "index.js",
66
"type": "module",
7-
"files": [ "CHANGELOG.md", "conf.d", "html", "lib", "routes", "sql", "server.js" ],
7+
"files": [
8+
"CHANGELOG.md",
9+
"conf.d",
10+
"html",
11+
"lib",
12+
"routes",
13+
"sql",
14+
"server.js"
15+
],
816
"scripts": {
917
"format": "npm run lint:fix && npm run prettier:fix",
1018
"lint": "npx eslint *.js **/*.js",

routes/zone.js

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,24 @@ import validate from '@nictool/validate'
33
import Zone from '../lib/zone.js'
44
import { meta } from '../lib/util.js'
55

6+
function zoneResponseFailAction(request, h, err) {
7+
const detail = err?.details?.find(
8+
(d) => Array.isArray(d.path) && d.path[0] === 'zone' && d.path[2] === 'zone',
9+
)
10+
11+
if (detail) {
12+
const index = detail.path[1]
13+
const badZone = request.response?.source?.zone?.[index]?.zone
14+
const badId = request.response?.source?.zone?.[index]?.id
15+
16+
if (badZone !== undefined) {
17+
err.message = `${err.message}. Invalid zone value: "${badZone}" (id: ${badId ?? 'unknown'})`
18+
}
19+
}
20+
21+
throw err
22+
}
23+
624
function ZoneRoutes(server) {
725
server.route([
826
{
@@ -15,13 +33,14 @@ function ZoneRoutes(server) {
1533
},
1634
response: {
1735
schema: validate.zone.GET_res,
18-
failAction: 'log',
36+
failAction: zoneResponseFailAction,
1937
},
2038
tags: ['api'],
2139
},
2240
handler: async (request, h) => {
2341
const getArgs = {
2442
deleted: request.query.deleted === true ? 1 : 0,
43+
limit: 1000,
2544
}
2645
if (request.params.id) getArgs.id = parseInt(request.params.id, 10)
2746

@@ -48,7 +67,7 @@ function ZoneRoutes(server) {
4867
},
4968
response: {
5069
schema: validate.zone.GET_res,
51-
failAction: 'log',
70+
failAction: zoneResponseFailAction,
5271
},
5372
tags: ['api'],
5473
},
@@ -78,7 +97,7 @@ function ZoneRoutes(server) {
7897
},
7998
response: {
8099
schema: validate.zone.GET_res,
81-
failAction: 'log',
100+
failAction: zoneResponseFailAction,
82101
},
83102
tags: ['api'],
84103
},

0 commit comments

Comments
 (0)