mirror of
https://github.com/tiennm99/is-a-dev.git
synced 2026-05-18 07:25:34 +00:00
Merge branch 'main' into main
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
NC_USER=xxx
|
||||
NC_API_KEY=xxxxxxxxxxxxxxx
|
||||
NC_DOMAIN=domain.com
|
||||
IP_ADDRESS=100.100.100.100
|
||||
DOMAIN_USER=username
|
||||
DOMAIN_API_KEY=apikey
|
||||
DOMAIN_API_HOST=api-example.com
|
||||
DOMAIN_API_PORT=2087
|
||||
DOMAIN_DOMAIN=example.com
|
||||
|
||||
@@ -8,12 +8,13 @@ jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI: '1'
|
||||
CI: 1
|
||||
ENV: production
|
||||
NC_USER: ${{ secrets.NC_USER }}
|
||||
NC_API_KEY: ${{ secrets.NC_API_KEY }}
|
||||
NC_DOMAIN: ${{ secrets.NC_DOMAIN }}
|
||||
IP_ADDRESS: ${{ secrets.IP_ADDRESS }}
|
||||
DOMAIN_USER: ${{ secrets.DOMAIN_USER }}
|
||||
DOMAIN_API_KEY: ${{ secrets.DOMAIN_API_KEY }}
|
||||
DOMAIN_API_HOST: ${{ secrets.DOMAIN_API_HOST }}
|
||||
DOMAIN_API_PORT: ${{ secrets.DOMAIN_API_PORT }}
|
||||
DOMAIN_DOMAIN: ${{ secrets.DOMAIN_DOMAIN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: borales/actions-yarn@v2.0.0
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
"email": "any@email"
|
||||
},
|
||||
"record": {
|
||||
"CNAME": "github-username.github.io",
|
||||
"URL": "https://your-domain.is-a.dev"
|
||||
"CNAME": "github-username.github.io"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -63,12 +62,11 @@ This is a link to your website repository or your github account. This is purely
|
||||
### record (required)
|
||||
This is where you specify how you want to link to your server/webpage.
|
||||
|
||||
Currently, only `CNAME`, `ALIAS`, `A`, `URL` record types are supported.
|
||||
Currently, only `CNAME`, `A`, `URL` record types are supported.
|
||||
|
||||
Here's a few different use cases for the given record types -
|
||||
|
||||
* **CNAME/ALIAS**
|
||||
Replace CNAME with ALIAS for alias record type
|
||||
* **CNAME**
|
||||
```json
|
||||
{
|
||||
"record": {
|
||||
@@ -100,13 +98,3 @@ Replace CNAME with ALIAS for alias record type
|
||||
}
|
||||
```
|
||||
|
||||
* **Force HTTPS on your CNAME (or ALIAS or A) record**
|
||||
```json
|
||||
{
|
||||
"record": {
|
||||
"CNAME": "username.github.io",
|
||||
"URL": "https://your-domain.is-a.dev"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"description": "A Mortal Web Developer With Immortal Dreams",
|
||||
"repo": "https://github.com/alestor123/alestor123.github.io",
|
||||
"owner": {
|
||||
"username": "alestor123",
|
||||
"email": "alestoraldous@gmail.com"
|
||||
},
|
||||
"record": {
|
||||
"CNAME": "alestor123.github.io",
|
||||
"URL": "https://alestor123.is-a.dev"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"description": "This is my first githib page",
|
||||
"repo": "https://github.com/alexkjoseph/alexkjoseph.github.io",
|
||||
"owner": {
|
||||
"username": "alexkjoseph",
|
||||
"email": "aj71563@gmail.com"
|
||||
},
|
||||
"record": {
|
||||
"CNAME": "alexkjoseph.github.io",
|
||||
"URL": "https://alexjoseph.is-a.dev"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"description": "B45i - Home Page",
|
||||
"repo": "https://github.com/B45i/b45i.github.io",
|
||||
"owner": {
|
||||
"username": "B45i",
|
||||
"email": "amalshajan2011@gmail.com"
|
||||
},
|
||||
"record": {
|
||||
"CNAME": "b45i.github.io",
|
||||
"URL": "https://b45i.is-a.dev"
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,6 @@
|
||||
"email": "bradley73@gmail.com"
|
||||
},
|
||||
"record": {
|
||||
"ALIAS": "bradleyholbrook.com"
|
||||
"CNAME": "bradleyholbrook.com"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"description": "Fullstack web & mobile dev, quality assurance, automation, reverse engineering, and flexible",
|
||||
"repo": "https://github.com/danhab99/danhab99.github.io",
|
||||
"owner": {
|
||||
"username": "danhab99",
|
||||
"email": "dan.habot@gmail.com"
|
||||
},
|
||||
"record": {
|
||||
"CNAME": "danhab99.github.io",
|
||||
"URL": "https://dan-habot.is-a.dev"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"description": "Davish's Wesbite",
|
||||
"repo": "https://github.com/akchy/akchy.github.io",
|
||||
"owner": {
|
||||
"username": "akchy",
|
||||
"email": "akarshashok12@gmail.com"
|
||||
},
|
||||
"record": {
|
||||
"CNAME": "akchy.github.io",
|
||||
"URL": "https://davish.is-a.dev"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"description": "Personal Page",
|
||||
"repo": "https://github.com/gkdskp/gkdskp.github.io",
|
||||
"owner": {
|
||||
"username": "gkdskp",
|
||||
"email": "gokuldskp@gmail.com"
|
||||
},
|
||||
"record": {
|
||||
"CNAME": "gkdskp.github.io",
|
||||
"URL": "https://gokul.is-a.dev"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"description": "Mamun's website",
|
||||
"repo": "https://github.com/mamunhpath",
|
||||
"owner": {
|
||||
"username": "mamunhpath",
|
||||
"email": "mamunhpath@hotmail.com"
|
||||
},
|
||||
"record": {
|
||||
"CNAME": "mamunhpath.github.io",
|
||||
"URL": "https://mamun.is-a.dev"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"description": "Piyush's Website",
|
||||
"owner": {
|
||||
"username": "officialpiyush",
|
||||
"email": "bhangalepiyush@gmail.com"
|
||||
},
|
||||
"record": {
|
||||
"URL": "https://piyush.codes"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"description": "personal website",
|
||||
"repo": "https://github.com/SharifClick/sharifclick.github.io",
|
||||
"owner": {
|
||||
"username": "sharifclick",
|
||||
"email": "me.sharifahmed@gmail.com"
|
||||
},
|
||||
"record": {
|
||||
"CNAME": "sharifclick.github.io",
|
||||
"URL": "https://sharifclick.is-a.dev"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"owner": {
|
||||
"username": "tjrgg",
|
||||
"email": "hello@tjrgg.co"
|
||||
},
|
||||
"record": {
|
||||
"URL": "https://tjrgg.co"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"owner": {
|
||||
"username": "tjrgg",
|
||||
"email": "hello@tjrgg.co"
|
||||
},
|
||||
"record": {
|
||||
"URL": "https://tjrgg.co"
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
"description": "Register *.is-a.dev domains for free",
|
||||
"scripts": {
|
||||
"test": "ENV=test jest",
|
||||
"publish-records": "yarn test && node ./scripts/register-domains.js"
|
||||
"publish-records": "node ./scripts/register-domains.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -16,9 +16,9 @@
|
||||
"author": "Akshay Nair <phenax5@gmail.com>",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@rqt/namecheap": "^2.4.2",
|
||||
"dotenv": "^8.2.0",
|
||||
"jest": "^26.4.2",
|
||||
"node-fetch": "^2.6.1",
|
||||
"ramda": "^0.27.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,27 +10,27 @@ const toHostList = R.chain(data => {
|
||||
|
||||
return R.chain(([recordType, urls]) =>
|
||||
(Array.isArray(urls) ? urls : [urls]).map(url => ({
|
||||
HostName: data.name,
|
||||
RecordType: recordType,
|
||||
Address: url,
|
||||
TTL,
|
||||
name: data.name,
|
||||
type: recordType,
|
||||
address: (recordType === 'CNAME' ? `${url}`.toLowerCase() : `${url}`).replace(/\/$/g, ''),
|
||||
ttl: TTL,
|
||||
}))
|
||||
, rs);
|
||||
});
|
||||
|
||||
const registerDomains = async ({ domainService, getDomains }) => {
|
||||
const registerDomains = async ({ domainService, getDomains, log = () => {} }) => {
|
||||
const domains = await getDomains().then(toHostList);
|
||||
|
||||
if (domains.length === 0)
|
||||
return Promise.reject(new Error('Nothing to register'));
|
||||
|
||||
console.log(`Publishing ${domains.length} records...`);
|
||||
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 });
|
||||
const result = await registerDomains({ domainService: dc, getDomains: gd, log: console.log });
|
||||
console.log(result);
|
||||
};
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
|
||||
const getInstructions = () => `
|
||||
The changes you have made will soon be reflected!!
|
||||
The changes have been published!! It should reflect in less than 24 hours.
|
||||
|
||||
## Here\'s what you need to do next
|
||||
|
||||
@@ -16,7 +16,7 @@ If your domain points to a server you own, add \`domain-name.is-a.dev\` to your
|
||||
|
||||
|
||||
## Need support with your domain?
|
||||
If you are having trouble setting up your domain, [create an issue](https://github.com/is-a-dev/register/issues/new/choose) and pick the \`support\` template. Describe any issue you are facing there. I\'ll try my best to get back to you asap!
|
||||
If you are having trouble setting up your domain, [create an issue](https://github.com/is-a-dev/register/issues/new/choose). I\'ll try my best to get back to you asap!
|
||||
|
||||
|
||||
## Love/Hate the service?
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
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=1&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 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=1&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' });
|
||||
});
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
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,
|
||||
});
|
||||
});
|
||||
|
||||
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'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+212
-75
@@ -1,118 +1,255 @@
|
||||
const R = require('ramda');
|
||||
const { getDomainService } = require('../utils/domain-service');
|
||||
const { getDomainService, diffRecords } = require('../utils/domain-service');
|
||||
const {DOMAIN_DOMAIN} = require('../utils/constants');
|
||||
|
||||
const getNc = ({ onSet, onGet } = {}) => ({
|
||||
dns: {
|
||||
setHosts: (_, list) => onSet(list),
|
||||
getHosts: (_) => onGet(),
|
||||
const getCpanel = ({ zone, addZone, editZone, redir, addRedir, editRedir } = {}) => ({
|
||||
zone: {
|
||||
fetch: (_) => zone(),
|
||||
add: (rec) => addZone(rec),
|
||||
edit: (rec) => editZone(rec),
|
||||
},
|
||||
redirection: {
|
||||
fetch: (_) => redir(),
|
||||
add: (rec) => addRedir(rec),
|
||||
edit: (rec) => editRedir(rec),
|
||||
},
|
||||
});
|
||||
|
||||
describe('Domain service', () => {
|
||||
describe('getHosts', () => {
|
||||
it('should resolve with a list of hosts', async () => {
|
||||
const hosts = [
|
||||
{ Name: 'xx', Type: 'CNAME', Address: 'fck.com.' },
|
||||
{ Name: 'xx', Type: 'A', Address: '111.1.1212.1' },
|
||||
];
|
||||
const onGet = async () => ({ hosts })
|
||||
const mockDomainService = getDomainService({ nc: getNc({ onGet }) });
|
||||
const list = await mockDomainService.getHosts();
|
||||
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' },
|
||||
];
|
||||
|
||||
expect(list).toEqual([
|
||||
{ HostName: 'xx', RecordType: 'CNAME', Address: 'fck.com' },
|
||||
{ HostName: 'xx', RecordType: 'A', Address: '111.1.1212.1' },
|
||||
]);
|
||||
const result = diffRecords(oldRecords, newRecords);
|
||||
expect(result).toEqual({
|
||||
edit: [],
|
||||
add: [
|
||||
{ name: 'boo', type: 'CNAME', address: 'x.com' },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
describe('setHosts', () => {
|
||||
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({
|
||||
edit: [
|
||||
{ name: 'xa', type: 'A', address: '69.69.69.69' },
|
||||
],
|
||||
add: [],
|
||||
});
|
||||
});
|
||||
|
||||
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({
|
||||
edit: [],
|
||||
add: [
|
||||
{ name: 'xa', type: 'A', address: '69.69.4.20' },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Domain service', () => {
|
||||
const addZone = jest.fn(async () => ({}));
|
||||
const editZone = jest.fn(async () => ({}));
|
||||
const addRedir = jest.fn(async () => ({}));
|
||||
const editRedir = jest.fn(async () => ({}));
|
||||
|
||||
const mockDS = ({ zones, redirections }) => getDomainService({ cpanel: getCpanel({
|
||||
zone: async () => zones,
|
||||
redir: async () => redirections,
|
||||
addZone,
|
||||
addRedir,
|
||||
editZone,
|
||||
editRedir,
|
||||
}) });
|
||||
|
||||
const getRecordCalls = recfn => recfn.mock.calls.map(R.head).map(R.pick(['name', 'type', 'address', 'redirect', 'domain']));
|
||||
|
||||
beforeEach(() => {
|
||||
addZone.mockClear();
|
||||
editZone.mockClear();
|
||||
addRedir.mockClear();
|
||||
editRedir.mockClear();
|
||||
});
|
||||
|
||||
describe('getHosts', () => {
|
||||
it('should resolve with a list of hosts', async () => {
|
||||
const records = [ { x: 'y' }, { z: 'a' } ];
|
||||
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();
|
||||
|
||||
const onSet = jest.fn((list) => {
|
||||
expect(list).toBe(records);
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
expect(list).toEqual([
|
||||
{ name: 'xx', type: 'CNAME', address: 'fck.com' },
|
||||
{ name: 'xx', type: 'A', address: '111.1.1212.1' },
|
||||
]);
|
||||
});
|
||||
|
||||
const mockDomainService = getDomainService({ nc: getNc({ onSet }) });
|
||||
await mockDomainService.setHosts(records);
|
||||
expect(onSet).toBeCalledTimes(1);
|
||||
it('should resolve with a redirections', async () => {
|
||||
const zones = [
|
||||
{ name: 'xx', type: 'CNAME', address: 'fck.com.' },
|
||||
{ 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();
|
||||
|
||||
expect(list).toEqual([
|
||||
{ name: 'xx', type: 'CNAME', address: 'fck.com' },
|
||||
{ name: 'xx', type: 'A', address: '111.1.1212.1' },
|
||||
{ name: 'foo', type: 'URL', address: 'https://google.com' },
|
||||
{ name: 'foo1', type: 'URL', address: 'https://duck.com' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateHosts', () => {
|
||||
it('should append new hosts with existing ones and set it', async () => {
|
||||
const records = [
|
||||
{ HostId: 1, Name: 'a', Type: 'CNAME', Address: 'boo' },
|
||||
{ HostId: 2, Name: 'b', Type: 'CNAME', Address: 'goo' },
|
||||
const zones = [
|
||||
{ someid: 1, name: 'a', type: 'CNAME', address: 'boo' },
|
||||
{ someid: 2, name: 'b', type: 'CNAME', address: 'goo' },
|
||||
];
|
||||
const redirections = [];
|
||||
|
||||
const onGet = () => Promise.resolve({ hosts: records });
|
||||
const onSet = jest.fn(async () => ({}));
|
||||
|
||||
const mockDomainService = getDomainService({ nc: getNc({ onSet, onGet }) });
|
||||
const mockDomainService = mockDS({ zones, redirections });
|
||||
await mockDomainService.updateHosts([
|
||||
{ HostName: 'a', RecordType: 'CNAME', Address: 'boo' },
|
||||
{ HostName: 'b', RecordType: 'CNAME', Address: 'goo' },
|
||||
{ HostName: 'c', RecordType: 'A', Address: '12.131321.213' },
|
||||
{ name: 'a', type: 'CNAME', address: 'boo' },
|
||||
{ name: 'b', type: 'CNAME', address: 'goo' },
|
||||
{ name: 'c', type: 'A', address: '12.131321.213' },
|
||||
]);
|
||||
|
||||
const [hosts] = onSet.mock.calls[0];
|
||||
expect(addZone).toBeCalledTimes(1);
|
||||
expect(getRecordCalls(addZone)).toEqual([
|
||||
{ name: 'c', type: 'A', address: '12.131321.213' },
|
||||
]);
|
||||
expect(editZone).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
expect(hosts.map(R.pick(['HostName', 'RecordType', 'Address']))).toEqual([
|
||||
{ HostName: 'a', RecordType: 'CNAME', Address: 'boo' },
|
||||
{ HostName: 'b', RecordType: 'CNAME', Address: 'goo' },
|
||||
{ HostName: 'c', RecordType: 'A', Address: '12.131321.213' },
|
||||
it('should update matching host and set it', async () => {
|
||||
const zones = [
|
||||
{ someid: 1, name: 'a', type: 'CNAME', address: 'boo' },
|
||||
{ someid: 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).toBeCalledTimes(0);
|
||||
expect(editZone).toBeCalledTimes(1);
|
||||
expect(getRecordCalls(editZone)).toEqual([
|
||||
{ name: 'b', type: 'CNAME', address: 'googoogaga' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should update matching host and set it', async () => {
|
||||
const records = [
|
||||
{ HostId: 1, Name: 'a', Type: 'CNAME', Address: 'boo' },
|
||||
{ HostId: 2, Name: 'b', Type: 'CNAME', Address: 'goo' },
|
||||
const zones = [
|
||||
{ someid: 1, name: 'a', type: 'CNAME', address: 'boo' },
|
||||
{ someid: 2, name: 'b', type: 'CNAME', address: 'goo' },
|
||||
{ someid: 2, name: 'b', type: 'CNAME', address: 'xaa' },
|
||||
];
|
||||
const redirections = [];
|
||||
|
||||
const onGet = () => Promise.resolve({ hosts: records });
|
||||
const onSet = jest.fn(async () => ({}));
|
||||
|
||||
const mockDomainService = getDomainService({ nc: getNc({ onSet, onGet }) });
|
||||
const mockDomainService = mockDS({ zones, redirections });
|
||||
await mockDomainService.updateHosts([
|
||||
{ HostName: 'a', RecordType: 'CNAME', Address: 'boo' },
|
||||
{ HostName: 'b', RecordType: 'CNAME', Address: 'googoogaga' },
|
||||
{ name: 'a', type: 'CNAME', address: 'boo' },
|
||||
{ name: 'b', type: 'CNAME', address: 'googoogaga' },
|
||||
{ name: 'b', type: 'CNAME', address: 'farboo' },
|
||||
]);
|
||||
|
||||
const [hosts] = onSet.mock.calls[0];
|
||||
|
||||
expect(hosts.map(R.pick(['HostName', 'RecordType', 'Address']))).toEqual([
|
||||
{ HostName: 'a', RecordType: 'CNAME', Address: 'boo' },
|
||||
{ HostName: 'b', RecordType: 'CNAME', Address: 'googoogaga' },
|
||||
expect(addZone).toBeCalledTimes(0);
|
||||
expect(editZone).toBeCalledTimes(2);
|
||||
expect(getRecordCalls(editZone)).toEqual([
|
||||
{ name: 'b', type: 'CNAME', address: 'googoogaga' },
|
||||
{ name: 'b', type: 'CNAME', address: 'farboo' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should update matching host and set it', async () => {
|
||||
const records = [
|
||||
{ HostId: 1, Name: 'a', Type: 'CNAME', Address: 'boo' },
|
||||
{ HostId: 2, Name: 'b', Type: 'CNAME', Address: 'goo' },
|
||||
{ HostId: 2, Name: 'b', Type: 'CNAME', Address: 'xaa' },
|
||||
it('should workout this complex example', async () => {
|
||||
const zones = [
|
||||
{ someid: 1, name: 'a', type: 'CNAME', address: 'world' },
|
||||
{ someid: 2, name: 'b', type: 'A', address: '1' },
|
||||
{ someid: 2, name: 'b', type: 'A', address: '2' },
|
||||
{ someid: 2, name: 'c', type: 'CNAME', address: 'hello.com' },
|
||||
];
|
||||
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 onGet = () => Promise.resolve({ hosts: records });
|
||||
const onSet = jest.fn(async () => ({}));
|
||||
|
||||
const mockDomainService = getDomainService({ nc: getNc({ onSet, onGet }) });
|
||||
const mockDomainService = mockDS({ zones, redirections });
|
||||
await mockDomainService.updateHosts([
|
||||
{ HostName: 'a', RecordType: 'CNAME', Address: 'boo' },
|
||||
{ HostName: 'b', RecordType: 'CNAME', Address: 'googoogaga' },
|
||||
{ HostName: 'b', RecordType: 'CNAME', Address: 'farboo' },
|
||||
{ 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' },
|
||||
]);
|
||||
|
||||
const [hosts] = onSet.mock.calls[0];
|
||||
|
||||
expect(hosts.map(R.pick(['HostName', 'RecordType', 'Address']))).toEqual([
|
||||
{ HostName: 'a', RecordType: 'CNAME', Address: 'boo' },
|
||||
{ HostName: 'b', RecordType: 'CNAME', Address: 'googoogaga' },
|
||||
{ HostName: 'b', RecordType: 'CNAME', Address: 'farboo' },
|
||||
expect(addZone).toBeCalledTimes(2);
|
||||
expect(editZone).toBeCalledTimes(1);
|
||||
expect(addRedir).toBeCalledTimes(1);
|
||||
expect(editRedir).toBeCalledTimes(2);
|
||||
expect(getRecordCalls(addZone)).toEqual([
|
||||
{ name: 'b', type: 'A', address: '3' },
|
||||
{ name: 'd', type: 'CNAME', address: 'helo.com' }
|
||||
]);
|
||||
expect(getRecordCalls(editZone)).toEqual([
|
||||
{ name: 'a', type: 'CNAME', address: 'boo' },
|
||||
]);
|
||||
expect(getRecordCalls(addRedir)).toEqual([
|
||||
{ domain: `d.${DOMAIN_DOMAIN}`, type: 'permanent', redirect: 'https://hhh.com' },
|
||||
]);
|
||||
expect(getRecordCalls(editRedir)).toEqual([
|
||||
{ domain: `b.${DOMAIN_DOMAIN}`, type: 'permanent', redirect: 'https://wowow.com' },
|
||||
{ domain: `x.${DOMAIN_DOMAIN}`, type: 'permanent', redirect: 'https://example69.com' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
const R = require('ramda');
|
||||
const fs = require('fs');
|
||||
const { getDomains, validateDomainData } = require('../utils/domain');
|
||||
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);
|
||||
});
|
||||
|
||||
it('should be valid', (done) => {
|
||||
getDomains()
|
||||
.then(R.map(data => {
|
||||
|
||||
+61
-87
@@ -1,11 +1,18 @@
|
||||
const R = require('ramda');
|
||||
const { toHostList, registerDomains } = require('../scripts/register-domains');
|
||||
const { TTL } = require('../utils/constants');
|
||||
const { TTL, DOMAIN_DOMAIN } = require('../utils/constants');
|
||||
const { getDomainService } = require('../utils/domain-service');
|
||||
|
||||
const getNc = ({ onSet, onGet } = {}) => ({
|
||||
dns: {
|
||||
setHosts: (_, list) => onSet(list),
|
||||
getHosts: (_) => onGet(),
|
||||
const getCpanel = ({ zone, addZone, editZone, redir, addRedir, editRedir } = {}) => ({
|
||||
zone: {
|
||||
fetch: (_) => zone(),
|
||||
add: (rec) => addZone(rec),
|
||||
edit: (rec) => editZone(rec),
|
||||
},
|
||||
redirection: {
|
||||
fetch: (_) => redir(),
|
||||
add: (rec) => addRedir(rec),
|
||||
edit: (rec) => editRedir(rec),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -18,113 +25,80 @@ describe('toHostList', () => {
|
||||
]);
|
||||
|
||||
expect(res).toEqual([
|
||||
{ HostName: 'akshay', RecordType: 'CNAME', Address: 'phenax.github.io', TTL },
|
||||
{ HostName: 'foobar', RecordType: 'CNAME', Address: 'v.io', TTL },
|
||||
{ HostName: 'xx', RecordType: 'A', Address: '1.2.3.4', TTL },
|
||||
{ HostName: 'xx', RecordType: 'A', Address: '5.6.3.2', TTL },
|
||||
{ HostName: 'xx', RecordType: 'A', Address: '1.2.31.1', TTL },
|
||||
{ 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 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('registerDomains', () => {
|
||||
const addZone = jest.fn(async () => ({}));
|
||||
const editZone = jest.fn(async () => ({}));
|
||||
const addRedir = jest.fn(async () => ({}));
|
||||
const editRedir = jest.fn(async () => ({}));
|
||||
|
||||
const mockDS = ({ zones, redirections }) => getDomainService({ cpanel: getCpanel({
|
||||
zone: async () => zones,
|
||||
redir: async () => redirections,
|
||||
addZone,
|
||||
addRedir,
|
||||
editZone,
|
||||
editRedir,
|
||||
}) });
|
||||
|
||||
beforeEach(() => {
|
||||
addZone.mockClear();
|
||||
editZone.mockClear();
|
||||
addRedir.mockClear();
|
||||
editRedir.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 = [
|
||||
{ HostId: 1, Name: 'a', Type: 'CNAME', Address: 'boo' },
|
||||
{ HostId: 2, Name: 'b', Type: 'CNAME', Address: 'goo' },
|
||||
{ HostId: 2, Name: 'b', Type: 'CNAME', Address: 'xaa' },
|
||||
{ someididk: 1, name: 'a', type: 'CNAME', address: 'hello' },
|
||||
{ someididk: 2, name: 'b', type: 'CNAME', address: 'goo' },
|
||||
{ someididk: 2, name: 'b', type: 'CNAME', address: 'xaa' },
|
||||
];
|
||||
const remoteRedirections = [];
|
||||
|
||||
const onSet = jest.fn(async () => ({}));
|
||||
|
||||
const domainService = getDomainService({ nc: getNc({ onSet, onGet: async () => ({ hosts: remoteHosts }) }) });
|
||||
const domainService = mockDS({ zones: remoteHosts, redirections: remoteRedirections });
|
||||
await registerDomains({ getDomains: async () => localHosts, domainService });
|
||||
|
||||
expect(onSet).toBeCalledTimes(1);
|
||||
|
||||
const [hosts] = onSet.mock.calls[0];
|
||||
expect(hosts).toEqual([
|
||||
{ HostId: 1, Address: 'hello', HostName: 'a', RecordType: 'CNAME', TTL },
|
||||
{ HostId: 2, Address: 'xaa', HostName: 'b', RecordType: 'CNAME', TTL },
|
||||
]);
|
||||
expect(addZone).toBeCalledTimes(0);
|
||||
expect(editZone).toBeCalledTimes(0);
|
||||
expect(addRedir).toBeCalledTimes(0);
|
||||
expect(editRedir).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should add the new set hosts', async () => {
|
||||
const localHosts = [
|
||||
{ name: 'a', record: { CNAME: 'boo' } },
|
||||
{ name: 'b', record: { CNAME: 'xaa' } },
|
||||
{ name: 'c', record: { CNAME: 'yello' } },
|
||||
{ name: 'a', record: { CNAME: 'boo', URL: 'z' } },
|
||||
{ name: 'b', record: { CNAME: 'xaa', URL: 'x' } },
|
||||
{ name: 'c', record: { CNAME: 'yello', URL: 'https://google.com' } },
|
||||
];
|
||||
const remoteHosts = [
|
||||
{ HostId: 1, Name: 'a', Type: 'CNAME', Address: 'boo' },
|
||||
{ HostId: 2, Name: 'b', Type: 'CNAME', Address: 'xaa' },
|
||||
{ someididk: 1, name: 'a', type: 'CNAME', address: 'boo' },
|
||||
{ someididk: 2, name: 'b', type: 'CNAME', address: 'xaa' },
|
||||
];
|
||||
const remoteRedirections = [
|
||||
{ domain: `b.${DOMAIN_DOMAIN}`, destination: 'x' },
|
||||
{ domain: `a.${DOMAIN_DOMAIN}`, destination: 'y' },
|
||||
];
|
||||
|
||||
const onSet = jest.fn(async () => ({}));
|
||||
|
||||
const domainService = getDomainService({ nc: getNc({ onSet, onGet: async () => ({ hosts: remoteHosts }) }) });
|
||||
const domainService = mockDS({ zones: remoteHosts, redirections: remoteRedirections });
|
||||
await registerDomains({ getDomains: async () => localHosts, domainService });
|
||||
|
||||
expect(onSet).toBeCalledTimes(1);
|
||||
|
||||
const [hosts] = onSet.mock.calls[0];
|
||||
expect(hosts).toEqual([
|
||||
{ HostId: 1, Address: 'boo', HostName: 'a', RecordType: 'CNAME', TTL },
|
||||
{ HostId: 2, Address: 'xaa', HostName: 'b', RecordType: 'CNAME', TTL },
|
||||
{ Address: 'yello', HostName: 'c', RecordType: 'CNAME', TTL },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should remove unlisted hosts', async () => {
|
||||
const localHosts = [
|
||||
{ name: 'a', record: { CNAME: 'boo' } },
|
||||
];
|
||||
const remoteHosts = [
|
||||
{ HostId: 1, Name: 'a', Type: 'CNAME', Address: 'boo' },
|
||||
{ HostId: 2, Name: 'b', Type: 'CNAME', Address: 'xaa' },
|
||||
];
|
||||
|
||||
const onSet = jest.fn(async () => ({}));
|
||||
|
||||
const domainService = getDomainService({ nc: getNc({ onSet, onGet: async () => ({ hosts: remoteHosts }) }) });
|
||||
await registerDomains({ getDomains: async () => localHosts, domainService });
|
||||
|
||||
expect(onSet).toBeCalledTimes(1);
|
||||
|
||||
const [hosts] = onSet.mock.calls[0];
|
||||
expect(hosts).toEqual([
|
||||
{ HostId: 1, Address: 'boo', HostName: 'a', RecordType: 'CNAME', TTL },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should change record type from cname to a', async () => {
|
||||
const localHosts = [
|
||||
{ name: 'a', record: { CNAME: 'boo' } },
|
||||
{ name: 'b', record: { A: ['1', '2', '3'] } },
|
||||
];
|
||||
const remoteHosts = [
|
||||
{ HostId: 1, Name: 'a', Type: 'CNAME', Address: 'boo' },
|
||||
{ HostId: 2, Name: 'b', Type: 'CNAME', Address: 'xaa' },
|
||||
];
|
||||
|
||||
const onSet = jest.fn(async () => ({}));
|
||||
|
||||
const domainService = getDomainService({ nc: getNc({ onSet, onGet: async () => ({ hosts: remoteHosts }) }) });
|
||||
await registerDomains({ getDomains: async () => localHosts, domainService });
|
||||
|
||||
expect(onSet).toBeCalledTimes(1);
|
||||
|
||||
const [hosts] = onSet.mock.calls[0];
|
||||
expect(hosts).toEqual([
|
||||
{ HostId: 1, Address: 'boo', HostName: 'a', RecordType: 'CNAME', TTL },
|
||||
{ Address: '1', HostName: 'b', RecordType: 'A', TTL },
|
||||
{ Address: '2', HostName: 'b', RecordType: 'A', TTL },
|
||||
{ Address: '3', HostName: 'b', RecordType: 'A', TTL },
|
||||
]);
|
||||
expect(addZone).toBeCalledTimes(1);
|
||||
expect(editZone).toBeCalledTimes(0);
|
||||
expect(addRedir).toBeCalledTimes(1);
|
||||
expect(editRedir).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+13
-8
@@ -1,21 +1,26 @@
|
||||
const path = require('path');
|
||||
|
||||
const { ENV = 'sandbox', CI } = process.env;
|
||||
const { ENV = 'test', CI } = process.env;
|
||||
|
||||
if (!CI) {
|
||||
require('dotenv').config({ path: path.resolve(`.env.${ENV}`) });
|
||||
}
|
||||
|
||||
const { NC_USER, NC_API_KEY, NC_DOMAIN, IP_ADDRESS } = process.env;
|
||||
const { DOMAIN_USER, DOMAIN_API_KEY, DOMAIN_DOMAIN, DOMAIN_API_HOST, DOMAIN_API_PORT } = process.env;
|
||||
|
||||
const IS_TEST = ENV === 'test';
|
||||
|
||||
const DOMAINS_PATH = require('path').resolve('domains');
|
||||
|
||||
module.exports = {
|
||||
ENV,
|
||||
VALID_RECORD_TYPES: ['CNAME', 'A', 'ALIAS', 'URL'],
|
||||
NC_DOMAIN: NC_DOMAIN || 'booboo.xyz',
|
||||
NC_USER: IS_TEST ? 'testuser' : NC_USER,
|
||||
NC_API_KEY: IS_TEST ? 'testkey' : NC_API_KEY,
|
||||
IP_ADDRESS,
|
||||
TTL: 5*60,
|
||||
IS_TEST,
|
||||
VALID_RECORD_TYPES: ['CNAME', 'A', 'URL'],
|
||||
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,
|
||||
DOMAINS_PATH,
|
||||
TTL: 5*60*60,
|
||||
};
|
||||
|
||||
+120
-46
@@ -1,72 +1,146 @@
|
||||
const R = require('ramda');
|
||||
const Namecheap = require('@rqt/namecheap');
|
||||
const { NC_DOMAIN, NC_USER, NC_API_KEY, ENV, IP_ADDRESS } = require('../utils/constants');
|
||||
const { cpanel } = require('./lib/cpanel');
|
||||
const { DOMAIN_DOMAIN, IS_TEST } = require('./constants');
|
||||
|
||||
const IS_SANDBOX = ENV === 'sandbox';
|
||||
const log = IS_TEST ? () => {} : console.log;
|
||||
|
||||
const getDomainService = ({ nc }) => {
|
||||
const recordToRedirection = ({ name, address }) => ({
|
||||
domain: `${name}.${DOMAIN_DOMAIN}`,
|
||||
redirect: address,
|
||||
type: 'permanent',
|
||||
redirect_wildcard: 1,
|
||||
redirect_www: 0,
|
||||
});
|
||||
const recordToZone = ({ name, type, address, ...rec }) => ({
|
||||
...rec, //line
|
||||
name,
|
||||
type,
|
||||
address,
|
||||
...(type === 'CNAME' ? { cname: address } : {}),
|
||||
});
|
||||
|
||||
const cleanName = name => `${name}`.replace(new RegExp(`\.${DOMAIN_DOMAIN}\.?$`), '').toLowerCase();
|
||||
|
||||
const zoneToRecord = ({ name, type, cname, address, record, ...host }) => ({
|
||||
...host,
|
||||
name: cleanName(name),
|
||||
type: `${type}`,
|
||||
address: `${cname || address || record}`.replace(/\.$/g, '').toLowerCase(),
|
||||
});
|
||||
const redirectionToRecord = ({ domain, destination }) => ({
|
||||
name: cleanName(domain),
|
||||
type: 'URL',
|
||||
address: `${destination}`.replace(/\/$/g, ''),
|
||||
});
|
||||
|
||||
const getHostKey = host => `${host.name}##${host.type}`;
|
||||
|
||||
const toHostMap = hosts => hosts.reduce((acc, host) => {
|
||||
const key = getHostKey(host);
|
||||
return { ...acc, [key]: [ ...(acc[key] || []), host ] };
|
||||
}, {});
|
||||
|
||||
const diffRecords = (oldRecords, newRecords) => {
|
||||
const remoteHostMap = toHostMap(oldRecords);
|
||||
const localHostMap = toHostMap(newRecords);
|
||||
|
||||
return R.toPairs(localHostMap).reduce((acc, [key, local]) => {
|
||||
const remote = remoteHostMap[key];
|
||||
|
||||
if (remote) {
|
||||
let adds = [];
|
||||
let edits = [];
|
||||
|
||||
const diff = R.differenceWith((a, b) => a.address === b.address, local, remote);
|
||||
|
||||
if (diff.length === local.length - remote.length) {
|
||||
adds = diff;
|
||||
} else {
|
||||
edits = diff;
|
||||
}
|
||||
|
||||
return { ...acc, add: acc.add.concat(adds), edit: acc.edit.concat(edits) };
|
||||
}
|
||||
|
||||
return { ...acc, add: acc.add.concat(local) };
|
||||
}, { add: [], edit: [] });
|
||||
};
|
||||
|
||||
const lazyTask = fn => data => () => fn(data);
|
||||
|
||||
const batchLazyTasks = count => tasks => tasks.reduce((batches, task) => {
|
||||
if (batches.length === 0) return [[task]];
|
||||
|
||||
const full = R.init(batches);
|
||||
const last = R.last(batches);
|
||||
|
||||
if (last.length >= count) return [...batches, [task]];
|
||||
return [...full, [...last, task]];
|
||||
}, []);
|
||||
|
||||
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 results = values.map(R.pathOr({}, ['cpanelresult', 'data', 0]));
|
||||
const failed = results.filter(x => (x.result || {}).status != 1);
|
||||
|
||||
log(`${values.length - failed.length}/${values.length}`);
|
||||
failed.length && log(failed);
|
||||
|
||||
return null;
|
||||
});
|
||||
}, Promise.resolve());
|
||||
|
||||
const getDomainService = ({ cpanel }) => {
|
||||
let hostList = [];
|
||||
|
||||
const fetchZoneRecords = () => cpanel.zone.fetch().then(R.map(zoneToRecord));
|
||||
const fetchRedirections = () => cpanel.redirection.fetch().then(R.map(redirectionToRecord));
|
||||
|
||||
const addZoneRecord = lazyTask(R.compose(cpanel.zone.add, recordToZone));
|
||||
const editZoneRecord = lazyTask(R.compose(cpanel.zone.edit, recordToZone));
|
||||
const addRedirection = lazyTask(R.compose(cpanel.redirection.add, recordToRedirection));
|
||||
const editRedirection = lazyTask(R.compose(cpanel.redirection.edit, recordToRedirection));
|
||||
|
||||
const getHosts = async () => {
|
||||
if (hostList.length) return hostList;
|
||||
|
||||
const list = await nc.dns.getHosts(NC_DOMAIN)
|
||||
.then(R.propOr([], 'hosts'))
|
||||
.then(R.map(host => R.omit(['Name', 'Type'], {
|
||||
...host,
|
||||
HostName: host.Name,
|
||||
RecordType: host.Type,
|
||||
Address: `${host.Address}`.replace(/\.$/g, ''),
|
||||
})));
|
||||
const list = await Promise.all([fetchZoneRecords(), fetchRedirections()]).then(R.flatten);
|
||||
|
||||
hostList = list;
|
||||
return list;
|
||||
};
|
||||
|
||||
const setHosts = hosts => nc.dns.setHosts(NC_DOMAIN, hosts);
|
||||
const BATCH_SIZE = 1;
|
||||
|
||||
const getHostKey = host => `${host.HostName}--${host.RecordType}`;
|
||||
const toHostMap = hosts => hosts.reduce((acc, host) => {
|
||||
const key = getHostKey(host);
|
||||
return { ...acc, [key]: [ ...(acc[key] || []), host ] };
|
||||
}, {});
|
||||
const addRecords = R.compose(batchLazyTasks(BATCH_SIZE), R.filter(Boolean), R.map(R.cond([
|
||||
[ R.propEq('name', 'www'), () => null ],
|
||||
[ R.propEq('type', 'URL'), addRedirection ],
|
||||
[ R.T, addZoneRecord ],
|
||||
])));
|
||||
const editRecords = R.compose(batchLazyTasks(BATCH_SIZE), R.map(R.cond([
|
||||
[ R.propEq('type', 'URL'), editRedirection ],
|
||||
[ R.T, editZoneRecord ],
|
||||
])));
|
||||
|
||||
const updateHosts = async hosts => {
|
||||
const hostList = await getHosts();
|
||||
const remoteHostMap = toHostMap(hostList);
|
||||
const localHostMap = toHostMap(hosts);
|
||||
const remoteHostList = await getHosts();
|
||||
const { add, edit } = diffRecords(remoteHostList, hosts);
|
||||
|
||||
const newHostList = R.toPairs(localHostMap).reduce((acc, [key, local]) => {
|
||||
const remote = remoteHostMap[key];
|
||||
|
||||
if (remote) {
|
||||
return acc.concat(local.map((localItem, index) => R.merge(remote[index], localItem)));
|
||||
}
|
||||
|
||||
return [...acc, ...local];
|
||||
}, []);
|
||||
|
||||
return setHosts(newHostList);
|
||||
await executeBatch(addRecords(add).concat(editRecords(edit)));
|
||||
return { additions: add.length, edits: edit.length };
|
||||
};
|
||||
|
||||
return { getHosts, setHosts, updateHosts };
|
||||
return { getHosts, updateHosts };
|
||||
};
|
||||
|
||||
if (!NC_API_KEY) {
|
||||
console.error('NC_API_KEY cannot be empty');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const nc = new Namecheap({
|
||||
user: NC_USER,
|
||||
key: NC_API_KEY,
|
||||
ip: IP_ADDRESS,
|
||||
sandbox: IS_SANDBOX,
|
||||
});
|
||||
|
||||
const domainService = getDomainService({ nc });
|
||||
const domainService = getDomainService({ cpanel });
|
||||
|
||||
module.exports = {
|
||||
getDomainService,
|
||||
domainService,
|
||||
diffRecords,
|
||||
};
|
||||
|
||||
+2
-5
@@ -1,9 +1,7 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const R = require('ramda');
|
||||
const { VALID_RECORD_TYPES } = require('./constants');
|
||||
|
||||
const DOMAINS_PATH = path.resolve('domains');
|
||||
const { VALID_RECORD_TYPES, DOMAINS_PATH } = require('./constants');
|
||||
|
||||
const log = m => x => console.log(m, x) || x;
|
||||
|
||||
@@ -38,7 +36,7 @@ const validateDomainData = validate({
|
||||
R.equals('@'),
|
||||
R.allPass([
|
||||
R.compose(between(2, 100), R.length),
|
||||
str => str && str.match(/^[A-Za-z0-9\-]+$/ig),
|
||||
str => str && str.match(/^[a-z0-9\-]+$/ig),
|
||||
])
|
||||
]),
|
||||
},
|
||||
@@ -62,7 +60,6 @@ const validateDomainData = validate({
|
||||
R.compose(R.isEmpty, R.flip(R.difference)(VALID_RECORD_TYPES), R.keys),
|
||||
R.cond([
|
||||
[R.prop('CNAME'), validateNameRecord('CNAME')],
|
||||
[R.prop('ALIAS'), validateNameRecord('ALIAS')],
|
||||
[R.prop('A'), R.propSatisfies(R.is(Array), 'A')],
|
||||
[R.prop('URL'), R.propSatisfies(R.is(String), 'URL')],
|
||||
[R.T, R.T],
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
const R = require('ramda');
|
||||
const fetch = require('node-fetch');
|
||||
const qs = require('qs');
|
||||
const { DOMAIN_API_HOST, DOMAIN_API_PORT, DOMAIN_USER, DOMAIN_API_KEY, DOMAIN_DOMAIN } = require('../constants');
|
||||
|
||||
const CpanelClient = (options) => {
|
||||
// TODO: Make defaultQuery functional
|
||||
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 }
|
||||
// -> { cpanelresult: { data[{ class, ttl, name, line, Line, cname, type, record }] } }
|
||||
fetch: R.compose(
|
||||
p => p.then(R.pathOr([], ['cpanelresult', 'data'])),
|
||||
api2('ZoneEdit', 'fetchzone_records', { customonly: 1, domain: options.domain })
|
||||
),
|
||||
|
||||
// { name, type(A|CNAME), cname, address, ttl }
|
||||
// -> {}
|
||||
add: api2('ZoneEdit', 'add_zone_record', { domain: options.domain }),
|
||||
|
||||
// { name, type(A|CNAME), cname, address, ttl }
|
||||
// -> {}
|
||||
edit: api2('ZoneEdit', 'edit_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'),
|
||||
edit: uapi('Mime', 'add_redirect'), // NOTE: adding new updates exisiting
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
if (!DOMAIN_API_KEY) {
|
||||
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 },
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
cpanel,
|
||||
CpanelClient,
|
||||
};
|
||||
|
||||
@@ -458,11 +458,6 @@
|
||||
"@types/yargs" "^15.0.0"
|
||||
chalk "^4.0.0"
|
||||
|
||||
"@rqt/namecheap@^2.4.2":
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/@rqt/namecheap/-/namecheap-2.4.2.tgz#8537eea6efbe7ac4fd449e3aee5f2c1d80986fcf"
|
||||
integrity sha512-RfrK7ywOraz0nR/BlQt8fJQDcOfI9cLSTpa6l9JS9ER0HJ6t1FQ8Pxmplj/lVQv8rkczuVADdZVC3HlL98Q31w==
|
||||
|
||||
"@sinonjs/commons@^1.7.0":
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217"
|
||||
@@ -2468,6 +2463,11 @@ nice-try@^1.0.4:
|
||||
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
||||
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
|
||||
|
||||
node-fetch@^2.6.1:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
||||
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
||||
|
||||
node-int64@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
|
||||
|
||||
Reference in New Issue
Block a user