add and update multiple indexes at once

This commit is contained in:
Evert Prants 2021-05-16 17:21:49 +03:00
parent 2bd12aeaa3
commit 176a3f66b0
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
2 changed files with 157 additions and 69 deletions

View File

@ -88,17 +88,17 @@ Returns all of `:domain`'s DNS records or performs a search based on provided qu
```
### `POST /zone/records/:domain`
Updates a DNS record of `:domain` at `index`.
Updates a single or multiple DNS records of `:domain` at `index`.
**Body:**
```typescript
{
index: number;
record: {
index: number;
name?: string;
type?: string;
value?: string;
}
} | {...}[];
}
```
@ -106,13 +106,17 @@ Updates a DNS record of `:domain` at `index`.
```typescript
{
success: boolean;
message: string;
changed: DNSRecord[];
errors: {
message: string;
record: DNSRecord;
}[];
}
```
### `PUT /zone/records/:domain`
Creates a new DNS record for `:domain`.
Creates a single or multiple new DNS records for `:domain`.
**Body:**
```typescript
@ -121,7 +125,7 @@ Creates a new DNS record for `:domain`.
name: string;
type: string;
value: string;
}
} | {...}[];
}
```
@ -129,13 +133,17 @@ Creates a new DNS record for `:domain`.
```typescript
{
success: boolean;
message: string;
created: ({ index: number, ...DNSRecord })[];
errors: {
message: string;
record: DNSRecord;
}[];
}
```
### `DELETE /zone/records/:domain`
Deletes a DNS record from `:domain` at `index`.
Deletes a DNS record from `:domain` at `index`. **Warning:** Deleting an index that is not at the end of the record causes following records' indexes to shift back by one.
**Body:**
```typescript

View File

@ -101,56 +101,98 @@ api.get('/zone/records/:domain', domainAuthorization, async (req, res) => {
});
/**
* Update a record by its index in the zone file
* index: number;
* Update records by their index in the zone file
* record: {
* index: number;
* name?: string;
* type?: DNSRecordType;
* value?: string;
* }
* }[];
*/
api.post('/zone/records/:domain', domainAuthorization, async (req, res) => {
const domain = req.params.domain;
const index = parseInt(req.body.index, 10);
const setters = req.body.record;
let setters = req.body.record;
const cached = await getOrLoad(domain);
const { zone } = cached;
if (index == null || isNaN(index) || !zone.records[index]) {
throw new Error('Invalid record index.');
}
const keys = Object.keys(setters);
const record = zone.records[index];
if (!setters || keys.length === 0) {
res.json({ success: true, message: 'Nothing was changed.', record });
if (!setters) {
res.json({ success: true, message: 'Nothing was changed.' });
return;
}
if (setters.type) {
const upperType = setters.type.toUpperCase();
if (!Array.isArray(setters)) {
setters = [setters];
}
const cached = await getOrLoad(domain);
const { zone } = cached;
const changed = [];
const errors = [];
for (const setter of setters) {
const index = parseInt(setter.index, 10);
if (index == null || isNaN(index) || !zone.records[index]) {
errors.push({
message: 'Invalid record index.'
});
continue;
}
const keys = Object.keys(setter);
const record = { ...zone.records[index] };
if (!setter || keys.length === 0) {
errors.push({
message: 'Nothing was changed.',
record,
})
continue;
}
if (setter.type) {
const upperType = setter.type.toUpperCase();
if (upperType === 'SOA' && record.type !== DNSRecordType.SOA) {
throw new Error('Cannot change type to Start Of Authority.');
errors.push({
message: 'Cannot change type to Start Of Authority.',
record
});
continue;
}
if (!DNSRecordType[<keyof typeof DNSRecordType>upperType] && upperType !== '*') {
throw new Error('Unsupported record type.');
errors.push({
message: 'Unsupported record type.',
record
});
continue;
}
}
keys.forEach((key) => {
if (record[key]) {
record[key] = setters[key];
record[key] = setter[key];
}
});
if (!validator.validateRecord(record)) {
throw new Error('Validation error: Invalid characters');
errors.push({
message: 'Validation error: Invalid characters',
record
});
continue;
}
zone.records[index] = record;
changed.push(record);
}
await cache.update(domain, cached);
res.json({ success: true, message: 'Record changed successfully.', record });
if (!changed.length && errors.length) {
res.status(400).json({ success: false, message: 'Updating record(s) failed.', changed, errors });
} else if (changed.length) {
res.json({ success: true, message: 'Record(s) changed successfully.', changed, errors });
} else {
res.json({ success: true, message: 'Nothing was changed.', changed, errors });
}
});
/**
@ -184,50 +226,88 @@ api.delete('/zone/records/:domain', domainAuthorization, async (req, res) => {
* name: string;
* type: DNSRecordType;
* value: string;
* }
* }[];
*/
api.put('/zone/records/:domain', domainAuthorization, async (req, res) => {
const domain = req.params.domain;
const setter = req.body.record;
let setters = req.body.record;
if (!setter) {
if (!setters) {
throw new Error('New record is missing!');
}
if (!Array.isArray(setters)) {
setters = [setters];
}
const cached = await getOrLoad(domain);
const { zone } = cached;
const created = [];
const errors = [];
for (const setter of setters) {
const missing = ['name', 'type', 'value'].reduce<string[]>(
(list, entry) => (setter[entry] == null ? [...list, entry] : list)
, []);
if (missing.length) {
throw new Error(`${missing.join(', ')} ${missing.length > 1 ? 'are' : 'is'} required.`);
errors.push({
message: `${missing.join(', ')} ${missing.length > 1 ? 'are' : 'is'} required.`,
record: setter
});
continue;
}
const { name, type, value } = setter;
const upperType = type.toUpperCase();
if (upperType === 'SOA') {
throw new Error('Cannot add another Start Of Authority record. Please use POST method to modify the existing record.');
errors.push({
message: 'Cannot add another Start Of Authority record. Please use POST method to modify the existing record.',
record: setter
});
continue;
}
if (!DNSRecordType[<keyof typeof DNSRecordType>upperType] && upperType !== '*') {
throw new Error('Unsupported record type.');
errors.push({
message: 'Unsupported record type.',
record: setter
});
continue;
}
const cached = await getOrLoad(domain);
const { zone } = cached;
const newRecord = { name, type: upperType, value };
if (!validator.validateRecord(newRecord)) {
throw new Error('Validation error: Invalid characters');
errors.push({
message: 'Validation error: Invalid characters',
record: setter
});
continue;
}
if (cache.search(cached, name, upperType, value, true).length) {
throw new Error('Exact same record already exists. No need to duplicate records!');
errors.push({
message: 'Exact same record already exists. No need to duplicate records!',
record: setter
});
continue;
}
zone.records.push(newRecord);
const index = zone.records.push(newRecord) - 1;
created.push({ ...newRecord, index });
}
await cache.update(domain, cached);
res.status(201).json({ success: true, message: 'Record added.', record: newRecord });
if (!created.length && errors.length) {
res.status(400).json({ success: false, message: 'Creating record(s) failed.', created, errors });
} else if (created.length) {
res.status(201).json({ success: true, message: 'Record(s) created successfully.', created, errors });
} else {
res.json({ success: true, message: 'Nothing was created.', created, errors });
}
});
/**