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
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
104 changes: 45 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, { claimId, 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,34 @@ 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, keep) => {
if (args.id !== undefined && groups.some((group) => group.id === args.id)) {
return keep(idConflict('group', args.id, options))
}

const groups = await this._load()
groups.push(args)
await this._save(groups)
return gid
args.id = claimId(groups, data, 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 +122,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
63 changes: 27 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, { claimId, 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,17 @@ 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, keep) => {
if (args.id !== undefined && nameservers.some((nameserver) => nameserver.id === args.id)) {
return keep(idConflict('nameserver', args.id, options))
}

args.id = claimId(nameservers, data, args.id, 0xffff)
nameservers.push(args)
return args.id
})
}

async get(args) {
Expand All @@ -72,32 +67,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