Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
1 change: 1 addition & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ jobs:
contents: read
with:
mysql: true
data-stores: mysql,json
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
- fix: GET /zone/{id}/ns reads nameservers through the zone store
- fix(mysql): read v2 group permission rows, and save partial permission changes
- fix: file store login checks the group in user@group
- create: allocate ids internally and reject caller-supplied ids

### [3.0.3] - 2026-07-27

Expand Down
105 changes: 46 additions & 59 deletions lib/group/store/file.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import FileStore from '../../store/file.js'
import FileStore, { nextId, removeRows } from '../../store/file.js'
import { idConflict } from '../../store/error.js'

import GroupBase from './base.js'
Expand All @@ -23,10 +23,6 @@ class GroupRepoFile extends GroupBase {
return this.file.load('group')
}

async _save(groups) {
return this.file.save('group', groups)
}

_postProcess(row, deletedArg) {
const r = JSON.parse(JSON.stringify(row))
r.deleted = Boolean(r.deleted)
Expand Down Expand Up @@ -60,37 +56,35 @@ class GroupRepoFile extends GroupBase {

async create(args, options) {
args = JSON.parse(JSON.stringify(args))

if (args.id !== undefined) {
const existing = [
...(await this.get({ id: args.id })),
...(await this.get({ id: args.id, deleted: true })),
]
if (existing.length > 0) return idConflict('group', args.id, options)
}

const usable_ns = args.usable_ns ?? []
delete args.usable_ns

const gid = args.id
args.permissions = {
...JSON.parse(JSON.stringify(defaultPermissions)),
id: gid,
name: `Group ${args.name} perms`,
user: { id: gid, create: false, write: false, delete: false },
group: { id: gid, create: false, write: false, delete: false },
nameserver: {
usable: Array.isArray(usable_ns) ? usable_ns : [],
create: false,
write: false,
delete: false,
},
}
return this.file.mutate('group', (groups, data) => {
if (args.id !== undefined && groups.some((group) => group.id === args.id)) {
return idConflict('group', args.id, options)
}

const groups = await this._load()
groups.push(args)
await this._save(groups)
return gid
if (args.id === undefined) args.id = nextId(groups, data.last_id)
data.last_id = Math.max(data.last_id ?? 0, args.id)

const gid = args.id
args.permissions = {
...JSON.parse(JSON.stringify(defaultPermissions)),
id: gid,
name: `Group ${args.name} perms`,
user: { id: gid, create: false, write: false, delete: false },
group: { id: gid, create: false, write: false, delete: false },
nameserver: {
usable: Array.isArray(usable_ns) ? usable_ns : [],
create: false,
write: false,
delete: false,
},
}

groups.push(args)
return gid
})
}

async get(args_orig) {
Expand Down Expand Up @@ -129,42 +123,35 @@ class GroupRepoFile extends GroupBase {
const usable_ns = args.usable_ns
delete args.usable_ns

const groups = await this._load()
const idx = groups.findIndex((g) => g.id === id)
if (idx === -1) return false
return this.file.mutate('group', (groups) => {
const idx = groups.findIndex((g) => g.id === id)
if (idx === -1) return false

if (usable_ns !== undefined && groups[idx].permissions) {
groups[idx].permissions.nameserver = {
...groups[idx].permissions.nameserver,
usable: Array.isArray(usable_ns) ? usable_ns : [],
if (usable_ns !== undefined && groups[idx].permissions) {
groups[idx].permissions.nameserver = {
...groups[idx].permissions.nameserver,
usable: Array.isArray(usable_ns) ? usable_ns : [],
}
}
}

if (Object.keys(args).length > 0) {
groups[idx] = { ...groups[idx], ...args }
}

await this._save(groups)
return true
if (Object.keys(args).length > 0) {
groups[idx] = { ...groups[idx], ...args }
}
return true
})
}

async delete(args) {
const groups = await this._load()
const idx = groups.findIndex((g) => g.id === args.id)
if (idx === -1) return false

groups[idx].deleted = args.deleted ?? true
await this._save(groups)
return true
return this.file.mutate('group', (groups) => {
const row = groups.find((g) => g.id === args.id)
if (!row) return false
row.deleted = args.deleted ?? true
return true
})
}

async destroy(args) {
const groups = await this._load()
const before = groups.length
const filtered = groups.filter((g) => g.id !== args.id)
if (filtered.length === before) return false
await this._save(filtered)
return true
return this.file.mutate('group', (groups) => removeRows(groups, (g) => g.id === args.id) > 0)
}
}

Expand Down
64 changes: 28 additions & 36 deletions lib/nameserver/store/file.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import FileStore from '../../store/file.js'
import FileStore, { nextId, removeRows } from '../../store/file.js'
import { idConflict } from '../../store/error.js'

import NameserverBase from './base.js'
Expand All @@ -18,10 +18,6 @@ class NameserverRepoFile extends NameserverBase {
return this.file.load('nameserver')
}

async _save(nameservers) {
return this.file.save('nameserver', nameservers)
}

_postProcess(row, deletedArg) {
const r = JSON.parse(JSON.stringify(row))

Expand All @@ -40,18 +36,18 @@ class NameserverRepoFile extends NameserverBase {
}

async create(args, options) {
if (args.id !== undefined) {
const existing = [
...(await this.get({ id: args.id })),
...(await this.get({ id: args.id, deleted: true })),
]
if (existing.length > 0) return idConflict('nameserver', args.id, options)
}
args = JSON.parse(JSON.stringify(args))

const nameservers = await this._load()
nameservers.push(JSON.parse(JSON.stringify(args)))
await this._save(nameservers)
return args.id
return this.file.mutate('nameserver', (nameservers, data) => {
if (args.id !== undefined && nameservers.some((nameserver) => nameserver.id === args.id)) {
return idConflict('nameserver', args.id, options)
}

if (args.id === undefined) args.id = nextId(nameservers, data.last_id, 0xffff)
data.last_id = Math.max(data.last_id ?? 0, args.id)
nameservers.push(args)
return args.id
})
}

async get(args) {
Expand All @@ -72,32 +68,28 @@ class NameserverRepoFile extends NameserverBase {

async put(args) {
if (!args.id) return false
const nameservers = await this._load()
const idx = nameservers.findIndex((n) => n.id === args.id)
if (idx === -1) return false

nameservers[idx] = { ...nameservers[idx], ...JSON.parse(JSON.stringify(args)) }
await this._save(nameservers)
return true
return this.file.mutate('nameserver', (nameservers) => {
const idx = nameservers.findIndex((n) => n.id === args.id)
if (idx === -1) return false
nameservers[idx] = { ...nameservers[idx], ...JSON.parse(JSON.stringify(args)) }
return true
})
}

async delete(args) {
const nameservers = await this._load()
const idx = nameservers.findIndex((n) => n.id === args.id)
if (idx === -1) return false

nameservers[idx].deleted = args.deleted ?? true
await this._save(nameservers)
return true
return this.file.mutate('nameserver', (nameservers) => {
const row = nameservers.find((n) => n.id === args.id)
if (!row) return false
row.deleted = args.deleted ?? true
return true
})
}

async destroy(args) {
const nameservers = await this._load()
const before = nameservers.length
const filtered = nameservers.filter((n) => n.id !== args.id)
if (filtered.length === before) return false
await this._save(filtered)
return true
return this.file.mutate(
'nameserver',
(nameservers) => removeRows(nameservers, (n) => n.id === args.id) > 0,
)
}
}

Expand Down
Loading
Loading