diff --git a/.release b/.release
index 0d06b1c..6ad30e5 160000
--- a/.release
+++ b/.release
@@ -1 +1 @@
-Subproject commit 0d06b1cbb2a5ef38840cd3d2346842795dc72865
+Subproject commit 6ad30e5df5fc026b332bbae7a0a4dfd47baf5898
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 37c4966..736616c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
### Unreleased
+### [0.8.9] - 2026-03-29
+
+- group: GET can return one group or array
+- PUT added to: group, ns, user, zone, zr
+
### [0.8.8] - 2026-03-25
- zone_rec: delete request has id in param
@@ -108,7 +113,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
[0.5.0]: https://github.com/NicTool/validate/releases/tag/0.5.0
[0.6.0]: https://github.com/NicTool/validate/releases/tag/0.6.0
[0.6.1]: https://github.com/NicTool/validate/releases/tag/0.6.1
-[0.6.3]: https://github.com/NicTool/validate/releases/tag/v0.6.3
+[0.6.3]: https://github.com/NicTool/validate/releases/tag/0.6.3
[0.7.0]: https://github.com/NicTool/validate/releases/tag/0.7.0
[0.7.1]: https://github.com/NicTool/validate/releases/tag/v0.7.1
[0.7.2]: https://github.com/NicTool/validate/releases/tag/v0.7.2
@@ -122,3 +127,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
[0.8.5]: https://github.com/NicTool/validate/releases/tag/v0.8.5
[0.8.7]: https://github.com/NicTool/validate/releases/tag/v0.8.7
[0.8.8]: https://github.com/NicTool/validate/releases/tag/v0.8.8
+[0.8.9]: https://github.com/NicTool/validate/releases/tag/v0.8.9
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 158d764..6fd2b97 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -2,7 +2,7 @@
This handcrafted artisanal software is brought to you by:
-| 
msimerson (27) |
+| 
msimerson (28) |
| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
this file is generated by [.release](https://github.com/msimerson/.release).
diff --git a/lib/group.js b/lib/group.js
index b83329e..0e2e3cd 100644
--- a/lib/group.js
+++ b/lib/group.js
@@ -20,6 +20,7 @@ export const v3 = Joi.object({
deleted: Joi.boolean(),
has_children: Joi.boolean(),
permission: permission.v3,
+ usable_ns: Joi.array().items(shared.uint32),
})
// legacy group format
@@ -36,15 +37,39 @@ export const GET_req = Joi.object({
id: pid,
name: name,
deleted: Joi.boolean(),
+ include_subgroups: Joi.boolean(),
+})
+
+const groupItem = Joi.object({
+ id: pid,
+ name: name,
+ parent_gid: pid,
+ deleted: Joi.boolean(),
+ permissions: permission.v3,
})
export const GET_res = Joi.object({
- group: Joi.object({
- id: pid,
- name: name,
- parent_gid: pid,
- deleted: Joi.boolean(),
- }),
+ group: Joi.alternatives().try(groupItem, Joi.array().items(groupItem)),
+ meta: shared.meta,
+})
+
+export const GET_list_req = Joi.object({
+ parent_gid: pid,
+ name: name,
+ deleted: Joi.boolean(),
+ include_subgroups: Joi.boolean(),
+})
+
+export const GET_list_res = Joi.object({
+ group: Joi.array().items(
+ Joi.object({
+ id: pid,
+ name: name,
+ parent_gid: pid,
+ deleted: Joi.boolean(),
+ permissions: permission.v3,
+ }),
+ ),
meta: shared.meta,
})
@@ -53,6 +78,14 @@ export const POST = Joi.object({
name: name,
parent_gid: pid,
deleted: Joi.boolean(),
+ usable_ns: Joi.array().items(shared.uint32),
+})
+
+export const PUT = Joi.object({
+ name: name,
+ parent_gid: pid,
+ deleted: Joi.boolean(),
+ usable_ns: Joi.array().items(shared.uint32),
})
export const DELETE = Joi.object({
diff --git a/lib/nameserver.js b/lib/nameserver.js
index 2514ada..dce8a4d 100644
--- a/lib/nameserver.js
+++ b/lib/nameserver.js
@@ -45,17 +45,41 @@ export const v3 = Joi.object({
export const GET_req = Joi.object({
id: id,
name: name,
+ gid: shared.uint32,
deleted: Joi.boolean(),
})
-export const GET_res = Joi.object({
- nameserver: Joi.array().items(v3),
- meta: shared.meta,
-})
-
export const POST = v3
+export const PUT = Joi.object({
+ name: name,
+ ttl: shared.ttl,
+ description: Joi.string().empty('').max(255),
+ address: shared.ipv4,
+ address6: shared.ipv6.empty(''),
+ remote_login: remote_login,
+ export: Joi.object({
+ interval: shared.uint16,
+ serials: Joi.boolean(),
+ status: Joi.string().empty('').max(255),
+ type: type,
+ }),
+ deleted: Joi.boolean(),
+})
+
export const DELETE = Joi.object({
id: id,
deleted: Joi.boolean(),
})
+
+// GET_res uses a looser name check so records with legacy/missing trailing dots
+// don't fail the entire response.
+const v3_out = v3
+ .fork(['name'], () => Joi.string().min(1).max(255).allow(''))
+ .fork(['address'], () => shared.ipv4.allow('').optional())
+ .fork(['gid', 'ttl'], (s) => s.optional())
+
+export const GET_res = Joi.object({
+ nameserver: Joi.array().items(v3_out),
+ meta: shared.meta,
+})
diff --git a/lib/permission.js b/lib/permission.js
index e08a2e4..b321264 100644
--- a/lib/permission.js
+++ b/lib/permission.js
@@ -2,12 +2,12 @@ import Joi from 'joi'
import * as shared from './shared.js'
-export const id = Joi.number().integer().min(1).max(4294967295)
+export const id = Joi.number().integer().min(0).max(4294967295)
export const v3 = Joi.object({
id: id,
- name: Joi.string().empty(''),
- inherit: Joi.boolean(),
+ name: Joi.string().allow('', null),
+ inherit: Joi.boolean().allow(null),
self_write: Joi.boolean(),
deleted: Joi.boolean(),
group: Joi.object({
@@ -17,13 +17,13 @@ export const v3 = Joi.object({
delete: Joi.boolean(),
}),
user: Joi.object({
- id: id,
+ id: id.allow(null),
write: Joi.boolean(),
create: Joi.boolean(),
delete: Joi.boolean(),
}),
nameserver: Joi.object({
- usable: Joi.array().items(Joi.number().integer().positive()),
+ usable: Joi.array().items(Joi.number().integer().min(0)),
write: Joi.boolean(),
create: Joi.boolean(),
delete: Joi.boolean(),
diff --git a/lib/session.js b/lib/session.js
index bc4336d..a20d93b 100644
--- a/lib/session.js
+++ b/lib/session.js
@@ -8,7 +8,7 @@ export const id = shared.uint32
export const POST = Joi.object({
username: user.username.required(),
- password: user.password.required(),
+ password: Joi.string().min(1).required(),
})
export const GET_res = Joi.object({
diff --git a/lib/user.js b/lib/user.js
index 5f415a5..91d8cc1 100644
--- a/lib/user.js
+++ b/lib/user.js
@@ -4,6 +4,7 @@ const JoiPassword = Joi.extend(joiPasswordExtendCore)
import * as shared from './shared.js'
import * as group from './group.js'
+import * as permission from './permission.js'
export const id = Joi.number().integer().min(1).max(4294967295)
@@ -30,11 +31,15 @@ export const v3 = Joi.object({
password: password,
is_admin: Joi.boolean(),
deleted: Joi.boolean(),
+ permissions: permission.v3,
+ inherit_group_permissions: Joi.boolean(),
})
export const GET_req = Joi.object({
id: id,
+ gid: group.id,
deleted: Joi.boolean(),
+ include_subgroups: Joi.boolean(),
})
export const GET_res = Joi.object({
@@ -55,6 +60,18 @@ export const POST = Joi.object({
email: email,
password: password,
is_admin: Joi.boolean(),
+ inherit_group_permissions: Joi.boolean(),
+})
+
+export const PUT = Joi.object({
+ first_name: Joi.string().min(1),
+ last_name: Joi.string().min(1),
+ username: username,
+ email: email,
+ password: password,
+ is_admin: Joi.boolean(),
+ deleted: Joi.boolean(),
+ inherit_group_permissions: Joi.boolean(),
})
export const DELETE = Joi.object({
diff --git a/lib/zone.js b/lib/zone.js
index 402ac3a..2873b81 100644
--- a/lib/zone.js
+++ b/lib/zone.js
@@ -8,7 +8,7 @@ export const zone = Joi.string().min(3).max(255)
// .domain({ allowFullyQualified: true, allowUnderscore: true, tlds: false })
export const v3 = Joi.object({
- id: id,
+ id,
gid: shared.uint32.required(),
@@ -41,6 +41,7 @@ export const v3 = Joi.object({
export const GET_req = Joi.object({
id: id,
+ gid: shared.uint32,
zone: zone,
search: Joi.string().max(255).allow(''),
zone_like: Joi.string().max(255).allow(''),
@@ -50,16 +51,38 @@ export const GET_req = Joi.object({
sort_by: Joi.string().valid('id', 'zone', 'description', 'last_modified'),
sort_dir: Joi.string().lowercase().valid('asc', 'desc'),
deleted: Joi.boolean(),
+}).options({ allowUnknown: true })
+
+export const GET_ns_res = Joi.object({
+ ns: Joi.array().items(
+ Joi.object({
+ owner: Joi.string().required(),
+ ttl: shared.ttl.required(),
+ dname: Joi.string().required(),
+ }),
+ ),
+ meta: shared.meta,
})
export const GET_res = Joi.object({
zone: Joi.array().items(v3),
meta: shared.meta,
+ deleted: Joi.boolean(),
})
export const POST = v3
-export const DELETE = Joi.object({
- id: id,
+export const PUT = Joi.object({
+ description: Joi.string().empty('').allow(null),
+ mailaddr: Joi.string().empty('').allow(null),
+ ttl: shared.ttl,
+ refresh: shared.int32,
+ retry: shared.int32,
+ expire: shared.int32,
+ minimum: shared.int32,
deleted: Joi.boolean(),
})
+
+export const DELETE = Joi.object({
+ id,
+})
diff --git a/lib/zone_record.js b/lib/zone_record.js
index 99516b7..d5be903 100644
--- a/lib/zone_record.js
+++ b/lib/zone_record.js
@@ -227,7 +227,7 @@ export const GET_req = Joi.object({
id: shared.uint32,
zid: shared.uint32,
deleted: Joi.boolean(),
-})
+}).options({ allowUnknown: true })
export const GET_res = Joi.object({
zone_record: Joi.array().items(v3),
@@ -236,6 +236,8 @@ export const GET_res = Joi.object({
export const POST = v3.fork(['id', 'ttl'], (schema) => schema.optional())
+export const PUT = v3.fork(['id', 'zid', 'owner', 'ttl', 'type'], (schema) => schema.optional())
+
export const DELETE = Joi.object({
deleted: Joi.boolean(),
})
diff --git a/package.json b/package.json
index 1e4acb3..20ac64c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@nictool/validate",
- "version": "0.8.8",
+ "version": "0.8.9",
"description": "NicTool Object Validation",
"type": "module",
"files": [
@@ -19,7 +19,7 @@
},
"scripts": {
"format:check": "npm run prettier; npm run lint",
- "format": "npm run prettier -- --write && npm run lint --fix",
+ "format": "npm run prettier -- --write && npm run lint:fix",
"lint": "npx eslint .",
"lint:fix": "npx eslint --fix .",
"prettier": "npx prettier --ignore-path .gitignore --check .",