Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ Below are the prompts (see the [Notes](#notes) below for an important advisory r
```javascript
{
name: 'username',
message: 'QuickBase username:'
message: 'QuickBase username (leave blank to use the QUICKBASE_CLI_USERNAME environment variable):'
},
{
name: 'password',
message: 'QuickBase password (Leave blank to use the QUICKBASE_CLI_PASSWORD env variable):'
message: 'QuickBase password (leave blank to use the QUICKBASE_CLI_PASSWORD environment variable):'
},
{
name: 'dbid',
Expand All @@ -47,11 +47,19 @@ Below are the prompts (see the [Notes](#notes) below for an important advisory r
},
{
name: 'appToken',
message: 'QuickBase application token (if applicable):'
message: 'QuickBase application token (if applicable) (leave blank to use the QUICKBASE_CLI_APPTOKEN environment variable):'
},
{
name: 'userToken',
message: 'QuickBase user token (if applicable) (leave blank to use the QUICKBASE_CLI_USERTOKEN environment variable):'
},
{
name: 'appName',
message: 'Code page prefix (leave blank to disable prefixing uploaded pages):'
},
{
name: 'ticketExpiryHours',
message: 'Ticket expiry period in hours (default is 1):'
}
```

Expand Down Expand Up @@ -92,6 +100,8 @@ For now this is only a wrapper around `git clone`. After you pull down a repo yo

* Instead of exposing your password for the `quickbase-cli.config.js` file you can rely on an environment variable called `QUICKBASE_CLI_PASSWORD`. If you have that variable defined and leave the `password` empty when prompted the `qb deploy` command will use it instead. Always practice safe passwords.

* The same can also be done with username (using `QUICKBASE_CLI_USERNAME`), user token (using `QUICKBASE_CLI_USERTOKEN`) and/or app token (using `QUICKBASE_CLI_APPTOKEN`).

* ~~Moves are being made to add cool shit like a build process, global defaults, awesome starter templates, and pulling down existing code files from QuickBase. They're not out yet, so for now you're on your own.~~

* I no longer work with QuickBase applications, so the cool shit I had planned won't happen unless someone submits some dope pull requests.
9 changes: 8 additions & 1 deletion bin/qb-deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,14 @@ if (program.watch) {

async function qbDeploy(source) {
console.log('Uploading files to QuickBase...');


try {
await api.authenticateIfNeeded();
} catch(e) {
console.error(e);
return;
}

const stats = await fs.statSync(source);
const isFile = stats.isFile();

Expand Down
15 changes: 13 additions & 2 deletions bin/qb-init.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const QUESTIONS = [
{
type: 'input',
name: 'username',
message: 'QuickBase username:'
message: 'QuickBase username (leave blank to use the QUICKBASE_CLI_USERNAME environment variable):'
},
{
type: 'password',
Expand All @@ -29,13 +29,24 @@ const QUESTIONS = [
{
type: 'input',
name: 'appToken',
message: 'QuickBase application token (if applicable):'
message: 'QuickBase application token (if applicable) (leave blank to use the QUICKBASE_CLI_APPTOKEN environment variable):'
},
{
type: 'input',
name: 'userToken',
message: 'QuickBase user token (if applicable) (leave blank to use the QUICKBASE_CLI_USERTOKEN environment variable):'
},
{
type: 'input',
name: 'appName',
message:
'Code page prefix (leave blank to disable prefixing uploaded pages):'
},
{
type: 'input',
name: 'authenticate_hours',
message:
'Authentication expiry period in hours (default is 1):'
}
];

Expand Down
4 changes: 3 additions & 1 deletion demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
</head>
<body>
<h1>Hello, world</h1>

<p>This is a page, that should be deployed using quickbase-cli to quickbase, with styling and scripts properly referenced.</p>
<p class="check_css">If this paragraph is bold, CSS files are properly referenced.</p>
<p class="check_js">If this paragraph is bold, JS files are properly referenced.</p>
<script src="static/bundle.js"></script>
</body>
</html>
3 changes: 3 additions & 0 deletions demo/static/bundle.js
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
console.log('Hello from bundle.js');
var elements = document.getElementsByClassName('check_js');
var checkJsElement = elements[0];
checkJsElement.style.fontWeight = 'bold';
4 changes: 4 additions & 0 deletions demo/static/main.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
body {
font-family: sans-serif;
}

.check_css {
font-weight: bold;
}
149 changes: 121 additions & 28 deletions lib/api.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,40 @@
const https = require('https');
const URL = require('url');


class ApiClient {

constructor(config) {
this.config = config;

const password = process.env.QUICKBASE_CLI_PASSWORD;
this.config.password = this.config.password || password;
this.config.password = this.config.password || process.env.QUICKBASE_CLI_PASSWORD;
this.config.username = this.config.username || process.env.QUICKBASE_CLI_USERNAME;
this.config.appToken = this.config.appToken || process.env.QUICKBASE_CLI_APPTOKEN;
this.config.userToken = this.config.userToken || process.env.QUICKBASE_CLI_USERTOKEN;
this.authData = null;
}


uploadPage(pageName, pageText) {
const xmlData = `
<pagebody>${this.handleXMLChars(pageText)}</pagebody>
<pagetype>1</pagetype>
<pagename>${pageName}</pagename>
`;
<pagebody>${this._handleXMLChars(pageText)}</pagebody>
<pagetype>1</pagetype>
<pagename>${pageName}</pagename>
`;

return new Promise((resolve, reject) => {

return this.sendQbRequest('API_AddReplaceDBPage', xmlData);
this.sendQbRequest('API_AddReplaceDBPage', xmlData).then((response) => {
resolve(response)
}).catch((errorDesc, err) => {
reject(errorDesc, err)
});

});
}


// Private-ish
handleXMLChars(string) {
_handleXMLChars(string) {
if (!string) {
return;
}
Expand All @@ -41,31 +55,109 @@ class ApiClient {
});
}

sendQbRequest(action, data, mainAPICall) {

authenticateIfNeeded() {

return new Promise((resolve, reject) => {

//Decide here which type of authentication should be done
if (this.config.userToken) {
//Use usertoken
this.authData = `<usertoken>${this.config.userToken}</usertoken>`;
resolve()
} else if (this.config.username && this.config.password) {
//regenerate ticket first, then Use ticket

const dbid = 'main';
const action = "API_Authenticate";

const url = URL.parse(
`https://${this.config.realm}.quickbase.com/db/${dbid}?a=${action}`
);

const options = {
hostname: url.hostname,
path: url.pathname + url.search,
method: 'POST',
headers: {
'Content-Type': 'application/xml',
'QUICKBASE-ACTION': action
}
};

const postData = `
<qdbapi>
<username>${this.config.username}</username>
<password>${this.config.password}</password>
<hours>${this.config.authenticate_hours}</hours>
</qdbapi>`;

const req = https.request(options, res => {
let response = '';

res.setEncoding('utf8');
res.on('data', chunk => (response += chunk));
res.on('end', () => {
const errCode = +response.match(/<errcode>(.*)<\/errcode>/)[1];

if (errCode != 0) {
const errtext = response.match(/<errtext>(.*)<\/errtext>/)[1];
reject(errtext);
} else {
const ticket = response.match(/<ticket>(.*)<\/ticket>/)[1];

this.authData = `<ticket>${ticket}</ticket>`;
if (this.config.appToken) {
this.authData += `<apptoken>${this.config.appToken}</apptoken>`;
}

//Suggest to use ticket now, just validated
resolve();
}
});
});

req.on('error', err => reject('Could not send Authentication request', err));
req.write(postData);
req.end();

} else {
//Error: not enough auth credentials
reject("There are not enough authentication credentials in the config or environment. Please setup a valid username and password.")
}
});
};


async sendQbRequest(action, data, mainAPICall) {

const dbid = mainAPICall ? 'main' : this.config.dbid;
const url = URL.parse(
`https://${this.config.realm}.quickbase.com/db/${dbid}?a=${action}`
);
const postData = `
<qdbapi>
<username>${this.config.username}</username>
<password>${this.config.password}</password>
<hours>1</hours>
<apptoken>${this.config.appToken}</apptoken>
${data}
</qdbapi>
`;
const options = {
hostname: url.hostname,
path: url.pathname + url.search,
method: 'POST',
headers: {
'Content-Type': 'application/xml',
'QUICKBASE-ACTION': action
}
};

if (!this.authData) {
reject("You must call `authenticateIfNeeded()` before calling `sendQbRequest`");
return;
}

return new Promise((resolve, reject) => {

const postData = `
<qdbapi>
${this.authData}
${data}
</qdbapi>`;
const options = {
hostname: url.hostname,
path: url.pathname + url.search,
method: 'POST',
headers: {
'Content-Type': 'application/xml',
'QUICKBASE-ACTION': action
}
};

const req = https.request(options, res => {
let response = '';
res.setEncoding('utf8');
Expand All @@ -88,6 +180,7 @@ class ApiClient {
req.end();
});
}

}

module.exports = ApiClient;
24 changes: 23 additions & 1 deletion lib/generate-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ let template = `module.exports = {
realm: "{{realm}}",
dbid: "{{dbid}}",
appToken: "{{appToken}}",
appName: "{{appName}}"
userToken: "{{userToken}}",
appName: "{{appName}}",
authenticate_hours: "{{authenticate_hours}}",
}`;

const generateConfig = answers => {
Expand All @@ -17,6 +19,26 @@ const generateConfig = answers => {
/password: \"\{\{password\}\}\",/,
`//leave commented out to use QUICKBASE_CLI_PASSWORD env variable\n\t//password:`
);
} else if (i == 'username' && answers[i] == '') {
template = template.replace(
/username: \"\{\{username\}\}\",/,
`//leave commented out to use QUICKBASE_CLI_USERNAME env variable\n\t//username:`
);
} else if (i == 'appToken' && answers[i] == '') {
template = template.replace(
/appToken: \"\{\{appToken\}\}\",/,
`//leave commented out to use QUICKBASE_CLI_APPTOKEN env variable\n\t//appToken:`
);
} else if (i == 'userToken' && answers[i] == '') {
template = template.replace(
/userToken: \"\{\{userToken\}\}\",/,
`//leave commented out to use QUICKBASE_CLI_USERTOKEN env variable\n\t//userToken:`
);
} else if (i == 'authenticate_hours' && answers[i] == '') {
const authenticate_hours = parseInt(answers[i]) || 1
template = template.replace(
/authenticate_hours: \"\{\{authenticate_hours\}\}\",/, 'authenticate_hours: "'+authenticate_hours+'"'
);
} else {
template = template.replace(new RegExp(`{{${i}}}`, 'g'), answers[i]);
}
Expand Down