mirror of
https://github.com/tiennm99/is-a-dev.git
synced 2026-07-28 18:21:21 +00:00
Merge pull request #15164 from is-a-dev/wdhdev/1-letter-subdomains
1 letter subdomains + prettier
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
module.exports = {
|
||||
hasLabel: (context, label) => {
|
||||
const pr = context.payload.pull_request || context.payload.issue;
|
||||
const { labels = [] } = pr;
|
||||
hasLabel: (context, label) => {
|
||||
const pr = context.payload.pull_request || context.payload.issue;
|
||||
const { labels = [] } = pr;
|
||||
|
||||
return !!labels.find(({ name }) => name === label);
|
||||
},
|
||||
return !!labels.find(({ name }) => name === label);
|
||||
},
|
||||
};
|
||||
|
||||
+55
-40
@@ -1,61 +1,76 @@
|
||||
const R = require('ramda');
|
||||
const { VALID_RECORD_TYPES, DOMAIN_HOST_IP, TTL, ENV } = require('../utils/constants');
|
||||
const { domainService: dc } = require('../utils/domain-service');
|
||||
const { getDomains: gd } = require('../utils/get-domain');
|
||||
const R = require("ramda");
|
||||
const {
|
||||
VALID_RECORD_TYPES,
|
||||
DOMAIN_HOST_IP,
|
||||
TTL,
|
||||
ENV,
|
||||
} = require("../utils/constants");
|
||||
const { domainService: dc } = require("../utils/domain-service");
|
||||
const { getDomains: gd } = require("../utils/get-domain");
|
||||
|
||||
const getRecords = R.compose(R.toPairs, R.pick(VALID_RECORD_TYPES));
|
||||
|
||||
const address = (type, value) => {
|
||||
if ('URL' === type) return `${value}`.replace(/\/$/g, '');
|
||||
if ('TXT' === type) return value;
|
||||
return (type === 'CNAME' ? `${value}`.toLowerCase() : `${value}`).replace(/[/.]$/g, '');
|
||||
if ("URL" === type) return `${value}`.replace(/\/$/g, "");
|
||||
if ("TXT" === type) return value;
|
||||
return (type === "CNAME" ? `${value}`.toLowerCase() : `${value}`).replace(
|
||||
/[/.]$/g,
|
||||
"",
|
||||
);
|
||||
};
|
||||
|
||||
const toHostList = R.chain(data => {
|
||||
// URL redirection must contain explicit A record
|
||||
// Wildcard A record breaks when used with MX
|
||||
// Ref: https://github.com/is-a-dev/register/issues/2365
|
||||
if ((data.record.URL && data.record.MX) || data.name === '@') {
|
||||
data.record.A = [ DOMAIN_HOST_IP ]
|
||||
}
|
||||
const toHostList = R.chain((data) => {
|
||||
// URL redirection must contain explicit A record
|
||||
// Wildcard A record breaks when used with MX
|
||||
// Ref: https://github.com/is-a-dev/register/issues/2365
|
||||
if ((data.record.URL && data.record.MX) || data.name === "@") {
|
||||
data.record.A = [DOMAIN_HOST_IP];
|
||||
}
|
||||
|
||||
const records = getRecords(data.record);
|
||||
const records = getRecords(data.record);
|
||||
|
||||
return R.chain(([recordType, values]) => {
|
||||
const valueList = Array.isArray(values) ? values : [values];
|
||||
return R.chain(([recordType, values]) => {
|
||||
const valueList = Array.isArray(values) ? values : [values];
|
||||
|
||||
return valueList.map((value, index) => ({
|
||||
name: data.name,
|
||||
type: recordType,
|
||||
address: address(recordType, value),
|
||||
ttl: TTL,
|
||||
...(recordType === 'MX' ? { priority: index + 20 } : {})
|
||||
}))
|
||||
}, records)
|
||||
return valueList.map((value, index) => ({
|
||||
name: data.name,
|
||||
type: recordType,
|
||||
address: address(recordType, value),
|
||||
ttl: TTL,
|
||||
...(recordType === "MX" ? { priority: index + 20 } : {}),
|
||||
}));
|
||||
}, records);
|
||||
});
|
||||
|
||||
const registerDomains = async ({ domainService, getDomains, log = () => { } }) => {
|
||||
const domains = await getDomains().then(toHostList);
|
||||
const registerDomains = async ({
|
||||
domainService,
|
||||
getDomains,
|
||||
log = () => {},
|
||||
}) => {
|
||||
const domains = await getDomains().then(toHostList);
|
||||
|
||||
if (domains.length === 0)
|
||||
return Promise.reject(new Error('Nothing to register'));
|
||||
if (domains.length === 0)
|
||||
return Promise.reject(new Error("Nothing to register"));
|
||||
|
||||
log(`${domains.length} records found`);
|
||||
return domainService.updateHosts(domains);
|
||||
log(`${domains.length} records found`);
|
||||
return domainService.updateHosts(domains);
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
console.log(`Running in ${ENV} mode`);
|
||||
const result = await registerDomains({ domainService: dc, getDomains: gd, log: console.log });
|
||||
console.log(result);
|
||||
console.log(`Running in ${ENV} mode`);
|
||||
const result = await registerDomains({
|
||||
domainService: dc,
|
||||
getDomains: gd,
|
||||
log: console.log,
|
||||
});
|
||||
console.log(result);
|
||||
};
|
||||
|
||||
if (require.main === module) {
|
||||
main().catch(e => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
main().catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
} else {
|
||||
module.exports = { toHostList, registerDomains };
|
||||
module.exports = { toHostList, registerDomains };
|
||||
}
|
||||
|
||||
|
||||
+10
-12
@@ -1,4 +1,3 @@
|
||||
|
||||
const getInstructions = () => `
|
||||
The changes have been published!! It should reflect in less than 24 hours.
|
||||
|
||||
@@ -35,16 +34,15 @@ Help me in my mission to keep this service alive forever by donating!
|
||||
`;
|
||||
|
||||
module.exports = {
|
||||
async instructions(context, github) {
|
||||
const pr = context.payload.issue || context.payload.pull_request;
|
||||
const { number } = pr;
|
||||
async instructions(context, github) {
|
||||
const pr = context.payload.issue || context.payload.pull_request;
|
||||
const { number } = pr;
|
||||
|
||||
await github.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: number,
|
||||
body: getInstructions(),
|
||||
});
|
||||
}
|
||||
await github.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: number,
|
||||
body: getInstructions(),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
+190
-175
@@ -1,195 +1,210 @@
|
||||
const R = require('ramda');
|
||||
const { CpanelClient } = require('../utils/lib/cpanel');
|
||||
const R = require("ramda");
|
||||
const { CpanelClient } = require("../utils/lib/cpanel");
|
||||
|
||||
const mockFetch = (expectRequest, decorate = R.identity) => (reqUrl, request) => {
|
||||
expectRequest(reqUrl, request);
|
||||
return Promise.resolve({
|
||||
json: async () => decorate(request),
|
||||
});
|
||||
};
|
||||
|
||||
describe('Cpanel client', () => {
|
||||
describe('fetchzonerecords', () => {
|
||||
it('should make the correct request', async () => {
|
||||
const fetch = mockFetch((url, request) => {
|
||||
expect(url).toBe('https://example.com:2000/json-api/cpanel?customonly=0&domain=a.b&cpanel_jsonapi_user=boy&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=fetchzone_records&cpanel_jsonapi_apiversion=2');
|
||||
expect(request).toEqual({
|
||||
headers: {
|
||||
Authorization: 'cpanel boy:boybyebye',
|
||||
},
|
||||
rejectUnauthorized: false,
|
||||
const mockFetch =
|
||||
(expectRequest, decorate = R.identity) =>
|
||||
(reqUrl, request) => {
|
||||
expectRequest(reqUrl, request);
|
||||
return Promise.resolve({
|
||||
json: async () => decorate(request),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const cpanel = CpanelClient({
|
||||
host: 'example.com',
|
||||
port: 2000,
|
||||
username: 'boy',
|
||||
apiKey: 'boybyebye',
|
||||
domain: 'a.b',
|
||||
dependencies: { fetch },
|
||||
});
|
||||
describe("Cpanel client", () => {
|
||||
describe("fetchzonerecords", () => {
|
||||
it("should make the correct request", async () => {
|
||||
const fetch = mockFetch((url, request) => {
|
||||
expect(url).toBe(
|
||||
"https://example.com:2000/json-api/cpanel?customonly=0&domain=a.b&cpanel_jsonapi_user=boy&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=fetchzone_records&cpanel_jsonapi_apiversion=2",
|
||||
);
|
||||
expect(request).toEqual({
|
||||
headers: {
|
||||
Authorization: "cpanel boy:boybyebye",
|
||||
},
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
});
|
||||
|
||||
await cpanel.zone.fetch();
|
||||
const cpanel = CpanelClient({
|
||||
host: "example.com",
|
||||
port: 2000,
|
||||
username: "boy",
|
||||
apiKey: "boybyebye",
|
||||
domain: "a.b",
|
||||
dependencies: { fetch },
|
||||
});
|
||||
|
||||
await cpanel.zone.fetch();
|
||||
});
|
||||
|
||||
it("should make the correct request with query", async () => {
|
||||
const fetch = mockFetch((url, request) => {
|
||||
expect(url).toBe(
|
||||
"https://example.com:2000/json-api/cpanel?customonly=0&domain=foobar.boeey&cpanel_jsonapi_user=boy&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=fetchzone_records&cpanel_jsonapi_apiversion=2",
|
||||
);
|
||||
expect(request).toEqual({
|
||||
headers: {
|
||||
Authorization: "cpanel boy:boybyebye",
|
||||
},
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
});
|
||||
|
||||
const cpanel = CpanelClient({
|
||||
host: "example.com",
|
||||
port: 2000,
|
||||
username: "boy",
|
||||
apiKey: "boybyebye",
|
||||
domain: "a.b",
|
||||
dependencies: { fetch },
|
||||
});
|
||||
|
||||
await cpanel.zone.fetch({ domain: "foobar.boeey" });
|
||||
});
|
||||
});
|
||||
|
||||
it('should make the correct request with query', async () => {
|
||||
const fetch = mockFetch((url, request) => {
|
||||
expect(url).toBe('https://example.com:2000/json-api/cpanel?customonly=0&domain=foobar.boeey&cpanel_jsonapi_user=boy&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=fetchzone_records&cpanel_jsonapi_apiversion=2');
|
||||
expect(request).toEqual({
|
||||
headers: {
|
||||
Authorization: 'cpanel boy:boybyebye',
|
||||
},
|
||||
rejectUnauthorized: false,
|
||||
describe("addzonerecord", () => {
|
||||
it("should make the correct request", async () => {
|
||||
const fetch = mockFetch((url, request) => {
|
||||
expect(url).toBe(
|
||||
"https://example.com:2000/json-api/cpanel?domain=a.b&name=googo&type=CNAME&cname=beey&ttl=2020&cpanel_jsonapi_user=boy&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=add_zone_record&cpanel_jsonapi_apiversion=2",
|
||||
);
|
||||
expect(request).toEqual({
|
||||
headers: {
|
||||
Authorization: "cpanel boy:boybyebye",
|
||||
},
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
});
|
||||
|
||||
const cpanel = CpanelClient({
|
||||
host: "example.com",
|
||||
port: 2000,
|
||||
username: "boy",
|
||||
apiKey: "boybyebye",
|
||||
domain: "a.b",
|
||||
dependencies: { fetch },
|
||||
});
|
||||
|
||||
await cpanel.zone.add({
|
||||
name: "googo",
|
||||
type: "boyee",
|
||||
cname: "beey",
|
||||
type: "CNAME",
|
||||
ttl: 2020,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const cpanel = CpanelClient({
|
||||
host: 'example.com',
|
||||
port: 2000,
|
||||
username: 'boy',
|
||||
apiKey: 'boybyebye',
|
||||
domain: 'a.b',
|
||||
dependencies: { fetch },
|
||||
});
|
||||
|
||||
await cpanel.zone.fetch({ domain: 'foobar.boeey' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('addzonerecord', () => {
|
||||
it('should make the correct request', async () => {
|
||||
const fetch = mockFetch((url, request) => {
|
||||
expect(url).toBe('https://example.com:2000/json-api/cpanel?domain=a.b&name=googo&type=CNAME&cname=beey&ttl=2020&cpanel_jsonapi_user=boy&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=add_zone_record&cpanel_jsonapi_apiversion=2');
|
||||
expect(request).toEqual({
|
||||
headers: {
|
||||
Authorization: 'cpanel boy:boybyebye',
|
||||
},
|
||||
rejectUnauthorized: false,
|
||||
describe("addzonerecord", () => {
|
||||
it("should make the correct request", async () => {
|
||||
const fetch = mockFetch((url, request) => {
|
||||
expect(url).toBe(
|
||||
"https://example.com:2000/json-api/cpanel?domain=a.b&line=500&cpanel_jsonapi_user=boy&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=remove_zone_record&cpanel_jsonapi_apiversion=2",
|
||||
);
|
||||
expect(request).toEqual({
|
||||
headers: {
|
||||
Authorization: "cpanel boy:boybyebye",
|
||||
},
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
});
|
||||
|
||||
const cpanel = CpanelClient({
|
||||
host: "example.com",
|
||||
port: 2000,
|
||||
username: "boy",
|
||||
apiKey: "boybyebye",
|
||||
domain: "a.b",
|
||||
dependencies: { fetch },
|
||||
});
|
||||
|
||||
await cpanel.zone.remove({
|
||||
line: 500,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const cpanel = CpanelClient({
|
||||
host: 'example.com',
|
||||
port: 2000,
|
||||
username: 'boy',
|
||||
apiKey: 'boybyebye',
|
||||
domain: 'a.b',
|
||||
dependencies: { fetch },
|
||||
});
|
||||
|
||||
await cpanel.zone.add({
|
||||
name: 'googo',
|
||||
type: 'boyee',
|
||||
cname: 'beey',
|
||||
type: 'CNAME',
|
||||
ttl: 2020,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addzonerecord', () => {
|
||||
it('should make the correct request', async () => {
|
||||
const fetch = mockFetch((url, request) => {
|
||||
expect(url).toBe('https://example.com:2000/json-api/cpanel?domain=a.b&line=500&cpanel_jsonapi_user=boy&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=remove_zone_record&cpanel_jsonapi_apiversion=2');
|
||||
expect(request).toEqual({
|
||||
headers: {
|
||||
Authorization: 'cpanel boy:boybyebye',
|
||||
},
|
||||
rejectUnauthorized: false,
|
||||
describe("fetchredirections", () => {
|
||||
it("should make the correct request", async () => {
|
||||
const fetch = mockFetch((url, request) => {
|
||||
expect(url).toBe(
|
||||
"https://example.com:2000/execute/Mime/list_redirects?cpanel_jsonapi_user=boy&cpanel_jsonapi_module=Mime&cpanel_jsonapi_func=list_redirects&cpanel_jsonapi_apiversion=2",
|
||||
);
|
||||
expect(request).toEqual({
|
||||
headers: {
|
||||
Authorization: "cpanel boy:boybyebye",
|
||||
},
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
});
|
||||
|
||||
const cpanel = CpanelClient({
|
||||
host: "example.com",
|
||||
port: 2000,
|
||||
username: "boy",
|
||||
apiKey: "boybyebye",
|
||||
domain: "a.b",
|
||||
dependencies: { fetch },
|
||||
});
|
||||
|
||||
await cpanel.redirection.fetch();
|
||||
});
|
||||
});
|
||||
|
||||
const cpanel = CpanelClient({
|
||||
host: 'example.com',
|
||||
port: 2000,
|
||||
username: 'boy',
|
||||
apiKey: 'boybyebye',
|
||||
domain: 'a.b',
|
||||
dependencies: { fetch },
|
||||
});
|
||||
|
||||
await cpanel.zone.remove({
|
||||
line: 500,
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("addredirection", () => {
|
||||
it("should make the correct request", async () => {
|
||||
const fetch = mockFetch((url, request) => {
|
||||
expect(url).toBe(
|
||||
"https://example.com:2000/execute/Mime/add_redirect?domain=googo&destination=https%3A%2F%2Foodf.com&cpanel_jsonapi_user=boy&cpanel_jsonapi_module=Mime&cpanel_jsonapi_func=add_redirect&cpanel_jsonapi_apiversion=2",
|
||||
);
|
||||
expect(request).toEqual({
|
||||
headers: {
|
||||
Authorization: "cpanel boy:boybyebye",
|
||||
},
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchredirections', () => {
|
||||
it('should make the correct request', async () => {
|
||||
const fetch = mockFetch((url, request) => {
|
||||
expect(url).toBe('https://example.com:2000/execute/Mime/list_redirects?cpanel_jsonapi_user=boy&cpanel_jsonapi_module=Mime&cpanel_jsonapi_func=list_redirects&cpanel_jsonapi_apiversion=2');
|
||||
expect(request).toEqual({
|
||||
headers: {
|
||||
Authorization: 'cpanel boy:boybyebye',
|
||||
},
|
||||
rejectUnauthorized: false,
|
||||
const cpanel = CpanelClient({
|
||||
host: "example.com",
|
||||
port: 2000,
|
||||
username: "boy",
|
||||
apiKey: "boybyebye",
|
||||
domain: "a.b",
|
||||
dependencies: { fetch },
|
||||
});
|
||||
|
||||
await cpanel.redirection.add({
|
||||
domain: "googo",
|
||||
destination: "https://oodf.com",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const cpanel = CpanelClient({
|
||||
host: 'example.com',
|
||||
port: 2000,
|
||||
username: 'boy',
|
||||
apiKey: 'boybyebye',
|
||||
domain: 'a.b',
|
||||
dependencies: { fetch },
|
||||
});
|
||||
|
||||
await cpanel.redirection.fetch();
|
||||
});
|
||||
});
|
||||
describe('addredirection', () => {
|
||||
it('should make the correct request', async () => {
|
||||
const fetch = mockFetch((url, request) => {
|
||||
expect(url).toBe('https://example.com:2000/execute/Mime/add_redirect?domain=googo&destination=https%3A%2F%2Foodf.com&cpanel_jsonapi_user=boy&cpanel_jsonapi_module=Mime&cpanel_jsonapi_func=add_redirect&cpanel_jsonapi_apiversion=2');
|
||||
expect(request).toEqual({
|
||||
headers: {
|
||||
Authorization: 'cpanel boy:boybyebye',
|
||||
},
|
||||
rejectUnauthorized: false,
|
||||
|
||||
describe("deleteredirection", () => {
|
||||
it("should make the correct request", async () => {
|
||||
const fetch = mockFetch((url, request) => {
|
||||
expect(url).toBe(
|
||||
"https://example.com:2000/execute/Mime/delete_redirect?domain=googo&cpanel_jsonapi_user=boy&cpanel_jsonapi_module=Mime&cpanel_jsonapi_func=delete_redirect&cpanel_jsonapi_apiversion=2",
|
||||
);
|
||||
expect(request).toEqual({
|
||||
headers: {
|
||||
Authorization: "cpanel boy:boybyebye",
|
||||
},
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
});
|
||||
|
||||
const cpanel = CpanelClient({
|
||||
host: "example.com",
|
||||
port: 2000,
|
||||
username: "boy",
|
||||
apiKey: "boybyebye",
|
||||
domain: "a.b",
|
||||
dependencies: { fetch },
|
||||
});
|
||||
|
||||
await cpanel.redirection.remove({ domain: "googo" });
|
||||
});
|
||||
});
|
||||
|
||||
const cpanel = CpanelClient({
|
||||
host: 'example.com',
|
||||
port: 2000,
|
||||
username: 'boy',
|
||||
apiKey: 'boybyebye',
|
||||
domain: 'a.b',
|
||||
dependencies: { fetch },
|
||||
});
|
||||
|
||||
await cpanel.redirection.add({
|
||||
domain: 'googo',
|
||||
destination: 'https://oodf.com'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteredirection', () => {
|
||||
it('should make the correct request', async () => {
|
||||
const fetch = mockFetch((url, request) => {
|
||||
expect(url).toBe('https://example.com:2000/execute/Mime/delete_redirect?domain=googo&cpanel_jsonapi_user=boy&cpanel_jsonapi_module=Mime&cpanel_jsonapi_func=delete_redirect&cpanel_jsonapi_apiversion=2');
|
||||
expect(request).toEqual({
|
||||
headers: {
|
||||
Authorization: 'cpanel boy:boybyebye',
|
||||
},
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
});
|
||||
|
||||
const cpanel = CpanelClient({
|
||||
host: 'example.com',
|
||||
port: 2000,
|
||||
username: 'boy',
|
||||
apiKey: 'boybyebye',
|
||||
domain: 'a.b',
|
||||
dependencies: { fetch },
|
||||
});
|
||||
|
||||
await cpanel.redirection.remove({ domain: 'googo' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+391
-314
@@ -1,344 +1,421 @@
|
||||
const R = require('ramda');
|
||||
const { getDomainService, diffRecords } = require('../utils/domain-service');
|
||||
const { DOMAIN_DOMAIN } = require('../utils/constants');
|
||||
const R = require("ramda");
|
||||
const { getDomainService, diffRecords } = require("../utils/domain-service");
|
||||
const { DOMAIN_DOMAIN } = require("../utils/constants");
|
||||
|
||||
const getCpanel = ({ zone, addZone, removeZone, redir, addRedir, removeRedir, addEmail, removeEmail } = {}) => ({
|
||||
zone: {
|
||||
fetch: (_) => zone(),
|
||||
add: (rec) => addZone(rec),
|
||||
remove: (rec) => removeZone(rec),
|
||||
},
|
||||
redirection: {
|
||||
fetch: (_) => redir(),
|
||||
add: (rec) => addRedir(rec),
|
||||
remove: (rec) => removeRedir(rec),
|
||||
},
|
||||
email: {
|
||||
add: (rec) => addEmail(rec),
|
||||
remove: (rec) => removeEmail(rec),
|
||||
},
|
||||
const getCpanel = ({
|
||||
zone,
|
||||
addZone,
|
||||
removeZone,
|
||||
redir,
|
||||
addRedir,
|
||||
removeRedir,
|
||||
addEmail,
|
||||
removeEmail,
|
||||
} = {}) => ({
|
||||
zone: {
|
||||
fetch: (_) => zone(),
|
||||
add: (rec) => addZone(rec),
|
||||
remove: (rec) => removeZone(rec),
|
||||
},
|
||||
redirection: {
|
||||
fetch: (_) => redir(),
|
||||
add: (rec) => addRedir(rec),
|
||||
remove: (rec) => removeRedir(rec),
|
||||
},
|
||||
email: {
|
||||
add: (rec) => addEmail(rec),
|
||||
remove: (rec) => removeEmail(rec),
|
||||
},
|
||||
});
|
||||
|
||||
describe('diffRecords', () => {
|
||||
it('should show added record', () => {
|
||||
const oldRecords = [
|
||||
{ name: 'xx', type: 'CNAME', address: 'fck.com.' },
|
||||
{ name: 'xa', type: 'A', address: '111.1.1212.1' },
|
||||
];
|
||||
const newRecords = [
|
||||
{ name: 'xx', type: 'CNAME', address: 'fck.com.' },
|
||||
{ name: 'xa', type: 'A', address: '111.1.1212.1' },
|
||||
{ name: 'boo', type: 'CNAME', address: 'x.com' },
|
||||
];
|
||||
describe("diffRecords", () => {
|
||||
it("should show added record", () => {
|
||||
const oldRecords = [
|
||||
{ name: "xx", type: "CNAME", address: "fck.com." },
|
||||
{ name: "xa", type: "A", address: "111.1.1212.1" },
|
||||
];
|
||||
const newRecords = [
|
||||
{ name: "xx", type: "CNAME", address: "fck.com." },
|
||||
{ name: "xa", type: "A", address: "111.1.1212.1" },
|
||||
{ name: "boo", type: "CNAME", address: "x.com" },
|
||||
];
|
||||
|
||||
const result = diffRecords(oldRecords, newRecords);
|
||||
expect(result).toEqual({
|
||||
remove: [],
|
||||
add: [
|
||||
{ name: 'boo', type: 'CNAME', address: 'x.com' },
|
||||
],
|
||||
const result = diffRecords(oldRecords, newRecords);
|
||||
expect(result).toEqual({
|
||||
remove: [],
|
||||
add: [{ name: "boo", type: "CNAME", address: "x.com" }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should show edited records', () => {
|
||||
const oldRecords = [
|
||||
{ name: 'xx', type: 'CNAME', address: 'fck.com.' },
|
||||
{ name: 'xa', type: 'A', address: '111.1.1212.1' },
|
||||
];
|
||||
const newRecords = [
|
||||
{ name: 'xx', type: 'CNAME', address: 'fck.com.' },
|
||||
{ name: 'xa', type: 'A', address: '69.69.69.69' },
|
||||
];
|
||||
it("should show edited records", () => {
|
||||
const oldRecords = [
|
||||
{ name: "xx", type: "CNAME", address: "fck.com." },
|
||||
{ name: "xa", type: "A", address: "111.1.1212.1" },
|
||||
];
|
||||
const newRecords = [
|
||||
{ name: "xx", type: "CNAME", address: "fck.com." },
|
||||
{ name: "xa", type: "A", address: "69.69.69.69" },
|
||||
];
|
||||
|
||||
const result = diffRecords(oldRecords, newRecords);
|
||||
expect(result).toEqual({
|
||||
remove: [
|
||||
{ name: 'xa', type: 'A', address: '111.1.1212.1' },
|
||||
],
|
||||
add: [
|
||||
{ name: 'xa', type: 'A', address: '69.69.69.69' },
|
||||
],
|
||||
const result = diffRecords(oldRecords, newRecords);
|
||||
expect(result).toEqual({
|
||||
remove: [{ name: "xa", type: "A", address: "111.1.1212.1" }],
|
||||
add: [{ name: "xa", type: "A", address: "69.69.69.69" }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should show added records with the same name and record type', () => {
|
||||
const oldRecords = [
|
||||
{ name: 'xx', type: 'CNAME', address: 'fck.com.' },
|
||||
{ name: 'xa', type: 'A', address: '69.69.69.69' },
|
||||
];
|
||||
const newRecords = [
|
||||
{ name: 'xx', type: 'CNAME', address: 'fck.com.' },
|
||||
{ name: 'xa', type: 'A', address: '69.69.69.69' },
|
||||
{ name: 'xa', type: 'A', address: '69.69.4.20' },
|
||||
];
|
||||
it("should show added records with the same name and record type", () => {
|
||||
const oldRecords = [
|
||||
{ name: "xx", type: "CNAME", address: "fck.com." },
|
||||
{ name: "xa", type: "A", address: "69.69.69.69" },
|
||||
];
|
||||
const newRecords = [
|
||||
{ name: "xx", type: "CNAME", address: "fck.com." },
|
||||
{ name: "xa", type: "A", address: "69.69.69.69" },
|
||||
{ name: "xa", type: "A", address: "69.69.4.20" },
|
||||
];
|
||||
|
||||
const result = diffRecords(oldRecords, newRecords);
|
||||
expect(result).toEqual({
|
||||
remove: [],
|
||||
add: [
|
||||
{ name: 'xa', type: 'A', address: '69.69.4.20' },
|
||||
],
|
||||
const result = diffRecords(oldRecords, newRecords);
|
||||
expect(result).toEqual({
|
||||
remove: [],
|
||||
add: [{ name: "xa", type: "A", address: "69.69.4.20" }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should diff complex changes', () => {
|
||||
const oldRecords = [
|
||||
{ name: 'a', type: 'CNAME', address: 'fck.com.' },
|
||||
{ name: 'b', type: 'A', address: '69.69.69.69' },
|
||||
{ name: '111', type: 'CNAME', address: 'x' },
|
||||
{ name: 'd', type: 'A', address: '69.69.4.20' },
|
||||
];
|
||||
const newRecords = [
|
||||
{ name: '111', type: 'CNAME', address: 'x' },
|
||||
{ name: 'd', type: 'CNAME', address: 'duck.com' },
|
||||
{ name: 'a', type: 'CNAME', address: 'og.com' },
|
||||
{ name: 'b', type: 'A', address: '69.69.69.69' },
|
||||
{ name: 'b', type: 'A', address: '69.69.4.20' },
|
||||
{ name: 'c', type: 'CNAME', address: 'ccc.cc' },
|
||||
];
|
||||
it("should diff complex changes", () => {
|
||||
const oldRecords = [
|
||||
{ name: "a", type: "CNAME", address: "fck.com." },
|
||||
{ name: "b", type: "A", address: "69.69.69.69" },
|
||||
{ name: "111", type: "CNAME", address: "x" },
|
||||
{ name: "d", type: "A", address: "69.69.4.20" },
|
||||
];
|
||||
const newRecords = [
|
||||
{ name: "111", type: "CNAME", address: "x" },
|
||||
{ name: "d", type: "CNAME", address: "duck.com" },
|
||||
{ name: "a", type: "CNAME", address: "og.com" },
|
||||
{ name: "b", type: "A", address: "69.69.69.69" },
|
||||
{ name: "b", type: "A", address: "69.69.4.20" },
|
||||
{ name: "c", type: "CNAME", address: "ccc.cc" },
|
||||
];
|
||||
|
||||
const result = diffRecords(oldRecords, newRecords);
|
||||
expect(result).toEqual({
|
||||
remove: [
|
||||
{ name: 'a', type: 'CNAME', address: 'fck.com.' },
|
||||
{ name: 'd', type: 'A', address: '69.69.4.20' },
|
||||
],
|
||||
add: [
|
||||
{ name: 'd', type: 'CNAME', address: 'duck.com' },
|
||||
{ name: 'a', type: 'CNAME', address: 'og.com' },
|
||||
{ name: 'b', type: 'A', address: '69.69.4.20' },
|
||||
{ name: 'c', type: 'CNAME', address: 'ccc.cc' },
|
||||
],
|
||||
const result = diffRecords(oldRecords, newRecords);
|
||||
expect(result).toEqual({
|
||||
remove: [
|
||||
{ name: "a", type: "CNAME", address: "fck.com." },
|
||||
{ name: "d", type: "A", address: "69.69.4.20" },
|
||||
],
|
||||
add: [
|
||||
{ name: "d", type: "CNAME", address: "duck.com" },
|
||||
{ name: "a", type: "CNAME", address: "og.com" },
|
||||
{ name: "b", type: "A", address: "69.69.4.20" },
|
||||
{ name: "c", type: "CNAME", address: "ccc.cc" },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Domain service', () => {
|
||||
const addZone = jest.fn(async () => ({}));
|
||||
const removeZone = jest.fn(async () => ({}));
|
||||
const addRedir = jest.fn(async () => ({}));
|
||||
const removeRedir = jest.fn(async () => ({}));
|
||||
const addEmail = jest.fn(async () => ({}));
|
||||
const removeEmail = jest.fn(async () => ({}));
|
||||
describe("Domain service", () => {
|
||||
const addZone = jest.fn(async () => ({}));
|
||||
const removeZone = jest.fn(async () => ({}));
|
||||
const addRedir = jest.fn(async () => ({}));
|
||||
const removeRedir = jest.fn(async () => ({}));
|
||||
const addEmail = jest.fn(async () => ({}));
|
||||
const removeEmail = jest.fn(async () => ({}));
|
||||
|
||||
const mockDS = ({ zones, redirections }) => getDomainService({
|
||||
cpanel: getCpanel({
|
||||
zone: async () => zones,
|
||||
redir: async () => redirections,
|
||||
addZone,
|
||||
addEmail,
|
||||
addRedir,
|
||||
removeZone,
|
||||
removeRedir,
|
||||
removeEmail,
|
||||
})
|
||||
});
|
||||
const mockDS = ({ zones, redirections }) =>
|
||||
getDomainService({
|
||||
cpanel: getCpanel({
|
||||
zone: async () => zones,
|
||||
redir: async () => redirections,
|
||||
addZone,
|
||||
addEmail,
|
||||
addRedir,
|
||||
removeZone,
|
||||
removeRedir,
|
||||
removeEmail,
|
||||
}),
|
||||
});
|
||||
|
||||
const getRecordCalls = recfn => recfn.mock.calls
|
||||
.map(R.head)
|
||||
.map(R.pick(['name', 'type', 'address', 'redirect', 'domain', 'line', 'priority', 'exchanger']));
|
||||
const getRecordCalls = (recfn) =>
|
||||
recfn.mock.calls
|
||||
.map(R.head)
|
||||
.map(
|
||||
R.pick([
|
||||
"name",
|
||||
"type",
|
||||
"address",
|
||||
"redirect",
|
||||
"domain",
|
||||
"line",
|
||||
"priority",
|
||||
"exchanger",
|
||||
]),
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
addZone.mockClear();
|
||||
removeZone.mockClear();
|
||||
addRedir.mockClear();
|
||||
removeRedir.mockClear();
|
||||
addEmail.mockClear();
|
||||
removeEmail.mockClear();
|
||||
});
|
||||
|
||||
describe('getHosts', () => {
|
||||
it('should resolve with a list of hosts', async () => {
|
||||
const zones = [
|
||||
{ name: 'xx', type: 'CNAME', address: 'fck.com.' },
|
||||
{ name: 'xx', type: 'A', address: '111.1.1212.1' },
|
||||
];
|
||||
const redirections = [];
|
||||
const zone = async () => zones;
|
||||
const redir = async () => redirections;
|
||||
const mockDomainService = getDomainService({ cpanel: getCpanel({ zone, redir }) });
|
||||
const list = await mockDomainService.getHosts();
|
||||
|
||||
expect(list).toEqual([
|
||||
{ name: 'xx', type: 'CNAME', address: 'fck.com' },
|
||||
{ name: 'xx', type: 'A', address: '111.1.1212.1' },
|
||||
]);
|
||||
beforeEach(() => {
|
||||
addZone.mockClear();
|
||||
removeZone.mockClear();
|
||||
addRedir.mockClear();
|
||||
removeRedir.mockClear();
|
||||
addEmail.mockClear();
|
||||
removeEmail.mockClear();
|
||||
});
|
||||
|
||||
it('should resolve with a redirections', async () => {
|
||||
const zones = [
|
||||
{ line: '111', name: 'xx', type: 'CNAME', address: 'fck.com.' },
|
||||
{ line: '112', name: 'xx', type: 'A', address: '111.1.1212.1' },
|
||||
];
|
||||
const redirections = [
|
||||
{ domain: 'foo.booboo.xyz', destination: 'https://google.com' },
|
||||
{ domain: 'foo1.booboo.xyz', destination: 'https://duck.com' },
|
||||
];
|
||||
const zone = async () => zones;
|
||||
const redir = async () => redirections;
|
||||
const mockDomainService = getDomainService({ cpanel: getCpanel({ zone, redir }) });
|
||||
const list = await mockDomainService.getHosts();
|
||||
describe("getHosts", () => {
|
||||
it("should resolve with a list of hosts", async () => {
|
||||
const zones = [
|
||||
{ name: "xx", type: "CNAME", address: "fck.com." },
|
||||
{ name: "xx", type: "A", address: "111.1.1212.1" },
|
||||
];
|
||||
const redirections = [];
|
||||
const zone = async () => zones;
|
||||
const redir = async () => redirections;
|
||||
const mockDomainService = getDomainService({
|
||||
cpanel: getCpanel({ zone, redir }),
|
||||
});
|
||||
const list = await mockDomainService.getHosts();
|
||||
|
||||
expect(list).toEqual([
|
||||
{ id: '111', name: 'xx', type: 'CNAME', address: 'fck.com' },
|
||||
{ id: '112', name: 'xx', type: 'A', address: '111.1.1212.1' },
|
||||
{ id: `foo.${DOMAIN_DOMAIN}`, name: 'foo', type: 'URL', address: 'https://google.com' },
|
||||
{ id: `foo1.${DOMAIN_DOMAIN}`, name: 'foo1', type: 'URL', address: 'https://duck.com' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
expect(list).toEqual([
|
||||
{ name: "xx", type: "CNAME", address: "fck.com" },
|
||||
{ name: "xx", type: "A", address: "111.1.1212.1" },
|
||||
]);
|
||||
});
|
||||
|
||||
describe('updateHosts', () => {
|
||||
it('should append new hosts with existing ones and set it', async () => {
|
||||
const zones = [
|
||||
{ line: 1, name: 'a', type: 'CNAME', address: 'boo' },
|
||||
{ line: 2, name: 'b', type: 'CNAME', address: 'goo' },
|
||||
];
|
||||
const redirections = [];
|
||||
it("should resolve with a redirections", async () => {
|
||||
const zones = [
|
||||
{ line: "111", name: "xx", type: "CNAME", address: "fck.com." },
|
||||
{ line: "112", name: "xx", type: "A", address: "111.1.1212.1" },
|
||||
];
|
||||
const redirections = [
|
||||
{ domain: "foo.booboo.xyz", destination: "https://google.com" },
|
||||
{ domain: "foo1.booboo.xyz", destination: "https://duck.com" },
|
||||
];
|
||||
const zone = async () => zones;
|
||||
const redir = async () => redirections;
|
||||
const mockDomainService = getDomainService({
|
||||
cpanel: getCpanel({ zone, redir }),
|
||||
});
|
||||
const list = await mockDomainService.getHosts();
|
||||
|
||||
const mockDomainService = mockDS({ zones, redirections });
|
||||
await mockDomainService.updateHosts([
|
||||
{ name: 'a', type: 'CNAME', address: 'boo' },
|
||||
{ name: 'b', type: 'CNAME', address: 'goo' },
|
||||
{ name: 'c', type: 'A', address: '12.131321.213' },
|
||||
{ name: 'c', type: 'MX', address: 'foobar.com', priority: 2 },
|
||||
]);
|
||||
|
||||
expect(addZone).toHaveBeenCalledTimes(1);
|
||||
expect(getRecordCalls(addZone)).toEqual([
|
||||
{ name: 'c', type: 'A', address: '12.131321.213' },
|
||||
]);
|
||||
|
||||
expect(addEmail).toHaveBeenCalledTimes(1);
|
||||
expect(getRecordCalls(addEmail)).toEqual([
|
||||
{ domain: 'c.is-a.dev', exchanger: 'foobar.com', priority: 2 },
|
||||
]);
|
||||
|
||||
expect(removeZone).toHaveBeenCalledTimes(0);
|
||||
expect(removeEmail).toHaveBeenCalledTimes(0);
|
||||
expect(list).toEqual([
|
||||
{ id: "111", name: "xx", type: "CNAME", address: "fck.com" },
|
||||
{ id: "112", name: "xx", type: "A", address: "111.1.1212.1" },
|
||||
{
|
||||
id: `foo.${DOMAIN_DOMAIN}`,
|
||||
name: "foo",
|
||||
type: "URL",
|
||||
address: "https://google.com",
|
||||
},
|
||||
{
|
||||
id: `foo1.${DOMAIN_DOMAIN}`,
|
||||
name: "foo1",
|
||||
type: "URL",
|
||||
address: "https://duck.com",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should update matching host and set it', async () => {
|
||||
const zones = [
|
||||
{ line: 1, name: 'a', type: 'CNAME', address: 'boo' },
|
||||
{ line: 2, name: 'b', type: 'CNAME', address: 'goo' },
|
||||
];
|
||||
const redirections = [];
|
||||
describe("updateHosts", () => {
|
||||
it("should append new hosts with existing ones and set it", async () => {
|
||||
const zones = [
|
||||
{ line: 1, name: "a", type: "CNAME", address: "boo" },
|
||||
{ line: 2, name: "b", type: "CNAME", address: "goo" },
|
||||
];
|
||||
const redirections = [];
|
||||
|
||||
const mockDomainService = mockDS({ zones, redirections });
|
||||
await mockDomainService.updateHosts([
|
||||
{ name: 'a', type: 'CNAME', address: 'boo' },
|
||||
{ name: 'b', type: 'CNAME', address: 'googoogaga' },
|
||||
]);
|
||||
const mockDomainService = mockDS({ zones, redirections });
|
||||
await mockDomainService.updateHosts([
|
||||
{ name: "a", type: "CNAME", address: "boo" },
|
||||
{ name: "b", type: "CNAME", address: "goo" },
|
||||
{ name: "c", type: "A", address: "12.131321.213" },
|
||||
{ name: "c", type: "MX", address: "foobar.com", priority: 2 },
|
||||
]);
|
||||
|
||||
expect(addZone).toHaveBeenCalledTimes(1);
|
||||
expect(getRecordCalls(addZone)).toEqual([
|
||||
{ name: 'b', type: 'CNAME', address: 'googoogaga' },
|
||||
]);
|
||||
expect(removeZone).toHaveBeenCalledTimes(1);
|
||||
expect(getRecordCalls(removeZone)).toEqual([
|
||||
{ line: 2 },
|
||||
]);
|
||||
expect(addZone).toHaveBeenCalledTimes(1);
|
||||
expect(getRecordCalls(addZone)).toEqual([
|
||||
{ name: "c", type: "A", address: "12.131321.213" },
|
||||
]);
|
||||
|
||||
expect(addEmail).toHaveBeenCalledTimes(1);
|
||||
expect(getRecordCalls(addEmail)).toEqual([
|
||||
{ domain: "c.is-a.dev", exchanger: "foobar.com", priority: 2 },
|
||||
]);
|
||||
|
||||
expect(removeZone).toHaveBeenCalledTimes(0);
|
||||
expect(removeEmail).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it("should update matching host and set it", async () => {
|
||||
const zones = [
|
||||
{ line: 1, name: "a", type: "CNAME", address: "boo" },
|
||||
{ line: 2, name: "b", type: "CNAME", address: "goo" },
|
||||
];
|
||||
const redirections = [];
|
||||
|
||||
const mockDomainService = mockDS({ zones, redirections });
|
||||
await mockDomainService.updateHosts([
|
||||
{ name: "a", type: "CNAME", address: "boo" },
|
||||
{ name: "b", type: "CNAME", address: "googoogaga" },
|
||||
]);
|
||||
|
||||
expect(addZone).toHaveBeenCalledTimes(1);
|
||||
expect(getRecordCalls(addZone)).toEqual([
|
||||
{ name: "b", type: "CNAME", address: "googoogaga" },
|
||||
]);
|
||||
expect(removeZone).toHaveBeenCalledTimes(1);
|
||||
expect(getRecordCalls(removeZone)).toEqual([{ line: 2 }]);
|
||||
});
|
||||
|
||||
it("should update matching host and set it", async () => {
|
||||
const zones = [
|
||||
{ line: 1, name: "a", type: "CNAME", address: "boo" },
|
||||
{ line: 2, name: "b", type: "CNAME", address: "goo" },
|
||||
{ line: 3, name: "b", type: "CNAME", address: "xaa" },
|
||||
];
|
||||
const redirections = [];
|
||||
|
||||
const mockDomainService = mockDS({ zones, redirections });
|
||||
await mockDomainService.updateHosts([
|
||||
{ name: "a", type: "CNAME", address: "boo" },
|
||||
{ name: "b", type: "CNAME", address: "googoogaga" },
|
||||
{ name: "b", type: "CNAME", address: "farboo" },
|
||||
]);
|
||||
|
||||
expect(addZone).toHaveBeenCalledTimes(2);
|
||||
expect(getRecordCalls(addZone)).toEqual([
|
||||
{ name: "b", type: "CNAME", address: "googoogaga" },
|
||||
{ name: "b", type: "CNAME", address: "farboo" },
|
||||
]);
|
||||
expect(removeZone).toHaveBeenCalledTimes(2);
|
||||
expect(getRecordCalls(removeZone)).toEqual([
|
||||
{ line: 3 },
|
||||
{ line: 2 },
|
||||
]);
|
||||
});
|
||||
|
||||
it("should workout this complex example", async () => {
|
||||
const zones = [
|
||||
{ line: 1, name: "a", type: "CNAME", address: "world" },
|
||||
{ line: 2, name: "b", type: "A", address: "1" },
|
||||
{ line: 3, name: "b", type: "A", address: "2" },
|
||||
{ line: 4, name: "c", type: "CNAME", address: "hello.com" },
|
||||
{
|
||||
line: 5,
|
||||
name: "c",
|
||||
type: "MX",
|
||||
address: "mx1.hello.com",
|
||||
priority: 20,
|
||||
},
|
||||
{
|
||||
line: 6,
|
||||
name: "c",
|
||||
type: "MX",
|
||||
address: "mx2.hello.com",
|
||||
priority: 21,
|
||||
},
|
||||
{
|
||||
line: 7,
|
||||
name: "b",
|
||||
type: "MX",
|
||||
address: "foo.bar",
|
||||
priority: 20,
|
||||
},
|
||||
{ line: 101, name: "x", type: "A", address: "1" },
|
||||
{ line: 99, name: "y", type: "A", address: "2" },
|
||||
{ line: 100, name: "z", type: "A", address: "3" },
|
||||
];
|
||||
const redirections = [
|
||||
{
|
||||
domain: `b.${DOMAIN_DOMAIN}`,
|
||||
destination: "https://foobar.com",
|
||||
},
|
||||
{
|
||||
domain: `c.${DOMAIN_DOMAIN}`,
|
||||
destination: "https://goobar.com",
|
||||
},
|
||||
{
|
||||
domain: `x.${DOMAIN_DOMAIN}`,
|
||||
destination: "https://example.com",
|
||||
},
|
||||
];
|
||||
|
||||
const mockDomainService = mockDS({ zones, redirections });
|
||||
await mockDomainService.updateHosts([
|
||||
{ name: "a", type: "CNAME", address: "boo" },
|
||||
{ name: "b", type: "A", address: "1" },
|
||||
{ name: "b", type: "A", address: "2" },
|
||||
{ name: "b", type: "A", address: "3" },
|
||||
{ name: "b", type: "URL", address: "https://wowow.com" },
|
||||
{ name: "c", type: "CNAME", address: "hello.com" },
|
||||
{ name: "c", type: "URL", address: "https://goobar.com" },
|
||||
{ name: "d", type: "CNAME", address: "helo.com" },
|
||||
{ name: "d", type: "URL", address: "https://hhh.com" },
|
||||
{ name: "x", type: "URL", address: "https://example69.com" },
|
||||
{
|
||||
name: "c",
|
||||
type: "MX",
|
||||
address: "mx2.hello.com",
|
||||
priority: 21,
|
||||
},
|
||||
{ name: "a", type: "MX", address: "example.com", priority: 20 },
|
||||
]);
|
||||
|
||||
expect(addZone).toHaveBeenCalledTimes(3);
|
||||
expect(getRecordCalls(addZone)).toEqual([
|
||||
{ name: "a", type: "CNAME", address: "boo" },
|
||||
{ name: "b", type: "A", address: "3" },
|
||||
{ name: "d", type: "CNAME", address: "helo.com" },
|
||||
]);
|
||||
expect(removeZone).toHaveBeenCalledTimes(4);
|
||||
expect(getRecordCalls(removeZone)).toEqual([
|
||||
{ line: 101 },
|
||||
{ line: 100 },
|
||||
{ line: 99 },
|
||||
{ line: 1 },
|
||||
]);
|
||||
|
||||
expect(addEmail).toHaveBeenCalledTimes(1);
|
||||
expect(getRecordCalls(addEmail)).toEqual([
|
||||
{
|
||||
domain: "a.is-a.dev",
|
||||
exchanger: "example.com",
|
||||
priority: 20,
|
||||
},
|
||||
]);
|
||||
expect(removeEmail).toHaveBeenCalledTimes(2);
|
||||
expect(getRecordCalls(removeEmail)).toEqual([
|
||||
{ domain: "b.is-a.dev", exchanger: "foo.bar", priority: 20 },
|
||||
{
|
||||
domain: "c.is-a.dev",
|
||||
exchanger: "mx1.hello.com",
|
||||
priority: 20,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(addRedir).toHaveBeenCalledTimes(3);
|
||||
expect(getRecordCalls(addRedir)).toEqual([
|
||||
{
|
||||
domain: `b.${DOMAIN_DOMAIN}`,
|
||||
type: "permanent",
|
||||
redirect: "https://wowow.com",
|
||||
},
|
||||
{
|
||||
domain: `d.${DOMAIN_DOMAIN}`,
|
||||
type: "permanent",
|
||||
redirect: "https://hhh.com",
|
||||
},
|
||||
{
|
||||
domain: `x.${DOMAIN_DOMAIN}`,
|
||||
type: "permanent",
|
||||
redirect: "https://example69.com",
|
||||
},
|
||||
]);
|
||||
expect(removeRedir).toHaveBeenCalledTimes(2);
|
||||
expect(getRecordCalls(removeRedir)).toEqual([
|
||||
{ domain: `b.${DOMAIN_DOMAIN}` },
|
||||
{ domain: `x.${DOMAIN_DOMAIN}` },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should update matching host and set it', async () => {
|
||||
const zones = [
|
||||
{ line: 1, name: 'a', type: 'CNAME', address: 'boo' },
|
||||
{ line: 2, name: 'b', type: 'CNAME', address: 'goo' },
|
||||
{ line: 3, name: 'b', type: 'CNAME', address: 'xaa' },
|
||||
];
|
||||
const redirections = [];
|
||||
|
||||
const mockDomainService = mockDS({ zones, redirections });
|
||||
await mockDomainService.updateHosts([
|
||||
{ name: 'a', type: 'CNAME', address: 'boo' },
|
||||
{ name: 'b', type: 'CNAME', address: 'googoogaga' },
|
||||
{ name: 'b', type: 'CNAME', address: 'farboo' },
|
||||
]);
|
||||
|
||||
expect(addZone).toHaveBeenCalledTimes(2);
|
||||
expect(getRecordCalls(addZone)).toEqual([
|
||||
{ name: 'b', type: 'CNAME', address: 'googoogaga' },
|
||||
{ name: 'b', type: 'CNAME', address: 'farboo' },
|
||||
]);
|
||||
expect(removeZone).toHaveBeenCalledTimes(2);
|
||||
expect(getRecordCalls(removeZone)).toEqual([
|
||||
{ line: 3 },
|
||||
{ line: 2 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should workout this complex example', async () => {
|
||||
const zones = [
|
||||
{ line: 1, name: 'a', type: 'CNAME', address: 'world' },
|
||||
{ line: 2, name: 'b', type: 'A', address: '1' },
|
||||
{ line: 3, name: 'b', type: 'A', address: '2' },
|
||||
{ line: 4, name: 'c', type: 'CNAME', address: 'hello.com' },
|
||||
{ line: 5, name: 'c', type: 'MX', address: 'mx1.hello.com', priority: 20 },
|
||||
{ line: 6, name: 'c', type: 'MX', address: 'mx2.hello.com', priority: 21 },
|
||||
{ line: 7, name: 'b', type: 'MX', address: 'foo.bar', priority: 20 },
|
||||
{ line: 101, name: 'x', type: 'A', address: '1' },
|
||||
{ line: 99, name: 'y', type: 'A', address: '2' },
|
||||
{ line: 100, name: 'z', type: 'A', address: '3' },
|
||||
];
|
||||
const redirections = [
|
||||
{ domain: `b.${DOMAIN_DOMAIN}`, destination: 'https://foobar.com' },
|
||||
{ domain: `c.${DOMAIN_DOMAIN}`, destination: 'https://goobar.com' },
|
||||
{ domain: `x.${DOMAIN_DOMAIN}`, destination: 'https://example.com' },
|
||||
];
|
||||
|
||||
const mockDomainService = mockDS({ zones, redirections });
|
||||
await mockDomainService.updateHosts([
|
||||
{ name: 'a', type: 'CNAME', address: 'boo' },
|
||||
{ name: 'b', type: 'A', address: '1' },
|
||||
{ name: 'b', type: 'A', address: '2' },
|
||||
{ name: 'b', type: 'A', address: '3' },
|
||||
{ name: 'b', type: 'URL', address: 'https://wowow.com' },
|
||||
{ name: 'c', type: 'CNAME', address: 'hello.com' },
|
||||
{ name: 'c', type: 'URL', address: 'https://goobar.com' },
|
||||
{ name: 'd', type: 'CNAME', address: 'helo.com' },
|
||||
{ name: 'd', type: 'URL', address: 'https://hhh.com' },
|
||||
{ name: 'x', type: 'URL', address: 'https://example69.com' },
|
||||
{ name: 'c', type: 'MX', address: 'mx2.hello.com', priority: 21 },
|
||||
{ name: 'a', type: 'MX', address: 'example.com', priority: 20 },
|
||||
]);
|
||||
|
||||
expect(addZone).toHaveBeenCalledTimes(3);
|
||||
expect(getRecordCalls(addZone)).toEqual([
|
||||
{ name: 'a', type: 'CNAME', address: 'boo' },
|
||||
{ name: 'b', type: 'A', address: '3' },
|
||||
{ name: 'd', type: 'CNAME', address: 'helo.com' },
|
||||
]);
|
||||
expect(removeZone).toHaveBeenCalledTimes(4);
|
||||
expect(getRecordCalls(removeZone)).toEqual([
|
||||
{ line: 101 },
|
||||
{ line: 100 },
|
||||
{ line: 99 },
|
||||
{ line: 1 },
|
||||
]);
|
||||
|
||||
expect(addEmail).toHaveBeenCalledTimes(1);
|
||||
expect(getRecordCalls(addEmail)).toEqual([
|
||||
{ domain: 'a.is-a.dev', exchanger: 'example.com', priority: 20 },
|
||||
]);
|
||||
expect(removeEmail).toHaveBeenCalledTimes(2);
|
||||
expect(getRecordCalls(removeEmail)).toEqual([
|
||||
{ domain: 'b.is-a.dev', exchanger: 'foo.bar', priority: 20 },
|
||||
{ domain: 'c.is-a.dev', exchanger: 'mx1.hello.com', priority: 20 },
|
||||
]);
|
||||
|
||||
expect(addRedir).toHaveBeenCalledTimes(3);
|
||||
expect(getRecordCalls(addRedir)).toEqual([
|
||||
{ domain: `b.${DOMAIN_DOMAIN}`, type: 'permanent', redirect: 'https://wowow.com' },
|
||||
{ domain: `d.${DOMAIN_DOMAIN}`, type: 'permanent', redirect: 'https://hhh.com' },
|
||||
{ domain: `x.${DOMAIN_DOMAIN}`, type: 'permanent', redirect: 'https://example69.com' },
|
||||
]);
|
||||
expect(removeRedir).toHaveBeenCalledTimes(2);
|
||||
expect(getRecordCalls(removeRedir)).toEqual([
|
||||
{ domain: `b.${DOMAIN_DOMAIN}` },
|
||||
{ domain: `x.${DOMAIN_DOMAIN}` },
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
const { getDomains } = require('../utils/get-domain');
|
||||
const { getDomains } = require("../utils/get-domain");
|
||||
|
||||
describe('getDomains', () => {
|
||||
it('should resolve with the list of domains', async () => {
|
||||
const list = await getDomains();
|
||||
expect(Array.isArray(list)).toBe(true);
|
||||
});
|
||||
describe("getDomains", () => {
|
||||
it("should resolve with the list of domains", async () => {
|
||||
const list = await getDomains();
|
||||
expect(Array.isArray(list)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+34
-28
@@ -1,31 +1,37 @@
|
||||
const R = require('ramda');
|
||||
const fs = require('fs');
|
||||
const { getDomains } = require('../utils/get-domain');
|
||||
const { validateDomainData } = require('../utils/validations');
|
||||
const { DOMAINS_PATH } = require('../utils/constants');
|
||||
const R = require("ramda");
|
||||
const fs = require("fs");
|
||||
const { getDomains } = require("../utils/get-domain");
|
||||
const { validateDomainData } = require("../utils/validations");
|
||||
const { DOMAINS_PATH } = require("../utils/constants");
|
||||
|
||||
describe('Domains', () => {
|
||||
it('should all be json', async () => {
|
||||
const files = await fs.promises.readdir(DOMAINS_PATH, {});
|
||||
expect(files.filter(f => !/\.json$/g.test(f)).length).toBe(0);
|
||||
});
|
||||
describe("Domains", () => {
|
||||
it("should all be json", async () => {
|
||||
const files = await fs.promises.readdir(DOMAINS_PATH, {});
|
||||
expect(files.filter((f) => !/\.json$/g.test(f)).length).toBe(0);
|
||||
});
|
||||
|
||||
it('should be valid', (done) => {
|
||||
getDomains()
|
||||
.then(R.reject(R.propEq('name', '_psl')))
|
||||
.then(R.map(data => {
|
||||
const { errors } = validateDomainData(data);
|
||||
if (errors.length) {
|
||||
const message = errors
|
||||
.map(([key, { reason }]) => `[${data.name}.${key}]: ${reason}`)
|
||||
.join('\n');
|
||||
return `\nValidation errors in ${data.name}.json: \n${message}`;
|
||||
}
|
||||
return '';
|
||||
}))
|
||||
.then(R.filter(R.complement(R.isEmpty)))
|
||||
.then(messages => messages.length ? done(messages.join('\n')) : done())
|
||||
.catch(done);
|
||||
});
|
||||
it("should be valid", (done) => {
|
||||
getDomains()
|
||||
.then(R.reject(R.propEq("name", "_psl")))
|
||||
.then(
|
||||
R.map((data) => {
|
||||
const { errors } = validateDomainData(data);
|
||||
if (errors.length) {
|
||||
const message = errors
|
||||
.map(
|
||||
([key, { reason }]) =>
|
||||
`[${data.name}.${key}]: ${reason}`,
|
||||
)
|
||||
.join("\n");
|
||||
return `\nValidation errors in ${data.name}.json: \n${message}`;
|
||||
}
|
||||
return "";
|
||||
}),
|
||||
)
|
||||
.then(R.filter(R.complement(R.isEmpty)))
|
||||
.then((messages) =>
|
||||
messages.length ? done(messages.join("\n")) : done(),
|
||||
)
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+225
-192
@@ -1,209 +1,242 @@
|
||||
const R = require('ramda')
|
||||
const { toHostList, registerDomains } = require('../scripts/register-domains')
|
||||
const { TTL, DOMAIN_DOMAIN } = require('../utils/constants')
|
||||
const { getDomainService } = require('../utils/domain-service')
|
||||
const R = require("ramda");
|
||||
const { toHostList, registerDomains } = require("../scripts/register-domains");
|
||||
const { TTL, DOMAIN_DOMAIN } = require("../utils/constants");
|
||||
const { getDomainService } = require("../utils/domain-service");
|
||||
|
||||
const getCpanel = ({
|
||||
zone,
|
||||
addZone,
|
||||
removeZone,
|
||||
redir,
|
||||
addRedir,
|
||||
removeRedir,
|
||||
addEmail,
|
||||
removeEmail,
|
||||
zone,
|
||||
addZone,
|
||||
removeZone,
|
||||
redir,
|
||||
addRedir,
|
||||
removeRedir,
|
||||
addEmail,
|
||||
removeEmail,
|
||||
} = {}) => ({
|
||||
zone: {
|
||||
fetch: (_) => zone(),
|
||||
add: (rec) => addZone(rec),
|
||||
remove: (rec) => removeZone(rec),
|
||||
},
|
||||
redirection: {
|
||||
fetch: (_) => redir(),
|
||||
add: (rec) => addRedir(rec),
|
||||
remove: (rec) => removeRedir(rec),
|
||||
},
|
||||
email: {
|
||||
add: (rec) => addEmail(rec),
|
||||
remove: (rec) => removeEmail(rec),
|
||||
},
|
||||
})
|
||||
zone: {
|
||||
fetch: (_) => zone(),
|
||||
add: (rec) => addZone(rec),
|
||||
remove: (rec) => removeZone(rec),
|
||||
},
|
||||
redirection: {
|
||||
fetch: (_) => redir(),
|
||||
add: (rec) => addRedir(rec),
|
||||
remove: (rec) => removeRedir(rec),
|
||||
},
|
||||
email: {
|
||||
add: (rec) => addEmail(rec),
|
||||
remove: (rec) => removeEmail(rec),
|
||||
},
|
||||
});
|
||||
|
||||
describe('toHostList', () => {
|
||||
it('should flatten domain data to list of hosts (without https)', () => {
|
||||
const res = toHostList([
|
||||
{ name: 'akshay', record: { CNAME: 'phenax.github.io' } },
|
||||
{ name: 'foobar', record: { CNAME: 'v.io' } },
|
||||
{ name: 'xx', record: { A: ['1.2.3.4', '5.6.3.2', '1.2.31.1'] } },
|
||||
{ name: 'xx', record: { CNAME: 'foobar.com', MX: ['as.com', 'f.com'] } },
|
||||
])
|
||||
describe("toHostList", () => {
|
||||
it("should flatten domain data to list of hosts (without https)", () => {
|
||||
const res = toHostList([
|
||||
{ name: "akshay", record: { CNAME: "phenax.github.io" } },
|
||||
{ name: "foobar", record: { CNAME: "v.io" } },
|
||||
{ name: "xx", record: { A: ["1.2.3.4", "5.6.3.2", "1.2.31.1"] } },
|
||||
{
|
||||
name: "xx",
|
||||
record: { CNAME: "foobar.com", MX: ["as.com", "f.com"] },
|
||||
},
|
||||
]);
|
||||
|
||||
expect(res).toEqual([
|
||||
{ name: 'akshay', type: 'CNAME', address: 'phenax.github.io', ttl: TTL },
|
||||
{ name: 'foobar', type: 'CNAME', address: 'v.io', ttl: TTL },
|
||||
{ name: 'xx', type: 'A', address: '1.2.3.4', ttl: TTL },
|
||||
{ name: 'xx', type: 'A', address: '5.6.3.2', ttl: TTL },
|
||||
{ name: 'xx', type: 'A', address: '1.2.31.1', ttl: TTL },
|
||||
{ name: 'xx', type: 'CNAME', address: 'foobar.com', ttl: TTL },
|
||||
{ name: 'xx', type: 'MX', address: 'as.com', priority: 20, ttl: TTL },
|
||||
{ name: 'xx', type: 'MX', address: 'f.com', priority: 21, ttl: TTL },
|
||||
])
|
||||
})
|
||||
})
|
||||
expect(res).toEqual([
|
||||
{
|
||||
name: "akshay",
|
||||
type: "CNAME",
|
||||
address: "phenax.github.io",
|
||||
ttl: TTL,
|
||||
},
|
||||
{ name: "foobar", type: "CNAME", address: "v.io", ttl: TTL },
|
||||
{ name: "xx", type: "A", address: "1.2.3.4", ttl: TTL },
|
||||
{ name: "xx", type: "A", address: "5.6.3.2", ttl: TTL },
|
||||
{ name: "xx", type: "A", address: "1.2.31.1", ttl: TTL },
|
||||
{ name: "xx", type: "CNAME", address: "foobar.com", ttl: TTL },
|
||||
{
|
||||
name: "xx",
|
||||
type: "MX",
|
||||
address: "as.com",
|
||||
priority: 20,
|
||||
ttl: TTL,
|
||||
},
|
||||
{
|
||||
name: "xx",
|
||||
type: "MX",
|
||||
address: "f.com",
|
||||
priority: 21,
|
||||
ttl: TTL,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('registerDomains', () => {
|
||||
const addZone = jest.fn(async () => ({}))
|
||||
const removeZone = jest.fn(async () => ({}))
|
||||
const addRedir = jest.fn(async () => ({}))
|
||||
const removeRedir = jest.fn(async () => ({}))
|
||||
const addEmail = jest.fn(async () => ({}))
|
||||
const removeEmail = jest.fn(async () => ({}))
|
||||
describe("registerDomains", () => {
|
||||
const addZone = jest.fn(async () => ({}));
|
||||
const removeZone = jest.fn(async () => ({}));
|
||||
const addRedir = jest.fn(async () => ({}));
|
||||
const removeRedir = jest.fn(async () => ({}));
|
||||
const addEmail = jest.fn(async () => ({}));
|
||||
const removeEmail = jest.fn(async () => ({}));
|
||||
|
||||
const mockDS = ({ zones, redirections }) =>
|
||||
getDomainService({
|
||||
cpanel: getCpanel({
|
||||
zone: async () => zones,
|
||||
redir: async () => redirections,
|
||||
addZone,
|
||||
addEmail,
|
||||
addRedir,
|
||||
removeZone,
|
||||
removeRedir,
|
||||
removeEmail,
|
||||
}),
|
||||
})
|
||||
const mockDS = ({ zones, redirections }) =>
|
||||
getDomainService({
|
||||
cpanel: getCpanel({
|
||||
zone: async () => zones,
|
||||
redir: async () => redirections,
|
||||
addZone,
|
||||
addEmail,
|
||||
addRedir,
|
||||
removeZone,
|
||||
removeRedir,
|
||||
removeEmail,
|
||||
}),
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
addZone.mockClear()
|
||||
removeZone.mockClear()
|
||||
addRedir.mockClear()
|
||||
removeRedir.mockClear()
|
||||
addEmail.mockClear()
|
||||
removeEmail.mockClear()
|
||||
})
|
||||
beforeEach(() => {
|
||||
addZone.mockClear();
|
||||
removeZone.mockClear();
|
||||
addRedir.mockClear();
|
||||
removeRedir.mockClear();
|
||||
addEmail.mockClear();
|
||||
removeEmail.mockClear();
|
||||
});
|
||||
|
||||
it('should register the new set of hosts generated from domains list', async () => {
|
||||
const localHosts = [
|
||||
{ name: 'a', record: { CNAME: 'hello' } },
|
||||
{ name: 'b', record: { CNAME: 'xaa' } },
|
||||
]
|
||||
const remoteHosts = [
|
||||
{ line: 1, name: 'a', type: 'CNAME', address: 'hello' },
|
||||
{ line: 2, name: 'b', type: 'CNAME', address: 'goo' },
|
||||
{ line: 3, name: 'b', type: 'CNAME', address: 'xaa' },
|
||||
]
|
||||
const remoteRedirections = []
|
||||
it("should register the new set of hosts generated from domains list", async () => {
|
||||
const localHosts = [
|
||||
{ name: "a", record: { CNAME: "hello" } },
|
||||
{ name: "b", record: { CNAME: "xaa" } },
|
||||
];
|
||||
const remoteHosts = [
|
||||
{ line: 1, name: "a", type: "CNAME", address: "hello" },
|
||||
{ line: 2, name: "b", type: "CNAME", address: "goo" },
|
||||
{ line: 3, name: "b", type: "CNAME", address: "xaa" },
|
||||
];
|
||||
const remoteRedirections = [];
|
||||
|
||||
const domainService = mockDS({
|
||||
zones: remoteHosts,
|
||||
redirections: remoteRedirections,
|
||||
})
|
||||
await registerDomains({ getDomains: async () => localHosts, domainService })
|
||||
const domainService = mockDS({
|
||||
zones: remoteHosts,
|
||||
redirections: remoteRedirections,
|
||||
});
|
||||
await registerDomains({
|
||||
getDomains: async () => localHosts,
|
||||
domainService,
|
||||
});
|
||||
|
||||
expect(addZone).toHaveBeenCalledTimes(0)
|
||||
expect(removeZone).toHaveBeenCalledTimes(1)
|
||||
expect(addRedir).toHaveBeenCalledTimes(0)
|
||||
expect(removeRedir).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
expect(addZone).toHaveBeenCalledTimes(0);
|
||||
expect(removeZone).toHaveBeenCalledTimes(1);
|
||||
expect(addRedir).toHaveBeenCalledTimes(0);
|
||||
expect(removeRedir).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should add the new set hosts', async () => {
|
||||
const localHosts = [
|
||||
{ name: 'a', record: { CNAME: 'boo' } },
|
||||
{
|
||||
name: 'b',
|
||||
record: { A: ['1.1.1.1', '1.1.1.2'], MX: 'somemx', TXT: 'some txt' },
|
||||
},
|
||||
{ name: 'c', record: { URL: 'https://google.com' } },
|
||||
{ name: 'd', record: { CNAME: 'foobar' } },
|
||||
{ name: 'e', record: { A: ['2.2.2.2'], TXT: ['some', 'extra', 'txt'] } },
|
||||
]
|
||||
const remoteHosts = [
|
||||
{ line: 1, name: 'a', type: 'CNAME', address: 'boo' },
|
||||
{ line: 2, name: 'b', type: 'MX', address: 'othermx' },
|
||||
{ line: 3, name: 'd', type: 'CNAME', address: 'foobaz' },
|
||||
]
|
||||
const remoteRedirections = [
|
||||
{ domain: `b.${DOMAIN_DOMAIN}`, destination: 'x' },
|
||||
{ domain: `a.${DOMAIN_DOMAIN}`, destination: 'y' },
|
||||
]
|
||||
it("should add the new set hosts", async () => {
|
||||
const localHosts = [
|
||||
{ name: "a", record: { CNAME: "boo" } },
|
||||
{
|
||||
name: "b",
|
||||
record: {
|
||||
A: ["1.1.1.1", "1.1.1.2"],
|
||||
MX: "somemx",
|
||||
TXT: "some txt",
|
||||
},
|
||||
},
|
||||
{ name: "c", record: { URL: "https://google.com" } },
|
||||
{ name: "d", record: { CNAME: "foobar" } },
|
||||
{
|
||||
name: "e",
|
||||
record: { A: ["2.2.2.2"], TXT: ["some", "extra", "txt"] },
|
||||
},
|
||||
];
|
||||
const remoteHosts = [
|
||||
{ line: 1, name: "a", type: "CNAME", address: "boo" },
|
||||
{ line: 2, name: "b", type: "MX", address: "othermx" },
|
||||
{ line: 3, name: "d", type: "CNAME", address: "foobaz" },
|
||||
];
|
||||
const remoteRedirections = [
|
||||
{ domain: `b.${DOMAIN_DOMAIN}`, destination: "x" },
|
||||
{ domain: `a.${DOMAIN_DOMAIN}`, destination: "y" },
|
||||
];
|
||||
|
||||
const domainService = mockDS({
|
||||
zones: remoteHosts,
|
||||
redirections: remoteRedirections,
|
||||
})
|
||||
await registerDomains({ getDomains: async () => localHosts, domainService })
|
||||
const domainService = mockDS({
|
||||
zones: remoteHosts,
|
||||
redirections: remoteRedirections,
|
||||
});
|
||||
await registerDomains({
|
||||
getDomains: async () => localHosts,
|
||||
domainService,
|
||||
});
|
||||
|
||||
expect(addZone).toHaveBeenCalledTimes(8)
|
||||
expect(addZone.mock.calls).toEqual([
|
||||
[{ name: 'b', type: 'A', address: '1.1.1.1', line: undefined }],
|
||||
[{ name: 'b', type: 'A', address: '1.1.1.2', line: undefined }],
|
||||
[
|
||||
{
|
||||
name: 'b',
|
||||
type: 'TXT',
|
||||
address: 'some txt',
|
||||
txtdata: 'some txt',
|
||||
line: undefined,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'd',
|
||||
type: 'CNAME',
|
||||
cname: 'foobar',
|
||||
address: 'foobar',
|
||||
line: undefined,
|
||||
},
|
||||
],
|
||||
[{ name: 'e', type: 'A', address: '2.2.2.2', line: undefined }],
|
||||
[
|
||||
{
|
||||
name: 'e',
|
||||
type: 'TXT',
|
||||
address: 'some',
|
||||
txtdata: 'some',
|
||||
line: undefined,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'e',
|
||||
type: 'TXT',
|
||||
address: 'extra',
|
||||
txtdata: 'extra',
|
||||
line: undefined,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'e',
|
||||
type: 'TXT',
|
||||
address: 'txt',
|
||||
txtdata: 'txt',
|
||||
line: undefined,
|
||||
},
|
||||
],
|
||||
])
|
||||
expect(addZone).toHaveBeenCalledTimes(8);
|
||||
expect(addZone.mock.calls).toEqual([
|
||||
[{ name: "b", type: "A", address: "1.1.1.1", line: undefined }],
|
||||
[{ name: "b", type: "A", address: "1.1.1.2", line: undefined }],
|
||||
[
|
||||
{
|
||||
name: "b",
|
||||
type: "TXT",
|
||||
address: "some txt",
|
||||
txtdata: "some txt",
|
||||
line: undefined,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: "d",
|
||||
type: "CNAME",
|
||||
cname: "foobar",
|
||||
address: "foobar",
|
||||
line: undefined,
|
||||
},
|
||||
],
|
||||
[{ name: "e", type: "A", address: "2.2.2.2", line: undefined }],
|
||||
[
|
||||
{
|
||||
name: "e",
|
||||
type: "TXT",
|
||||
address: "some",
|
||||
txtdata: "some",
|
||||
line: undefined,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: "e",
|
||||
type: "TXT",
|
||||
address: "extra",
|
||||
txtdata: "extra",
|
||||
line: undefined,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: "e",
|
||||
type: "TXT",
|
||||
address: "txt",
|
||||
txtdata: "txt",
|
||||
line: undefined,
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
expect(removeZone).toHaveBeenCalledTimes(1)
|
||||
expect(removeZone.mock.calls).toEqual([[{ line: 3 }]])
|
||||
expect(removeZone).toHaveBeenCalledTimes(1);
|
||||
expect(removeZone.mock.calls).toEqual([[{ line: 3 }]]);
|
||||
|
||||
expect(addRedir).toHaveBeenCalledTimes(1)
|
||||
expect(addRedir.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
domain: 'c.booboo.xyz',
|
||||
redirect: 'https://google.com',
|
||||
redirect_wildcard: 1,
|
||||
redirect_www: 1,
|
||||
type: 'permanent',
|
||||
},
|
||||
],
|
||||
])
|
||||
expect(addRedir).toHaveBeenCalledTimes(1);
|
||||
expect(addRedir.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
domain: "c.booboo.xyz",
|
||||
redirect: "https://google.com",
|
||||
redirect_wildcard: 1,
|
||||
redirect_www: 1,
|
||||
type: "permanent",
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
expect(addEmail).toHaveBeenCalledTimes(1)
|
||||
expect(addEmail.mock.calls).toEqual([
|
||||
[{ domain: 'b.is-a.dev', exchanger: 'somemx', priority: 20 }],
|
||||
])
|
||||
})
|
||||
})
|
||||
expect(addEmail).toHaveBeenCalledTimes(1);
|
||||
expect(addEmail.mock.calls).toEqual([
|
||||
[{ domain: "b.is-a.dev", exchanger: "somemx", priority: 20 }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
+154
-120
@@ -1,129 +1,163 @@
|
||||
const { validateDomainData, isValidDomain } = require('../utils/validations');
|
||||
const INVALID_NAMES = require('../utils/invalid-domains.json');
|
||||
const { validateDomainData, isValidDomain } = require("../utils/validations");
|
||||
const INVALID_NAMES = require("../utils/invalid-domains.json");
|
||||
|
||||
const defaultDomain = {
|
||||
name: 'aaa',
|
||||
record: {
|
||||
A: ['121.121.121.121']
|
||||
},
|
||||
owner: {
|
||||
username: 'betsy',
|
||||
email: 'betsyfuckyoassup@foobar.com',
|
||||
},
|
||||
name: "aaa",
|
||||
record: {
|
||||
A: ["121.121.121.121"],
|
||||
},
|
||||
owner: {
|
||||
username: "betsy",
|
||||
email: "betsyfuckyoassup@foobar.com",
|
||||
},
|
||||
};
|
||||
|
||||
const getstroflen = len => Array(len).fill('a').join('');
|
||||
const getstroflen = (len) => Array(len).fill("a").join("");
|
||||
|
||||
describe('isValidMX', () => {
|
||||
it('should be valid mx record', () => {
|
||||
const cases = [
|
||||
{ mx: 'foobar.com', result: true },
|
||||
{ mx: 'as.as', result: true },
|
||||
{ mx: 'ASPMX.L.GOOGLE.COM', result: true },
|
||||
{ mx: 'ALT4.ASPMX.L.GOOGLE.COM', result: true },
|
||||
{ mx: 'hello', result: false },
|
||||
{ mx: 'helalsds-asd5sjdsd.com', result: true },
|
||||
{ mx: 'helalsds?asd5sjdsd.com', result: false },
|
||||
{ mx: 'helalsds_asd5sjdsd.com', result: false },
|
||||
describe("isValidMX", () => {
|
||||
it("should be valid mx record", () => {
|
||||
const cases = [
|
||||
{ mx: "foobar.com", result: true },
|
||||
{ mx: "as.as", result: true },
|
||||
{ mx: "ASPMX.L.GOOGLE.COM", result: true },
|
||||
{ mx: "ALT4.ASPMX.L.GOOGLE.COM", result: true },
|
||||
{ mx: "hello", result: false },
|
||||
{ mx: "helalsds-asd5sjdsd.com", result: true },
|
||||
{ mx: "helalsds?asd5sjdsd.com", result: false },
|
||||
{ mx: "helalsds_asd5sjdsd.com", result: false },
|
||||
];
|
||||
|
||||
cases.forEach(({ mx, result }) => {
|
||||
expect(isValidDomain(mx)).toBe(result);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("validateDomainData", () => {
|
||||
const invalidCases = [
|
||||
{},
|
||||
{ name: "helo" },
|
||||
{ name: "wwow", record: { A: ["12312"] } },
|
||||
...[
|
||||
"",
|
||||
" ",
|
||||
undefined,
|
||||
"hlo wld",
|
||||
"g32++13",
|
||||
"ajsdD_123yq",
|
||||
"khsda%",
|
||||
"122*dsd",
|
||||
getstroflen(101),
|
||||
].map((name) => ({
|
||||
...defaultDomain,
|
||||
name,
|
||||
})),
|
||||
{ ...defaultDomain, record: { CNAME: "sd", A: ["121,3213"] } },
|
||||
{ ...defaultDomain, record: { A: ["121", "12"], FOOBAR: ["sd"] } },
|
||||
{ ...defaultDomain, record: { A: [] } },
|
||||
{ ...defaultDomain, owner: {} },
|
||||
{ ...defaultDomain, owner: { username: "hwelo" } },
|
||||
{ ...defaultDomain, owner: { email: "hwelo" } },
|
||||
{ ...defaultDomain, record: { CNAME: "http://foobar.com" } },
|
||||
{ ...defaultDomain, record: { CNAME: "https://foobar.com" } },
|
||||
{ ...defaultDomain, record: { URL: "foobar.com" } },
|
||||
{
|
||||
...defaultDomain,
|
||||
record: { CNAME: "foobar.com", A: ["11.22.22.33"] },
|
||||
},
|
||||
{
|
||||
...defaultDomain,
|
||||
record: { CNAME: "foobar.com", MX: ["ALT4.ASPMX.L.GOOGLE.COM"] },
|
||||
},
|
||||
...INVALID_NAMES.map((name) => ({ ...defaultDomain, name })).slice(
|
||||
0,
|
||||
1,
|
||||
),
|
||||
{ ...defaultDomain, name: "ww2.baa" },
|
||||
{ ...defaultDomain, name: "help.baa" },
|
||||
{ ...defaultDomain, name: "_github-pages-challenge-is-a-dev" },
|
||||
{ ...defaultDomain, name: "_github-challenge-is-a-dev" },
|
||||
{ ...defaultDomain, record: { AAAA: [] } },
|
||||
{ ...defaultDomain, record: { AAAA: ["182.22.222.22", "::1"] } },
|
||||
{ ...defaultDomain, record: { AAAA: "182.22.222.22" } },
|
||||
{ ...defaultDomain, record: { A: "::1" } },
|
||||
{ ...defaultDomain, name: "_discord" },
|
||||
{ ...defaultDomain, name: "_gitlab-pages-verification-code" },
|
||||
{ ...defaultDomain, name: "_acme-challenge" },
|
||||
{ ...defaultDomain, name: "_dmarc" },
|
||||
{ ...defaultDomain, name: "_gh-is-a-dev" },
|
||||
{ ...defaultDomain, name: "_domainkey" },
|
||||
];
|
||||
|
||||
cases.forEach(({ mx, result }) => {
|
||||
expect(isValidDomain(mx)).toBe(result);
|
||||
const validCases = [
|
||||
defaultDomain,
|
||||
...[
|
||||
"hello",
|
||||
"hello-world",
|
||||
"11111111111",
|
||||
"--wow--",
|
||||
"wow--",
|
||||
"--wow",
|
||||
].map((name) => ({
|
||||
...defaultDomain,
|
||||
name,
|
||||
})),
|
||||
{
|
||||
...defaultDomain,
|
||||
description: getstroflen(99),
|
||||
},
|
||||
{ ...defaultDomain, record: { CNAME: "aa.sd" } },
|
||||
{ ...defaultDomain, record: { URL: "https://foobar.com" } },
|
||||
{ ...defaultDomain, record: { URL: "http://foobar.com/foobar/" } },
|
||||
{ ...defaultDomain, record: { MX: ["ALT4.ASPMX.L.GOOGLE.COM"] } },
|
||||
{ ...defaultDomain, record: { TXT: "foobar wow nice!!!" } },
|
||||
{
|
||||
...defaultDomain,
|
||||
record: { A: ["1.1.1.1"], MX: ["mx1.example.com"] },
|
||||
},
|
||||
{ ...defaultDomain, name: "gogo.foo.bar" },
|
||||
{ ...defaultDomain, name: "ww9.baa" },
|
||||
{ ...defaultDomain, name: "_github-pages-challenge-phenax.akshay" },
|
||||
{ ...defaultDomain, name: "_github-pages-challenge-hello01-ga" },
|
||||
{ ...defaultDomain, name: "_github-pages-challenge-hello01_ga" },
|
||||
{ ...defaultDomain, name: "_github-challenge-phenax.akshay" },
|
||||
{ ...defaultDomain, name: "_github-challenge-hello01-ga" },
|
||||
{ ...defaultDomain, name: "_github-challenge-hello01_ga" },
|
||||
{
|
||||
...defaultDomain,
|
||||
record: { TXT: ["foobar wow nice!!!", "more text"] },
|
||||
},
|
||||
{
|
||||
...defaultDomain,
|
||||
record: { AAAA: ["::1", "2001:db8:3333:4444:5555:6666:7777:8888"] },
|
||||
},
|
||||
{ ...defaultDomain, record: { A: ["122.222.222.222"] } },
|
||||
{ ...defaultDomain, name: "_discord.subdomain" },
|
||||
{ ...defaultDomain, name: "_gitlab-pages-verification-code.subdomain" },
|
||||
{ ...defaultDomain, name: "_acme-challenge.subdomain" },
|
||||
{ ...defaultDomain, name: "_dmarc.subdomain" },
|
||||
{ ...defaultDomain, name: "_gh-phenax.akshay" },
|
||||
{ ...defaultDomain, name: "_gh-hello01-ga" },
|
||||
{ ...defaultDomain, name: "_gh-hello01_ga" },
|
||||
{ ...defaultDomain, name: "_domainkey.subdomain" },
|
||||
{ ...defaultDomain, name: "mx._domainkey.subdomain" },
|
||||
{ ...defaultDomain, name: "a.b" },
|
||||
];
|
||||
|
||||
it("should return false for invalid data", () => {
|
||||
invalidCases.forEach((data) => {
|
||||
const { valid, errors } = validateDomainData(data);
|
||||
expect(valid).toBe(false);
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it("should return true if the name is valid", () => {
|
||||
validCases.forEach((data) => {
|
||||
const { valid, errors } = validateDomainData(data);
|
||||
if (!valid) console.log(JSON.stringify(errors, null, 2));
|
||||
expect(valid).toBe(true);
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateDomainData', () => {
|
||||
const invalidCases = [
|
||||
{},
|
||||
{ name: 'helo' },
|
||||
{ name: 'wwow', record: { A: ['12312'] } },
|
||||
...['', ' ', undefined, 'hlo wld', 'g32++13', 'ajsdD_123yq', 'khsda%', '122*dsd', getstroflen(101)].map(name => ({
|
||||
...defaultDomain,
|
||||
name,
|
||||
})),
|
||||
{ ...defaultDomain, record: { CNAME: 'sd', A: ['121,3213'] } },
|
||||
{ ...defaultDomain, record: { A: ['121', '12'], FOOBAR: ['sd'] } },
|
||||
{ ...defaultDomain, record: { A: [] } },
|
||||
{ ...defaultDomain, owner: {}, },
|
||||
{ ...defaultDomain, owner: { username: 'hwelo', }, },
|
||||
{ ...defaultDomain, owner: { email: 'hwelo' }, },
|
||||
{ ...defaultDomain, record: { CNAME: 'http://foobar.com' } },
|
||||
{ ...defaultDomain, record: { CNAME: 'https://foobar.com' } },
|
||||
{ ...defaultDomain, record: { URL: 'foobar.com' } },
|
||||
{ ...defaultDomain, record: { CNAME: 'foobar.com', A: ['11.22.22.33'] } },
|
||||
{ ...defaultDomain, record: { CNAME: 'foobar.com', MX: ['ALT4.ASPMX.L.GOOGLE.COM'] } },
|
||||
...INVALID_NAMES.map(name => ({ ...defaultDomain, name })).slice(0, 1),
|
||||
{ ...defaultDomain, name: 'a.b' },
|
||||
{ ...defaultDomain, name: 'ww2.baa' },
|
||||
{ ...defaultDomain, name: 'help.baa' },
|
||||
{ ...defaultDomain, name: '_github-pages-challenge-is-a-dev' },
|
||||
{ ...defaultDomain, name: '_github-challenge-is-a-dev' },
|
||||
{ ...defaultDomain, record: { AAAA: [] } },
|
||||
{ ...defaultDomain, record: { AAAA: ['182.22.222.22', '::1'] } },
|
||||
{ ...defaultDomain, record: { AAAA: '182.22.222.22' } },
|
||||
{ ...defaultDomain, record: { A: '::1' } },
|
||||
{ ...defaultDomain, name: '_discord' },
|
||||
{ ...defaultDomain, name: '_gitlab-pages-verification-code' },
|
||||
{ ...defaultDomain, name: '_acme-challenge' },
|
||||
{ ...defaultDomain, name: '_dmarc' },
|
||||
{ ...defaultDomain, name: '_gh-is-a-dev' },
|
||||
{ ...defaultDomain, name: '_domainkey' },
|
||||
];
|
||||
|
||||
const validCases = [
|
||||
defaultDomain,
|
||||
...['hello', 'hello-world', '11111111111', '--wow--', 'wow--', '--wow'].map(name => ({
|
||||
...defaultDomain,
|
||||
name,
|
||||
})),
|
||||
{
|
||||
...defaultDomain,
|
||||
description: getstroflen(99),
|
||||
},
|
||||
{ ...defaultDomain, record: { CNAME: 'aa.sd' } },
|
||||
{ ...defaultDomain, record: { URL: 'https://foobar.com' } },
|
||||
{ ...defaultDomain, record: { URL: 'http://foobar.com/foobar/' } },
|
||||
{ ...defaultDomain, record: { MX: ['ALT4.ASPMX.L.GOOGLE.COM'] } },
|
||||
{ ...defaultDomain, record: { TXT: 'foobar wow nice!!!' } },
|
||||
{ ...defaultDomain, record: { A: ['1.1.1.1'], MX: ['mx1.example.com'] } },
|
||||
{ ...defaultDomain, name: 'gogo.foo.bar' },
|
||||
{ ...defaultDomain, name: 'ww9.baa' },
|
||||
{ ...defaultDomain, name: '_github-pages-challenge-phenax.akshay' },
|
||||
{ ...defaultDomain, name: '_github-pages-challenge-hello01-ga' },
|
||||
{ ...defaultDomain, name: '_github-pages-challenge-hello01_ga' },
|
||||
{ ...defaultDomain, name: '_github-challenge-phenax.akshay' },
|
||||
{ ...defaultDomain, name: '_github-challenge-hello01-ga' },
|
||||
{ ...defaultDomain, name: '_github-challenge-hello01_ga' },
|
||||
{ ...defaultDomain, record: { TXT: ['foobar wow nice!!!', 'more text'] } },
|
||||
{ ...defaultDomain, record: { AAAA: ['::1', '2001:db8:3333:4444:5555:6666:7777:8888'] } },
|
||||
{ ...defaultDomain, record: { A: ['122.222.222.222'] } },
|
||||
{ ...defaultDomain, name: '_discord.subdomain' },
|
||||
{ ...defaultDomain, name: '_gitlab-pages-verification-code.subdomain' },
|
||||
{ ...defaultDomain, name: '_acme-challenge.subdomain' },
|
||||
{ ...defaultDomain, name: '_dmarc.subdomain' },
|
||||
{ ...defaultDomain, name: '_gh-phenax.akshay' },
|
||||
{ ...defaultDomain, name: '_gh-hello01-ga' },
|
||||
{ ...defaultDomain, name: '_gh-hello01_ga' },
|
||||
{ ...defaultDomain, name: '_domainkey.subdomain' },
|
||||
{ ...defaultDomain, name: 'mx._domainkey.subdomain' },
|
||||
];
|
||||
|
||||
it('should return false for invalid data', () => {
|
||||
invalidCases.forEach(data => {
|
||||
const { valid, errors } = validateDomainData(data);
|
||||
expect(valid).toBe(false);
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return true if the name is valid', () => {
|
||||
validCases.forEach(data => {
|
||||
const { valid, errors } = validateDomainData(data);
|
||||
if (!valid) console.log(JSON.stringify(errors, null, 2));
|
||||
expect(valid).toBe(true);
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+21
-21
@@ -1,30 +1,30 @@
|
||||
const path = require('path');
|
||||
const path = require("path");
|
||||
|
||||
const { NODE_ENV: ENV = 'test' } = process.env;
|
||||
const { NODE_ENV: ENV = "test" } = process.env;
|
||||
|
||||
const {
|
||||
DOMAIN_USER,
|
||||
DOMAIN_API_KEY,
|
||||
DOMAIN_DOMAIN,
|
||||
DOMAIN_API_HOST,
|
||||
DOMAIN_API_PORT,
|
||||
DOMAIN_HOST_IP,
|
||||
DOMAIN_USER,
|
||||
DOMAIN_API_KEY,
|
||||
DOMAIN_DOMAIN,
|
||||
DOMAIN_API_HOST,
|
||||
DOMAIN_API_PORT,
|
||||
DOMAIN_HOST_IP,
|
||||
} = process.env;
|
||||
|
||||
const IS_TEST = ENV === 'test';
|
||||
const IS_TEST = ENV === "test";
|
||||
|
||||
const DOMAINS_PATH = path.resolve('domains');
|
||||
const DOMAINS_PATH = path.resolve("domains");
|
||||
|
||||
module.exports = {
|
||||
ENV,
|
||||
IS_TEST,
|
||||
VALID_RECORD_TYPES: ['CNAME', 'A', 'URL', 'MX', 'TXT', 'AAAA'],
|
||||
DOMAIN_DOMAIN: DOMAIN_DOMAIN || 'booboo.xyz',
|
||||
DOMAIN_USER: IS_TEST ? 'testuser' : DOMAIN_USER,
|
||||
DOMAIN_API_KEY: IS_TEST ? 'testkey' : DOMAIN_API_KEY,
|
||||
DOMAIN_API_HOST: IS_TEST ? 'example.com' : DOMAIN_API_HOST,
|
||||
DOMAIN_API_PORT: IS_TEST ? 6969 : DOMAIN_API_PORT,
|
||||
DOMAIN_HOST_IP,
|
||||
DOMAINS_PATH,
|
||||
TTL: 5 * 60 * 60,
|
||||
ENV,
|
||||
IS_TEST,
|
||||
VALID_RECORD_TYPES: ["CNAME", "A", "URL", "MX", "TXT", "AAAA"],
|
||||
DOMAIN_DOMAIN: DOMAIN_DOMAIN || "booboo.xyz",
|
||||
DOMAIN_USER: IS_TEST ? "testuser" : DOMAIN_USER,
|
||||
DOMAIN_API_KEY: IS_TEST ? "testkey" : DOMAIN_API_KEY,
|
||||
DOMAIN_API_HOST: IS_TEST ? "example.com" : DOMAIN_API_HOST,
|
||||
DOMAIN_API_PORT: IS_TEST ? 6969 : DOMAIN_API_PORT,
|
||||
DOMAIN_HOST_IP,
|
||||
DOMAINS_PATH,
|
||||
TTL: 5 * 60 * 60,
|
||||
};
|
||||
|
||||
+161
-122
@@ -1,167 +1,206 @@
|
||||
const R = require('ramda');
|
||||
const { cpanel } = require('./lib/cpanel');
|
||||
const { DOMAIN_DOMAIN, VALID_RECORD_TYPES } = require('./constants');
|
||||
const { then, log, print, lazyTask, batchLazyTasks } = require('./helpers');
|
||||
const R = require("ramda");
|
||||
const { cpanel } = require("./lib/cpanel");
|
||||
const { DOMAIN_DOMAIN, VALID_RECORD_TYPES } = require("./constants");
|
||||
const { then, log, print, lazyTask, batchLazyTasks } = require("./helpers");
|
||||
|
||||
const BATCH_SIZE = 1;
|
||||
|
||||
const recordToRedirection = ({ name, address }) => ({
|
||||
domain: name === '@' ? DOMAIN_DOMAIN : `${name}.${DOMAIN_DOMAIN}`,
|
||||
redirect: address,
|
||||
type: 'permanent',
|
||||
redirect_wildcard: 1,
|
||||
redirect_www: 1,
|
||||
domain: name === "@" ? DOMAIN_DOMAIN : `${name}.${DOMAIN_DOMAIN}`,
|
||||
redirect: address,
|
||||
type: "permanent",
|
||||
redirect_wildcard: 1,
|
||||
redirect_www: 1,
|
||||
});
|
||||
const recordToZone = ({ name, type, address, id, priority }) => ({
|
||||
line: id,
|
||||
name: name === '@' ? `${DOMAIN_DOMAIN}.` : name,
|
||||
type,
|
||||
address,
|
||||
...(type === 'MX' ? { priority } : {}),
|
||||
...(type === 'CNAME' ? { cname: address } : {}),
|
||||
...(type === 'TXT' ? { txtdata: address } : {}),
|
||||
line: id,
|
||||
name: name === "@" ? `${DOMAIN_DOMAIN}.` : name,
|
||||
type,
|
||||
address,
|
||||
...(type === "MX" ? { priority } : {}),
|
||||
...(type === "CNAME" ? { cname: address } : {}),
|
||||
...(type === "TXT" ? { txtdata: address } : {}),
|
||||
});
|
||||
|
||||
const cleanName = name =>
|
||||
[DOMAIN_DOMAIN, `${DOMAIN_DOMAIN}.`].includes(name) ? '@' : `${name}`.replace(new RegExp(`\\.${DOMAIN_DOMAIN}\\.?$`), '').toLowerCase();
|
||||
const cleanName = (name) =>
|
||||
[DOMAIN_DOMAIN, `${DOMAIN_DOMAIN}.`].includes(name)
|
||||
? "@"
|
||||
: `${name}`
|
||||
.replace(new RegExp(`\\.${DOMAIN_DOMAIN}\\.?$`), "")
|
||||
.toLowerCase();
|
||||
|
||||
const zoneToRecord = ({
|
||||
name,
|
||||
type,
|
||||
cname,
|
||||
address,
|
||||
priority,
|
||||
preference,
|
||||
exchange,
|
||||
record,
|
||||
line: id
|
||||
}) =>
|
||||
({
|
||||
name,
|
||||
type,
|
||||
cname,
|
||||
address,
|
||||
priority,
|
||||
preference,
|
||||
exchange,
|
||||
record,
|
||||
line: id,
|
||||
}) => ({
|
||||
id,
|
||||
name: cleanName(name),
|
||||
type: `${type}`,
|
||||
address: `${exchange || cname || address || record}`.replace(/\.$/g, '').toLowerCase(),
|
||||
address: `${exchange || cname || address || record}`
|
||||
.replace(/\.$/g, "")
|
||||
.toLowerCase(),
|
||||
priority: priority || preference,
|
||||
});
|
||||
});
|
||||
const redirectionToRecord = ({ domain, destination }) => ({
|
||||
id: domain,
|
||||
name: cleanName(domain),
|
||||
type: 'URL',
|
||||
address: `${destination}`.replace(/\/$/g, ''),
|
||||
id: domain,
|
||||
name: cleanName(domain),
|
||||
type: "URL",
|
||||
address: `${destination}`.replace(/\/$/g, ""),
|
||||
});
|
||||
|
||||
const recordToEmailMx = ({ name, address, priority }) => ({
|
||||
domain: `${name}.is-a.dev`,
|
||||
exchanger: address,
|
||||
priority,
|
||||
})
|
||||
domain: `${name}.is-a.dev`,
|
||||
exchanger: address,
|
||||
priority,
|
||||
});
|
||||
|
||||
const getHostKey = host =>
|
||||
`${host.name.toLowerCase()}##${host.type.toLowerCase()}##${host.address.toLowerCase()}`;
|
||||
const getHostKey = (host) =>
|
||||
`${host.name.toLowerCase()}##${host.type.toLowerCase()}##${host.address.toLowerCase()}`;
|
||||
|
||||
const isReserved = (domain) =>
|
||||
domain.name.startsWith('*') || !VALID_RECORD_TYPES.includes(domain.type)
|
||||
domain.name.startsWith("*") || !VALID_RECORD_TYPES.includes(domain.type);
|
||||
|
||||
const diffRecords = (oldRecords, newRecords) => {
|
||||
const isMatchingRecord = (a, b) => getHostKey(a) === getHostKey(b);
|
||||
const isMatchingRecord = (a, b) => getHostKey(a) === getHostKey(b);
|
||||
|
||||
const remove = R.differenceWith(isMatchingRecord, oldRecords, newRecords);
|
||||
const add = R.differenceWith(isMatchingRecord, newRecords, oldRecords);
|
||||
const remove = R.differenceWith(isMatchingRecord, oldRecords, newRecords);
|
||||
const add = R.differenceWith(isMatchingRecord, newRecords, oldRecords);
|
||||
|
||||
return { add, remove };
|
||||
return { add, remove };
|
||||
};
|
||||
|
||||
const executeBatch = (batches) => batches.reduce((promise, batch, index) => {
|
||||
return promise.then(async () => {
|
||||
log('>>> Running batch number:', index + 1, `(size: ${batch.length})`);
|
||||
const executeBatch = (batches) =>
|
||||
batches.reduce((promise, batch, index) => {
|
||||
return promise.then(async () => {
|
||||
log(
|
||||
">>> Running batch number:",
|
||||
index + 1,
|
||||
`(size: ${batch.length})`,
|
||||
);
|
||||
|
||||
const values = await Promise.all(batch.map(fn => fn().catch(e => console.error(e))));
|
||||
const values = await Promise.all(
|
||||
batch.map((fn) => fn().catch((e) => console.error(e))),
|
||||
);
|
||||
|
||||
const results = values.map(data => R.pathOr({ result: data }, ['cpanelresult', 'data', 0], data));
|
||||
const failed = results.filter(x => (x.result || {}).status != 1);
|
||||
const results = values.map((data) =>
|
||||
R.pathOr({ result: data }, ["cpanelresult", "data", 0], data),
|
||||
);
|
||||
const failed = results.filter((x) => (x.result || {}).status != 1);
|
||||
|
||||
log(`${values.length - failed.length}/${values.length}`);
|
||||
failed.length && log(JSON.stringify(failed, null, 2));
|
||||
log(`${values.length - failed.length}/${values.length}`);
|
||||
failed.length && log(JSON.stringify(failed, null, 2));
|
||||
|
||||
return null;
|
||||
});
|
||||
}, Promise.resolve());
|
||||
return null;
|
||||
});
|
||||
}, Promise.resolve());
|
||||
|
||||
const getDomainService = ({ cpanel }) => {
|
||||
const fetchZoneRecords = R.compose(
|
||||
then(R.reject(isReserved)),
|
||||
then(R.map(zoneToRecord)),
|
||||
cpanel.zone.fetch
|
||||
);
|
||||
const fetchRedirections = R.compose(
|
||||
then(R.map(redirectionToRecord)),
|
||||
cpanel.redirection.fetch
|
||||
);
|
||||
const fetchZoneRecords = R.compose(
|
||||
then(R.reject(isReserved)),
|
||||
then(R.map(zoneToRecord)),
|
||||
cpanel.zone.fetch,
|
||||
);
|
||||
const fetchRedirections = R.compose(
|
||||
then(R.map(redirectionToRecord)),
|
||||
cpanel.redirection.fetch,
|
||||
);
|
||||
|
||||
const addZoneRecord = lazyTask(R.compose(
|
||||
R.ifElse(R.propEq('type', 'MX'),
|
||||
R.compose(cpanel.email.add, recordToEmailMx),
|
||||
cpanel.zone.add
|
||||
),
|
||||
recordToZone,
|
||||
print(r => `Adding zone for ${r.name}: (${r.type} ${r.address})...`),
|
||||
));
|
||||
const removeZoneRecord = lazyTask(R.compose(
|
||||
R.ifElse(R.propEq('type', 'MX'),
|
||||
R.compose(cpanel.email.remove, recordToEmailMx),
|
||||
R.compose(cpanel.zone.remove, R.pick(['line']))
|
||||
),
|
||||
recordToZone,
|
||||
print(r => `Deleting zone for ${r.name}: (${r.type} ${r.address})...`),
|
||||
));
|
||||
const addRedirection = lazyTask(R.compose(
|
||||
cpanel.redirection.add,
|
||||
recordToRedirection,
|
||||
print(({ name }) => `Adding redirection for ${name}`),
|
||||
));
|
||||
const removeRedirection = lazyTask(R.compose(
|
||||
cpanel.redirection.remove,
|
||||
R.pick(['domain']),
|
||||
recordToRedirection,
|
||||
print(({ name }) => `Deleting redirection for ${name}`),
|
||||
));
|
||||
const addZoneRecord = lazyTask(
|
||||
R.compose(
|
||||
R.ifElse(
|
||||
R.propEq("type", "MX"),
|
||||
R.compose(cpanel.email.add, recordToEmailMx),
|
||||
cpanel.zone.add,
|
||||
),
|
||||
recordToZone,
|
||||
print(
|
||||
(r) => `Adding zone for ${r.name}: (${r.type} ${r.address})...`,
|
||||
),
|
||||
),
|
||||
);
|
||||
const removeZoneRecord = lazyTask(
|
||||
R.compose(
|
||||
R.ifElse(
|
||||
R.propEq("type", "MX"),
|
||||
R.compose(cpanel.email.remove, recordToEmailMx),
|
||||
R.compose(cpanel.zone.remove, R.pick(["line"])),
|
||||
),
|
||||
recordToZone,
|
||||
print(
|
||||
(r) =>
|
||||
`Deleting zone for ${r.name}: (${r.type} ${r.address})...`,
|
||||
),
|
||||
),
|
||||
);
|
||||
const addRedirection = lazyTask(
|
||||
R.compose(
|
||||
cpanel.redirection.add,
|
||||
recordToRedirection,
|
||||
print(({ name }) => `Adding redirection for ${name}`),
|
||||
),
|
||||
);
|
||||
const removeRedirection = lazyTask(
|
||||
R.compose(
|
||||
cpanel.redirection.remove,
|
||||
R.pick(["domain"]),
|
||||
recordToRedirection,
|
||||
print(({ name }) => `Deleting redirection for ${name}`),
|
||||
),
|
||||
);
|
||||
|
||||
const getHosts = () =>
|
||||
Promise.all([fetchZoneRecords(), fetchRedirections()]).then(R.flatten);
|
||||
const getHosts = () =>
|
||||
Promise.all([fetchZoneRecords(), fetchRedirections()]).then(R.flatten);
|
||||
|
||||
const addRecords = R.compose(
|
||||
batchLazyTasks(BATCH_SIZE),
|
||||
R.filter(Boolean),
|
||||
R.map(R.cond([
|
||||
[R.propEq('type', 'URL'), addRedirection],
|
||||
[R.T, addZoneRecord],
|
||||
])),
|
||||
);
|
||||
const removeRecords = R.compose(
|
||||
batchLazyTasks(BATCH_SIZE),
|
||||
R.map(R.cond([ [ R.propEq('type', 'URL'), removeRedirection ], [ R.T, removeZoneRecord ] ])),
|
||||
R.sort((a, b) => b.id - a.id)
|
||||
);
|
||||
const addRecords = R.compose(
|
||||
batchLazyTasks(BATCH_SIZE),
|
||||
R.filter(Boolean),
|
||||
R.map(
|
||||
R.cond([
|
||||
[R.propEq("type", "URL"), addRedirection],
|
||||
[R.T, addZoneRecord],
|
||||
]),
|
||||
),
|
||||
);
|
||||
const removeRecords = R.compose(
|
||||
batchLazyTasks(BATCH_SIZE),
|
||||
R.map(
|
||||
R.cond([
|
||||
[R.propEq("type", "URL"), removeRedirection],
|
||||
[R.T, removeZoneRecord],
|
||||
]),
|
||||
),
|
||||
R.sort((a, b) => b.id - a.id),
|
||||
);
|
||||
|
||||
const updateHosts = async hosts => {
|
||||
const remoteHostList = await getHosts();
|
||||
const { add, remove } = diffRecords(remoteHostList, hosts);
|
||||
console.log(`Adding ${add.length}; Removing ${remove.length}`)
|
||||
const updateHosts = async (hosts) => {
|
||||
const remoteHostList = await getHosts();
|
||||
const { add, remove } = diffRecords(remoteHostList, hosts);
|
||||
console.log(`Adding ${add.length}; Removing ${remove.length}`);
|
||||
|
||||
await executeBatch([
|
||||
...removeRecords(remove),
|
||||
...addRecords(add),
|
||||
]);
|
||||
return { added: add.length, removed: remove.length };
|
||||
};
|
||||
await executeBatch([...removeRecords(remove), ...addRecords(add)]);
|
||||
return { added: add.length, removed: remove.length };
|
||||
};
|
||||
|
||||
return { getHosts, get: cpanel.zone.fetch, addZoneRecord, removeZoneRecord, updateHosts };
|
||||
return {
|
||||
getHosts,
|
||||
get: cpanel.zone.fetch,
|
||||
addZoneRecord,
|
||||
removeZoneRecord,
|
||||
updateHosts,
|
||||
};
|
||||
};
|
||||
|
||||
const domainService = getDomainService({ cpanel });
|
||||
|
||||
module.exports = {
|
||||
getDomainService,
|
||||
domainService,
|
||||
diffRecords,
|
||||
getDomainService,
|
||||
domainService,
|
||||
diffRecords,
|
||||
};
|
||||
|
||||
+20
-15
@@ -1,22 +1,27 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const R = require('ramda');
|
||||
const {DOMAINS_PATH} = require('./constants');
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const R = require("ramda");
|
||||
const { DOMAINS_PATH } = require("./constants");
|
||||
|
||||
const toDomain = str => path.join(DOMAINS_PATH, str);
|
||||
const toDomain = (str) => path.join(DOMAINS_PATH, str);
|
||||
|
||||
const parseDomain = name => str => {
|
||||
try {return JSON.parse(str);}
|
||||
catch (e) {throw new Error(`Error: Cant parse ${name} => ${str}`);}
|
||||
const parseDomain = (name) => (str) => {
|
||||
try {
|
||||
return JSON.parse(str);
|
||||
} catch (e) {
|
||||
throw new Error(`Error: Could not parse ${name} => ${str}`);
|
||||
}
|
||||
};
|
||||
|
||||
const toDomainData = name => R.compose(parseDomain(name), R.toString, fs.readFileSync, toDomain)(name);
|
||||
const toDomainData = (name) =>
|
||||
R.compose(parseDomain(name), R.toString, fs.readFileSync, toDomain)(name);
|
||||
|
||||
const getDomains = () =>
|
||||
fs.promises.readdir(DOMAINS_PATH, {})
|
||||
.then(R.map(name => ({
|
||||
...toDomainData(name),
|
||||
name: name.replace(/\.json$/, ''),
|
||||
})));
|
||||
fs.promises.readdir(DOMAINS_PATH, {}).then(
|
||||
R.map((name) => ({
|
||||
...toDomainData(name),
|
||||
name: name.replace(/\.json$/, ""),
|
||||
})),
|
||||
);
|
||||
|
||||
module.exports = {getDomains};
|
||||
module.exports = { getDomains };
|
||||
|
||||
+38
-36
@@ -1,52 +1,54 @@
|
||||
const R = require('ramda');
|
||||
const { IS_TEST } = require('./constants');
|
||||
const R = require("ramda");
|
||||
const { IS_TEST } = require("./constants");
|
||||
|
||||
const log = IS_TEST ? () => {} : console.log;
|
||||
const print = fn => x => log(fn(x)) || x;
|
||||
const print = (fn) => (x) => log(fn(x)) || x;
|
||||
|
||||
const between = (min, max) => num => num >= min && num <= max;
|
||||
const testRegex = regex => str => !!(str && str.match(regex));
|
||||
const between = (min, max) => (num) => num >= min && num <= max;
|
||||
const testRegex = (regex) => (str) => !!(str && str.match(regex));
|
||||
|
||||
const validate = pattern => data => R.compose(
|
||||
invalidPairs => invalidPairs.length
|
||||
? { errors: invalidPairs, valid: false }
|
||||
: { errors: [], valid: true },
|
||||
R.filter(([key, { fn }]) => fn ? !fn(data[key]) : false),
|
||||
R.toPairs,
|
||||
)(pattern);
|
||||
const validate = (pattern) => (data) =>
|
||||
R.compose(
|
||||
(invalidPairs) =>
|
||||
invalidPairs.length
|
||||
? { errors: invalidPairs, valid: false }
|
||||
: { errors: [], valid: true },
|
||||
R.filter(([key, { fn }]) => (fn ? !fn(data[key]) : false)),
|
||||
R.toPairs,
|
||||
)(pattern);
|
||||
|
||||
const or = R.anyPass;
|
||||
const and = R.allPass;
|
||||
|
||||
const then = fn => p => p.then(fn);
|
||||
const then = (fn) => (p) => p.then(fn);
|
||||
|
||||
const lazyTask = fn => data => () => fn(data);
|
||||
const lazyTask = (fn) => (data) => () => fn(data);
|
||||
|
||||
const batchLazyTasks = count => tasks => tasks.reduce((batches, task) => {
|
||||
if (batches.length === 0) return [[task]];
|
||||
const batchLazyTasks = (count) => (tasks) =>
|
||||
tasks.reduce((batches, task) => {
|
||||
if (batches.length === 0) return [[task]];
|
||||
|
||||
const full = R.init(batches);
|
||||
const last = R.last(batches);
|
||||
const full = R.init(batches);
|
||||
const last = R.last(batches);
|
||||
|
||||
if (last.length >= count) return [...batches, [task]];
|
||||
return [...full, [...last, task]];
|
||||
}, []);
|
||||
if (last.length >= count) return [...batches, [task]];
|
||||
return [...full, [...last, task]];
|
||||
}, []);
|
||||
|
||||
const withLengthGte = n => R.compose(R.gte(R.__, n), R.length);
|
||||
const withLengthEq = n => R.compose(R.equals(n), R.length);
|
||||
const withLengthGte = (n) => R.compose(R.gte(R.__, n), R.length);
|
||||
const withLengthEq = (n) => R.compose(R.equals(n), R.length);
|
||||
|
||||
module.exports = {
|
||||
or,
|
||||
and,
|
||||
validate,
|
||||
between,
|
||||
testRegex,
|
||||
log,
|
||||
print,
|
||||
then,
|
||||
lazyTask,
|
||||
batchLazyTasks,
|
||||
withLengthEq,
|
||||
withLengthGte,
|
||||
or,
|
||||
and,
|
||||
validate,
|
||||
between,
|
||||
testRegex,
|
||||
log,
|
||||
print,
|
||||
then,
|
||||
lazyTask,
|
||||
batchLazyTasks,
|
||||
withLengthEq,
|
||||
withLengthGte,
|
||||
};
|
||||
|
||||
|
||||
+22
-22
@@ -1,24 +1,24 @@
|
||||
[
|
||||
"_acme-challenge",
|
||||
"_discord",
|
||||
"_dmarc",
|
||||
"_domainkey",
|
||||
"_gh-is-a-dev",
|
||||
"_github-challenge-is-a-dev",
|
||||
"_github-pages-challenge-is-a-dev",
|
||||
"_gitlab-pages-verification-code",
|
||||
"con",
|
||||
"help",
|
||||
"no-reply",
|
||||
"noreply",
|
||||
"notification",
|
||||
"notifications",
|
||||
"support",
|
||||
"ww",
|
||||
"ww1",
|
||||
"ww2",
|
||||
"ww3",
|
||||
"ww4",
|
||||
"wwww",
|
||||
"your-domain-name"
|
||||
"_acme-challenge",
|
||||
"_discord",
|
||||
"_dmarc",
|
||||
"_domainkey",
|
||||
"_gh-is-a-dev",
|
||||
"_github-challenge-is-a-dev",
|
||||
"_github-pages-challenge-is-a-dev",
|
||||
"_gitlab-pages-verification-code",
|
||||
"con",
|
||||
"help",
|
||||
"no-reply",
|
||||
"noreply",
|
||||
"notification",
|
||||
"notifications",
|
||||
"support",
|
||||
"ww",
|
||||
"ww1",
|
||||
"ww2",
|
||||
"ww3",
|
||||
"ww4",
|
||||
"wwww",
|
||||
"your-domain-name"
|
||||
]
|
||||
|
||||
+110
-87
@@ -1,100 +1,123 @@
|
||||
const R = require('ramda');
|
||||
const qs = require('querystring');
|
||||
const { DOMAIN_API_HOST, DOMAIN_API_PORT, DOMAIN_USER, DOMAIN_API_KEY, DOMAIN_DOMAIN } = require('../constants');
|
||||
const R = require("ramda");
|
||||
const qs = require("querystring");
|
||||
const {
|
||||
DOMAIN_API_HOST,
|
||||
DOMAIN_API_PORT,
|
||||
DOMAIN_USER,
|
||||
DOMAIN_API_KEY,
|
||||
DOMAIN_DOMAIN,
|
||||
} = require("../constants");
|
||||
|
||||
const CpanelClient = (options) => {
|
||||
const api = ({ basePath = '', action = '' }) => (module, func, defaultQuery = {}) => (q = {}) => {
|
||||
const query = {
|
||||
...defaultQuery,
|
||||
...q,
|
||||
cpanel_jsonapi_user: options.username,
|
||||
cpanel_jsonapi_module: module,
|
||||
cpanel_jsonapi_func: func,
|
||||
cpanel_jsonapi_apiversion: 2,
|
||||
const api =
|
||||
({ basePath = "", action = "" }) =>
|
||||
(module, func, defaultQuery = {}) =>
|
||||
(q = {}) => {
|
||||
const query = {
|
||||
...defaultQuery,
|
||||
...q,
|
||||
cpanel_jsonapi_user: options.username,
|
||||
cpanel_jsonapi_module: module,
|
||||
cpanel_jsonapi_func: func,
|
||||
cpanel_jsonapi_apiversion: 2,
|
||||
};
|
||||
|
||||
const request = {
|
||||
headers: {
|
||||
Authorization: `cpanel ${options.username}:${options.apiKey}`,
|
||||
},
|
||||
rejectUnauthorized: false,
|
||||
};
|
||||
|
||||
const path = `${basePath}/${action}?${qs.stringify(query)}`;
|
||||
const reqUrl = `https://${options.host}:${options.port}/${path}`;
|
||||
|
||||
const { fetch } = options.dependencies;
|
||||
return fetch(reqUrl, request).then((res) => res.json());
|
||||
};
|
||||
|
||||
const api2 = api({ basePath: "json-api", action: "cpanel" });
|
||||
const uapi = (module, func, defaultQuery) =>
|
||||
api({ basePath: "execute", action: `${module}/${func}` })(
|
||||
module,
|
||||
func,
|
||||
defaultQuery,
|
||||
);
|
||||
|
||||
return {
|
||||
zone: {
|
||||
// { customonly, domain }
|
||||
// -> [{ class, ttl, name, line, Line, cname, type, record }]
|
||||
fetch: R.compose(
|
||||
(p) => p.then(R.pathOr([], ["cpanelresult", "data"])),
|
||||
api2("ZoneEdit", "fetchzone_records", {
|
||||
customonly: 0,
|
||||
domain: options.domain,
|
||||
}),
|
||||
),
|
||||
|
||||
// { name, type(A|CNAME), cname, address, ttl }
|
||||
// -> {}
|
||||
add: api2("ZoneEdit", "add_zone_record", {
|
||||
domain: options.domain,
|
||||
}),
|
||||
|
||||
// { line }
|
||||
// -> {}
|
||||
remove: api2("ZoneEdit", "remove_zone_record", {
|
||||
domain: options.domain,
|
||||
}),
|
||||
},
|
||||
redirection: {
|
||||
// {}
|
||||
// -> [{ domain, destination }]
|
||||
fetch: R.compose(
|
||||
(p) => p.then(R.pathOr([], ["data"])),
|
||||
uapi("Mime", "list_redirects"),
|
||||
),
|
||||
|
||||
// { domain, redirect, type(permanent|tmp), redirect_wildcard(0|1), redirect(0|1|2) }
|
||||
// -> {}
|
||||
add: uapi("Mime", "add_redirect"),
|
||||
|
||||
// { domain }
|
||||
// -> {}
|
||||
remove: uapi("Mime", "delete_redirect"),
|
||||
},
|
||||
file: {
|
||||
write: uapi("Fileman", "save_file_content", {
|
||||
from_charset: "UTF-8",
|
||||
to_charset: "UTF-8",
|
||||
fallback: 1,
|
||||
}),
|
||||
},
|
||||
email: {
|
||||
// { domain, exchanger, priority }
|
||||
// -> {}
|
||||
add: uapi("Email", "add_mx", { alwaysaccept: "auto" }),
|
||||
|
||||
// { domain, exchanger, priority }
|
||||
// -> {}
|
||||
remove: uapi("Email", "delete_mx", { alwaysaccept: "auto" }),
|
||||
},
|
||||
};
|
||||
|
||||
const request = {
|
||||
headers: {
|
||||
Authorization: `cpanel ${options.username}:${options.apiKey}`,
|
||||
},
|
||||
rejectUnauthorized: false,
|
||||
};
|
||||
|
||||
const path = `${basePath}/${action}?${qs.stringify(query)}`;
|
||||
const reqUrl = `https://${options.host}:${options.port}/${path}`;
|
||||
|
||||
const { fetch } = options.dependencies;
|
||||
return fetch(reqUrl, request).then(res => res.json());
|
||||
};
|
||||
|
||||
const api2 = api({ basePath: 'json-api', action: 'cpanel' });
|
||||
const uapi = (module, func, defaultQuery) =>
|
||||
api({ basePath: 'execute', action: `${module}/${func}` })(module, func, defaultQuery);
|
||||
|
||||
return {
|
||||
zone: {
|
||||
// { customonly, domain }
|
||||
// -> [{ class, ttl, name, line, Line, cname, type, record }]
|
||||
fetch: R.compose(
|
||||
p => p.then(R.pathOr([], ['cpanelresult', 'data'])),
|
||||
api2('ZoneEdit', 'fetchzone_records', { customonly: 0, domain: options.domain })
|
||||
),
|
||||
|
||||
// { name, type(A|CNAME), cname, address, ttl }
|
||||
// -> {}
|
||||
add: api2('ZoneEdit', 'add_zone_record', { domain: options.domain }),
|
||||
|
||||
// { line }
|
||||
// -> {}
|
||||
remove: api2('ZoneEdit', 'remove_zone_record', { domain: options.domain }),
|
||||
},
|
||||
redirection: {
|
||||
// {}
|
||||
// -> [{ domain, destination }]
|
||||
fetch: R.compose(
|
||||
p => p.then(R.pathOr([], ['data'])),
|
||||
uapi('Mime', 'list_redirects'),
|
||||
),
|
||||
|
||||
// { domain, redirect, type(permanent|tmp), redirect_wildcard(0|1), redirect(0|1|2) }
|
||||
// -> {}
|
||||
add: uapi('Mime', 'add_redirect'),
|
||||
|
||||
// { domain }
|
||||
// -> {}
|
||||
remove: uapi('Mime', 'delete_redirect'),
|
||||
},
|
||||
file: {
|
||||
write: uapi('Fileman', 'save_file_content', { from_charset: 'UTF-8', to_charset: 'UTF-8', fallback: 1 }),
|
||||
},
|
||||
email: {
|
||||
// { domain, exchanger, priority }
|
||||
// -> {}
|
||||
add: uapi('Email', 'add_mx', { alwaysaccept: 'auto' }),
|
||||
|
||||
// { domain, exchanger, priority }
|
||||
// -> {}
|
||||
remove: uapi('Email', 'delete_mx', { alwaysaccept: 'auto' }),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
if (!DOMAIN_API_KEY) {
|
||||
console.error('Api key cannot be empty');
|
||||
process.exit(1);
|
||||
console.error("Api key cannot be empty");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const cpanel = CpanelClient({
|
||||
host: DOMAIN_API_HOST,
|
||||
port: DOMAIN_API_PORT,
|
||||
username: DOMAIN_USER,
|
||||
apiKey: DOMAIN_API_KEY,
|
||||
domain: DOMAIN_DOMAIN,
|
||||
dependencies: { fetch },
|
||||
host: DOMAIN_API_HOST,
|
||||
port: DOMAIN_API_PORT,
|
||||
username: DOMAIN_USER,
|
||||
apiKey: DOMAIN_API_KEY,
|
||||
domain: DOMAIN_DOMAIN,
|
||||
dependencies: { fetch },
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
cpanel,
|
||||
CpanelClient,
|
||||
cpanel,
|
||||
CpanelClient,
|
||||
};
|
||||
|
||||
|
||||
+110
-85
@@ -1,102 +1,127 @@
|
||||
const R = require('ramda');
|
||||
const { VALID_RECORD_TYPES } = require('./constants');
|
||||
const { or, and, validate, between, testRegex, withLengthEq, withLengthGte } = require('./helpers');
|
||||
const INVALID_NAMES = require('./invalid-domains.json');
|
||||
const ipRegex_ = require('ip-regex');
|
||||
const R = require("ramda");
|
||||
const { VALID_RECORD_TYPES } = require("./constants");
|
||||
const {
|
||||
or,
|
||||
and,
|
||||
validate,
|
||||
between,
|
||||
testRegex,
|
||||
withLengthEq,
|
||||
withLengthGte,
|
||||
} = require("./helpers");
|
||||
const INVALID_NAMES = require("./invalid-domains.json");
|
||||
const ipRegex_ = require("ip-regex");
|
||||
const ipRegex = ipRegex_.default ?? ipRegex_;
|
||||
|
||||
const isValidURL = and([R.is(String), testRegex(/^https?:\/\//ig)]);
|
||||
const isValidURL = and([R.is(String), testRegex(/^https?:\/\//gi)]);
|
||||
|
||||
const isValidDomain = and([R.is(String), testRegex(/^(([a-z0-9-]+)\.)+[a-z]+$/ig)]);
|
||||
|
||||
const validateCnameRecord = type => and([
|
||||
R.propIs(String, type),
|
||||
R.compose(withLengthEq(1), R.keys), // CNAME cannot be used with any other record
|
||||
R.propSatisfies(withLengthGte(4), type),
|
||||
R.propSatisfies(isValidDomain, type),
|
||||
const isValidDomain = and([
|
||||
R.is(String),
|
||||
testRegex(/^(([a-z0-9-]+)\.)+[a-z]+$/gi),
|
||||
]);
|
||||
|
||||
const validateARecord = type => and([
|
||||
R.propIs(Array, type),
|
||||
R.propSatisfies(withLengthGte(1), type),
|
||||
R.all(testRegex(ipRegex.v4({ exact: true }))),
|
||||
]);
|
||||
const validateCnameRecord = (type) =>
|
||||
and([
|
||||
R.propIs(String, type),
|
||||
R.compose(withLengthEq(1), R.keys), // CNAME cannot be used with any other record
|
||||
R.propSatisfies(withLengthGte(4), type),
|
||||
R.propSatisfies(isValidDomain, type),
|
||||
]);
|
||||
|
||||
const validateMXRecord = type => and([
|
||||
R.propIs(Array, type),
|
||||
R.propSatisfies(withLengthGte(1), type),
|
||||
R.propSatisfies(R.all(isValidDomain), type),
|
||||
]);
|
||||
const validateARecord = (type) =>
|
||||
and([
|
||||
R.propIs(Array, type),
|
||||
R.propSatisfies(withLengthGte(1), type),
|
||||
R.all(testRegex(ipRegex.v4({ exact: true }))),
|
||||
]);
|
||||
|
||||
const validateAAAARecord = R.propSatisfies(and([
|
||||
R.is(Array),
|
||||
withLengthGte(1),
|
||||
R.all(testRegex(ipRegex.v6({ exact: true }))),
|
||||
]))
|
||||
const validateMXRecord = (type) =>
|
||||
and([
|
||||
R.propIs(Array, type),
|
||||
R.propSatisfies(withLengthGte(1), type),
|
||||
R.propSatisfies(R.all(isValidDomain), type),
|
||||
]);
|
||||
|
||||
const checkRestrictedNames = R.complement(R.includes(R.__, INVALID_NAMES))
|
||||
const validateAAAARecord = R.propSatisfies(
|
||||
and([
|
||||
R.is(Array),
|
||||
withLengthGte(1),
|
||||
R.all(testRegex(ipRegex.v6({ exact: true }))),
|
||||
]),
|
||||
);
|
||||
|
||||
const checkRestrictedNames = R.complement(R.includes(R.__, INVALID_NAMES));
|
||||
|
||||
const extraSupportedNames = [
|
||||
testRegex(/^_github(-pages)?-challenge-[a-z0-9-_]+$/i), // Exception for github verification records
|
||||
R.equals('_discord'),
|
||||
R.equals('_gitlab-pages-verification-code'),
|
||||
R.equals('_acme-challenge'),
|
||||
R.equals('_dmarc'),
|
||||
R.equals('_domainkey'),
|
||||
testRegex(/^_gh-[a-z0-9-_]+$/i), // Exception for the new github org verification records
|
||||
]
|
||||
testRegex(/^_github(-pages)?-challenge-[a-z0-9-_]+$/i),
|
||||
R.equals("_discord"),
|
||||
R.equals("_gitlab-pages-verification-code"),
|
||||
R.equals("_acme-challenge"),
|
||||
R.equals("_dmarc"),
|
||||
R.equals("_domainkey"),
|
||||
testRegex(/^_gh-[a-z0-9-_]+$/i),
|
||||
];
|
||||
|
||||
const validateDomainData = validate({
|
||||
name: {
|
||||
reason: 'The name of the file is invalid. It must be lowercased, alphanumeric and each component must be more than 2 characters long',
|
||||
fn: or([
|
||||
R.equals('@'),
|
||||
and([
|
||||
R.is(String),
|
||||
checkRestrictedNames,
|
||||
R.compose(
|
||||
R.all(or([
|
||||
name: {
|
||||
reason: "The name of the file is invalid. It must be lowercased, alphanumeric and each component must be between 1-100 characters long.",
|
||||
fn: or([
|
||||
R.equals("@"),
|
||||
and([
|
||||
R.compose(between(2, 100), R.length),
|
||||
testRegex(/^[a-z0-9-]+$/g),
|
||||
checkRestrictedNames,
|
||||
R.is(String),
|
||||
checkRestrictedNames,
|
||||
R.compose(
|
||||
R.all(
|
||||
or([
|
||||
and([
|
||||
R.compose(between(1, 100), R.length),
|
||||
testRegex(/^[a-z0-9-]+$/g),
|
||||
checkRestrictedNames,
|
||||
]),
|
||||
...extraSupportedNames,
|
||||
]),
|
||||
),
|
||||
R.split("."),
|
||||
),
|
||||
]),
|
||||
...extraSupportedNames,
|
||||
])),
|
||||
R.split('.'),
|
||||
),
|
||||
])
|
||||
]),
|
||||
},
|
||||
description: { reason: '', fn: R.T, },
|
||||
repo: { reason: '', fn: R.T, },
|
||||
owner: {
|
||||
reason: '`owner` needs valid username and email properties',
|
||||
fn: and([
|
||||
R.is(Object),
|
||||
R.complement(R.isEmpty),
|
||||
R.where({
|
||||
username: and([R.is(String), withLengthGte(1)]),
|
||||
email: R.is(String),
|
||||
}),
|
||||
]),
|
||||
},
|
||||
record: {
|
||||
reason: 'Invalid record. CNAME records have to be a host name and A records has to be a list of ips',
|
||||
fn: and([
|
||||
R.is(Object),
|
||||
R.compose(R.isEmpty, R.difference(R.__, VALID_RECORD_TYPES), R.keys),
|
||||
R.cond([
|
||||
[R.has('CNAME'), validateCnameRecord('CNAME')],
|
||||
[R.has('A'), validateARecord('A')],
|
||||
[R.has('URL'), R.propSatisfies(isValidURL, 'URL')],
|
||||
[R.has('MX'), validateMXRecord('MX')],
|
||||
[R.has('TXT'), R.propSatisfies(or([ R.is(String), R.is(Array) ]), 'TXT')],
|
||||
[R.has('AAAA'), validateAAAARecord('AAAA')],
|
||||
[R.T, R.T],
|
||||
]),
|
||||
]),
|
||||
},
|
||||
]),
|
||||
},
|
||||
description: { reason: "", fn: R.T },
|
||||
repo: { reason: "", fn: R.T },
|
||||
owner: {
|
||||
reason: "`owner` key needs valid username and email properties.",
|
||||
fn: and([
|
||||
R.is(Object),
|
||||
R.complement(R.isEmpty),
|
||||
R.where({
|
||||
username: and([R.is(String), withLengthGte(1)]),
|
||||
email: R.is(String),
|
||||
}),
|
||||
]),
|
||||
},
|
||||
record: {
|
||||
reason: "Invalid record(s) found. Please check the record types and values.",
|
||||
fn: and([
|
||||
R.is(Object),
|
||||
R.compose(
|
||||
R.isEmpty,
|
||||
R.difference(R.__, VALID_RECORD_TYPES),
|
||||
R.keys,
|
||||
),
|
||||
R.cond([
|
||||
[R.has("CNAME"), validateCnameRecord("CNAME")],
|
||||
[R.has("A"), validateARecord("A")],
|
||||
[R.has("URL"), R.propSatisfies(isValidURL, "URL")],
|
||||
[R.has("MX"), validateMXRecord("MX")],
|
||||
[
|
||||
R.has("TXT"),
|
||||
R.propSatisfies(or([R.is(String), R.is(Array)]), "TXT"),
|
||||
],
|
||||
[R.has("AAAA"), validateAAAARecord("AAAA")],
|
||||
[R.T, R.T],
|
||||
]),
|
||||
]),
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = { validateDomainData, isValidDomain };
|
||||
|
||||
Reference in New Issue
Block a user