Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release
Submodule .release updated 1 files
+3 −4 finish.sh
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).

### Unreleased

### [3.0.0-alpha.8] - 2026-03-14

- lib/zone: add limit option
- lib/nameserver.js: handle null fields from DB
- routes/zone: report zone name on validation failure

### [3.0.0-alpha.7] - 2026-03-13

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

### 3.0.0-alpha.3
### [3.0.0-alpha.3]

- routes/permission: added GET, POST, DELETE
- permission.get: default search with deleted=0
Expand All @@ -55,3 +61,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
[3.0.0-alpha.5]: https://github.com/NicTool/api/releases/tag/v3.0.0-alpha.5
[3.0.0-alpha.6]: https://github.com/NicTool/api/releases/tag/v3.0.0-alpha.6
[3.0.0-alpha.7]: https://github.com/NicTool/api/releases/tag/v3.0.0-alpha.7
[3.0.0-alpha.8]: https://github.com/NicTool/api/releases/tag/v3.0.0-alpha.8
2 changes: 1 addition & 1 deletion CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This handcrafted artisanal software is brought to you by:

| <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>)|
| <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>)|
| :---: |

<sub>this file is generated by [.release](https://github.com/msimerson/.release).
Expand Down
4 changes: 2 additions & 2 deletions lib/nameserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,11 @@ function dbToObject(rows) {
}
for (const f of ['export']) {
for (const p of ['type', 'interval', 'serials', 'status']) {
if (row[`${f}_${p}`] !== undefined) {
if (![null, undefined].includes(row[`${f}_${p}`])) {
if (row[f] === undefined) row[f] = {}
row[f][p] = row[`${f}_${p}`]
delete row[`${f}_${p}`]
}
delete row[`${f}_${p}`]
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions lib/nameserver.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,21 @@ describe('nameserver', function () {
assert.ok(await Nameserver.put({ id: testCase.id, name: testCase.name }))
})

it('handles null export interval gracefully', async () => {
await Nameserver.mysql.execute(
'UPDATE nt_nameserver SET export_interval = NULL WHERE nt_nameserver_id = ?',
[testCase.id],
)

const ns = await Nameserver.get({ id: testCase.id })
assert.equal(ns[0].export.interval, undefined)

await Nameserver.mysql.execute(
'UPDATE nt_nameserver SET export_interval = ? WHERE nt_nameserver_id = ?',
[0, testCase.id],
)
})

it('deletes a nameserver', async () => {
assert.ok(await Nameserver.delete({ id: testCase.id }))
let g = await Nameserver.get({ id: testCase.id, deleted: 1 })
Expand Down
4 changes: 2 additions & 2 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ function setEnv() {

/* c8 ignore next 9 */
switch (os.hostname()) {
case 'mbp.simerson.net':
case 'imac27.simerson.net':
case 'mattbook-m3.home.simerson.net':
case 'imac27.home.simerson.net':
process.env.NODE_ENV = 'development'
break
default:
Expand Down
30 changes: 25 additions & 5 deletions lib/zone.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,13 @@ class Zone {
args = JSON.parse(JSON.stringify(args))
if (args.deleted === undefined) args.deleted = false

const rows = await Mysql.execute(
...Mysql.select(
`SELECT nt_zone_id AS id
const limit = Number.isInteger(args.limit) ? args.limit : undefined
delete args.limit

const sqlLimit = limit === undefined ? '' : ` LIMIT ${Math.max(1, limit)}`

const [query, params] = Mysql.select(
`SELECT nt_zone_id AS id
, nt_group_id AS gid
, zone
, mailaddr
Expand All @@ -39,16 +43,32 @@ class Zone {
, last_publish
, deleted
FROM nt_zone`,
mapToDbColumn(args, zoneDbMap),
),
mapToDbColumn(args, zoneDbMap),
)

const rows = await Mysql.execute(`${query}${sqlLimit}`, params)
for (const row of rows) {
for (const b of boolFields) {
row[b] = row[b] === 1
}
for (const f of ['description', 'location']) {
if ([null].includes(row[f])) row[f] = ''
}

// Coerce legacy DB NULLs to sane defaults so responses validate
const zoneDefaults = {
minimum: 3600,
ttl: 3600,
refresh: 86400,
retry: 7200,
expire: 1209600,
}
for (const [f, val] of Object.entries(zoneDefaults)) {
if ([null, undefined].includes(row[f])) row[f] = val
}

if ([null, undefined].includes(row.serial)) row.serial = 0

if (row['last_publish'] === undefined) delete row['last_publish']
if (/00:00:00/.test(row['last_publish'])) row['last_publish'] = null
if (args.deleted === false) delete row.deleted
Expand Down
12 changes: 12 additions & 0 deletions lib/zone.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ describe('zone', function () {
assert.ok(await Zone.put({ id: testCase.id, mailaddr: testCase.mailaddr }))
})

it('handles null minimum gracefully', async () => {
await Zone.mysql.execute('UPDATE nt_zone SET minimum = NULL WHERE nt_zone_id = ?', [testCase.id])

const z = await Zone.get({ id: testCase.id })
assert.equal(z[0].minimum, 3600)

await Zone.mysql.execute('UPDATE nt_zone SET minimum = ? WHERE nt_zone_id = ?', [
testCase.minimum,
testCase.id,
])
})

describe('deletes a zone', async () => {
it('can delete a zone', async () => {
assert.ok(await Zone.delete({ id: testCase.id }))
Expand Down
12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
{
"name": "@nictool/api",
"version": "3.0.0-alpha.7",
"version": "3.0.0-alpha.8",
"description": "NicTool API",
"main": "index.js",
"type": "module",
"files": [ "CHANGELOG.md", "conf.d", "html", "lib", "routes", "sql", "server.js" ],
"files": [
"CHANGELOG.md",
"conf.d",
"html",
"lib",
"routes",
"sql",
"server.js"
],
"scripts": {
"format": "npm run lint:fix && npm run prettier:fix",
"lint": "npx eslint *.js **/*.js",
Expand Down
25 changes: 22 additions & 3 deletions routes/zone.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,24 @@ import validate from '@nictool/validate'
import Zone from '../lib/zone.js'
import { meta } from '../lib/util.js'

function zoneResponseFailAction(request, h, err) {
const detail = err?.details?.find(
(d) => Array.isArray(d.path) && d.path[0] === 'zone' && d.path[2] === 'zone',
)

if (detail) {
const index = detail.path[1]
const badZone = request.response?.source?.zone?.[index]?.zone
const badId = request.response?.source?.zone?.[index]?.id

if (badZone !== undefined) {
err.message = `${err.message}. Invalid zone value: "${badZone}" (id: ${badId ?? 'unknown'})`
}
}

throw err
}

function ZoneRoutes(server) {
server.route([
{
Expand All @@ -15,13 +33,14 @@ function ZoneRoutes(server) {
},
response: {
schema: validate.zone.GET_res,
failAction: 'log',
failAction: zoneResponseFailAction,
},
tags: ['api'],
},
handler: async (request, h) => {
const getArgs = {
deleted: request.query.deleted === true ? 1 : 0,
limit: 1000,
}
if (request.params.id) getArgs.id = parseInt(request.params.id, 10)

Expand All @@ -48,7 +67,7 @@ function ZoneRoutes(server) {
},
response: {
schema: validate.zone.GET_res,
failAction: 'log',
failAction: zoneResponseFailAction,
},
tags: ['api'],
},
Expand Down Expand Up @@ -78,7 +97,7 @@ function ZoneRoutes(server) {
},
response: {
schema: validate.zone.GET_res,
failAction: 'log',
failAction: zoneResponseFailAction,
},
tags: ['api'],
},
Expand Down
Loading