diff --git a/README.md b/README.md index 203d784..5af569c 100644 --- a/README.md +++ b/README.md @@ -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; - } + } | {...}[]; } ``` @@ -107,12 +107,16 @@ Updates a DNS record of `:domain` at `index`. { success: boolean; message: string; - record: DNSRecord; + 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; - } + } | {...}[]; } ``` @@ -130,12 +134,16 @@ Creates a new DNS record for `:domain`. { success: boolean; message: string; - record: DNSRecord; + 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 diff --git a/src/index.ts b/src/index.ts index 3a015db..ca24f4e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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 (upperType === 'SOA' && record.type !== DNSRecordType.SOA) { - throw new Error('Cannot change type to Start Of Authority.'); - } - - if (!DNSRecordType[upperType] && upperType !== '*') { - throw new Error('Unsupported record type.'); - } + if (!Array.isArray(setters)) { + setters = [setters]; } - keys.forEach((key) => { - if (record[key]) { - record[key] = setters[key]; - } - }); + const cached = await getOrLoad(domain); + const { zone } = cached; - if (!validator.validateRecord(record)) { - throw new Error('Validation error: Invalid characters'); + 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) { + errors.push({ + message: 'Cannot change type to Start Of Authority.', + record + }); + continue; + } + + if (!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); - 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!'); } - const missing = ['name', 'type', 'value'].reduce( - (list, entry) => (setter[entry] == null ? [...list, entry] : list) - , []); - - 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[upperType] && upperType !== '*') { - throw new Error('Unsupported record type.'); + if (!Array.isArray(setters)) { + setters = [setters]; } 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'); + const created = []; + const errors = []; + + for (const setter of setters) { + const missing = ['name', 'type', 'value'].reduce( + (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[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); - 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 }); + } }); /**