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` ### `POST /zone/records/:domain`
Updates a DNS record of `:domain` at `index`. Updates a single or multiple DNS records of `:domain` at `index`.
**Body:** **Body:**
```typescript ```typescript
{ {
index: number;
record: { record: {
index: number;
name?: string; name?: string;
type?: string; type?: string;
value?: string; value?: string;
} } | {...}[];
} }
``` ```
@ -107,12 +107,16 @@ Updates a DNS record of `:domain` at `index`.
{ {
success: boolean; success: boolean;
message: string; message: string;
record: DNSRecord; changed: DNSRecord[];
errors: {
message: string;
record: DNSRecord;
}[];
} }
``` ```
### `PUT /zone/records/:domain` ### `PUT /zone/records/:domain`
Creates a new DNS record for `:domain`. Creates a single or multiple new DNS records for `:domain`.
**Body:** **Body:**
```typescript ```typescript
@ -121,7 +125,7 @@ Creates a new DNS record for `:domain`.
name: string; name: string;
type: string; type: string;
value: string; value: string;
} } | {...}[];
} }
``` ```
@ -130,12 +134,16 @@ Creates a new DNS record for `:domain`.
{ {
success: boolean; success: boolean;
message: string; message: string;
record: DNSRecord; created: ({ index: number, ...DNSRecord })[];
errors: {
message: string;
record: DNSRecord;
}[];
} }
``` ```
### `DELETE /zone/records/:domain` ### `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:** **Body:**
```typescript ```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 * Update records by their index in the zone file
* index: number;
* record: { * record: {
* index: number;
* name?: string; * name?: string;
* type?: DNSRecordType; * type?: DNSRecordType;
* value?: string; * value?: string;
* } * }[];
*/ */
api.post('/zone/records/:domain', domainAuthorization, async (req, res) => { api.post('/zone/records/:domain', domainAuthorization, async (req, res) => {
const domain = req.params.domain; const domain = req.params.domain;
const index = parseInt(req.body.index, 10); let setters = req.body.record;
const setters = req.body.record;
const cached = await getOrLoad(domain); if (!setters) {
const { zone } = cached; res.json({ success: true, message: 'Nothing was changed.' });
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 });
return; return;
} }
if (setters.type) { if (!Array.isArray(setters)) {
const upperType = setters.type.toUpperCase(); setters = [setters];
if (upperType === 'SOA' && record.type !== DNSRecordType.SOA) {
throw new Error('Cannot change type to Start Of Authority.');
}
if (!DNSRecordType[<keyof typeof DNSRecordType>upperType] && upperType !== '*') {
throw new Error('Unsupported record type.');
}
} }
keys.forEach((key) => { const cached = await getOrLoad(domain);
if (record[key]) { const { zone } = cached;
record[key] = setters[key];
}
});
if (!validator.validateRecord(record)) { const changed = [];
throw new Error('Validation error: Invalid characters'); 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) {
errors.push({
message: 'Cannot change type to Start Of Authority.',
record
});
continue;
}
if (!DNSRecordType[<keyof typeof DNSRecordType>upperType] && upperType !== '*') {
errors.push({
message: 'Unsupported record type.',
record
});
continue;
}
}
keys.forEach((key) => {
if (record[key]) {
record[key] = setter[key];
}
});
if (!validator.validateRecord(record)) {
errors.push({
message: 'Validation error: Invalid characters',
record
});
continue;
}
zone.records[index] = record;
changed.push(record);
} }
await cache.update(domain, cached); 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; * name: string;
* type: DNSRecordType; * type: DNSRecordType;
* value: string; * value: string;
* } * }[];
*/ */
api.put('/zone/records/:domain', domainAuthorization, async (req, res) => { api.put('/zone/records/:domain', domainAuthorization, async (req, res) => {
const domain = req.params.domain; 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!'); throw new Error('New record is missing!');
} }
const missing = ['name', 'type', 'value'].reduce<string[]>( if (!Array.isArray(setters)) {
(list, entry) => (setter[entry] == null ? [...list, entry] : list) setters = [setters];
, []);
if (missing.length) {
throw new Error(`${missing.join(', ')} ${missing.length > 1 ? 'are' : 'is'} required.`);
}
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.');
}
if (!DNSRecordType[<keyof typeof DNSRecordType>upperType] && upperType !== '*') {
throw new Error('Unsupported record type.');
} }
const cached = await getOrLoad(domain); const cached = await getOrLoad(domain);
const { zone } = cached; const { zone } = cached;
const newRecord = { name, type: upperType, value };
if (!validator.validateRecord(newRecord)) { const created = [];
throw new Error('Validation error: Invalid characters'); 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) {
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') {
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 !== '*') {
errors.push({
message: 'Unsupported record type.',
record: setter
});
continue;
}
const newRecord = { name, type: upperType, value };
if (!validator.validateRecord(newRecord)) {
errors.push({
message: 'Validation error: Invalid characters',
record: setter
});
continue;
}
if (cache.search(cached, name, upperType, value, true).length) {
errors.push({
message: 'Exact same record already exists. No need to duplicate records!',
record: setter
});
continue;
}
const index = zone.records.push(newRecord) - 1;
created.push({ ...newRecord, index });
} }
if (cache.search(cached, name, upperType, value, true).length) {
throw new Error('Exact same record already exists. No need to duplicate records!');
}
zone.records.push(newRecord);
await cache.update(domain, cached); 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 });
}
}); });
/** /**