Compare commits
183 Commits
toggle_key
...
master
Author | SHA1 | Date |
---|---|---|
|
be5c886c97 | 3 years ago |
|
787d839086 | 3 years ago |
|
9e921d5909 | 3 years ago |
|
68f6fe2b96 | 3 years ago |
|
7286385833 | 3 years ago |
|
3dcc43578b | 3 years ago |
|
1ea6b6e99d | 3 years ago |
|
7c613bc788 | 3 years ago |
|
cb4809195b | 3 years ago |
|
00d84614c2 | 3 years ago |
|
52e7cef7ef | 3 years ago |
|
fbff1bc201 | 3 years ago |
|
7af15cc32d | 4 years ago |
|
7f397ce753 | 4 years ago |
|
8f8b039f65 | 4 years ago |
|
eeaf2d7b18 | 4 years ago |
|
db0b7d6444 | 4 years ago |
|
db6e7603f9 | 4 years ago |
|
ad5d7549d7 | 4 years ago |
|
5d2965ffc5 | 5 years ago |
|
f255928af7 | 5 years ago |
|
a108dbadc5 | 5 years ago |
|
c409aca080 | 5 years ago |
|
219424550b | 5 years ago |
|
f147acb51c | 5 years ago |
|
9a692ed652 | 5 years ago |
|
3a17c86a0f | 5 years ago |
|
677a22987a | 5 years ago |
|
89d912c6ff | 5 years ago |
|
4cac6713ef | 5 years ago |
|
f3b0de745b | 5 years ago |
|
cc8a99752f | 5 years ago |
|
6853d077e7 | 5 years ago |
|
80a2b6f0dd | 5 years ago |
|
4f68b3d7d6 | 5 years ago |
|
ef0ca40533 | 5 years ago |
|
f372ef18de | 5 years ago |
|
181a3a2bfa | 5 years ago |
|
61d08afb3b | 5 years ago |
|
1ba025328d | 5 years ago |
|
a79fb39f54 | 5 years ago |
|
3a72d74537 | 5 years ago |
|
e9ae74b7a9 | 5 years ago |
|
c305e9a83d | 5 years ago |
|
16bce4c83d | 5 years ago |
|
661997cd73 | 5 years ago |
|
159f989d08 | 5 years ago |
|
139df62ec4 | 5 years ago |
|
bae6387bb7 | 5 years ago |
|
bb7b9571a7 | 5 years ago |
|
a4dc29fb2b | 5 years ago |
|
342f56ce1a | 5 years ago |
|
05ecc90764 | 5 years ago |
|
69cf505a90 | 5 years ago |
|
9f41993566 | 5 years ago |
|
5c9311fb85 | 5 years ago |
|
5a8d52a5e3 | 5 years ago |
|
0f145b4444 | 5 years ago |
|
aef4bb5edb | 5 years ago |
|
36c854ef1b | 5 years ago |
|
edd428ff37 | 5 years ago |
|
0612ba001e | 5 years ago |
|
064680003d | 5 years ago |
|
655f2af45a | 5 years ago |
|
ce03749c2f | 5 years ago |
|
f6084b4339 | 5 years ago |
|
9b0a5ff0a3 | 5 years ago |
|
1fff48568f | 6 years ago |
|
b4c666fbcf | 6 years ago |
|
b866c33c93 | 6 years ago |
|
035cf0e91e | 6 years ago |
|
f3838ab4a8 | 7 years ago |
|
bf2b1c957a | 7 years ago |
|
86bbc1899d | 7 years ago |
|
d41d7491d4 | 7 years ago |
|
5fb43eb67c | 7 years ago |
|
1eeef4ede4 | 7 years ago |
|
ebc749c5e0 | 7 years ago |
|
b0bbb72f35 | 7 years ago |
|
2213c3874a | 7 years ago |
|
6ebd72a86c | 7 years ago |
|
b6814a1445 | 7 years ago |
|
e3d18efdc6 | 7 years ago |
|
869fb65738 | 7 years ago |
|
56b939124e | 7 years ago |
|
ee1c1c0856 | 7 years ago |
|
b087ac8dd1 | 7 years ago |
|
d922667f56 | 7 years ago |
|
5f6fefa7a6 | 7 years ago |
|
faa7e679ca | 7 years ago |
|
cd3bf26dbe | 7 years ago |
|
830dc1bc43 | 7 years ago |
|
dc0f151a7f | 7 years ago |
|
7f625e22f7 | 7 years ago |
|
528b7b07a8 | 7 years ago |
|
2b81e67ce7 | 7 years ago |
|
827e7b51b5 | 7 years ago |
|
16d529e935 | 7 years ago |
|
ad7702aaf4 | 7 years ago |
|
f5fbc8d19e | 7 years ago |
|
0a8923bf12 | 7 years ago |
|
4d572a2ec0 | 7 years ago |
|
d9a53d3e6e | 7 years ago |
|
8da37ea5de | 8 years ago |
|
ff0fccd6c2 | 8 years ago |
|
63c4576633 | 8 years ago |
|
b31d143bcd | 8 years ago |
|
0d8aec8d61 | 8 years ago |
|
1f9fdd205d | 8 years ago |
|
cdd0cf3739 | 8 years ago |
|
ba5c6b8d16 | 8 years ago |
|
cfef588283 | 8 years ago |
|
318c5f7ba6 | 8 years ago |
|
ee03e7cd78 | 8 years ago |
|
3b6934e348 | 8 years ago |
|
f161cc33b4 | 8 years ago |
|
40f1f2588e | 8 years ago |
|
e4e025f67e | 8 years ago |
|
e12805a8aa | 8 years ago |
|
e76c845f16 | 8 years ago |
|
072418695e | 8 years ago |
|
584b66bc66 | 8 years ago |
|
f8db455f74 | 8 years ago |
|
f19c5d1049 | 8 years ago |
|
c5b859ec98 | 8 years ago |
|
2ee93a7409 | 8 years ago |
|
bf1dbb68b8 | 8 years ago |
|
cf28e23d8e | 8 years ago |
|
5939dec185 | 8 years ago |
|
3ed1d775ac | 8 years ago |
|
87b1c76aaf | 8 years ago |
|
4599203bdf | 8 years ago |
|
d66bc9a6c4 | 8 years ago |
|
80f0618736 | 8 years ago |
|
ac2bceefbb | 8 years ago |
|
dbf4f6b5dd | 8 years ago |
|
8e9205cecc | 8 years ago |
|
e54a860172 | 8 years ago |
|
5a8697cdd8 | 8 years ago |
|
091ea973a8 | 8 years ago |
|
939b7221ab | 8 years ago |
|
934aaf7f51 | 8 years ago |
|
930e21ccb7 | 9 years ago |
|
eb5c8eef6a | 9 years ago |
|
03dd611a86 | 9 years ago |
|
f24376b192 | 9 years ago |
|
eea359d0ec | 9 years ago |
|
af9a71549b | 9 years ago |
|
3178676fba | 9 years ago |
|
3bdfab8219 | 9 years ago |
|
a3a24d9765 | 9 years ago |
|
8afb53e77e | 9 years ago |
|
d6d9cf40f9 | 9 years ago |
|
1010a142e2 | 9 years ago |
|
d3db5e2a5d | 9 years ago |
|
0209375865 | 9 years ago |
|
00a9d9c312 | 9 years ago |
|
6835eef468 | 9 years ago |
|
4626fd9c8d | 9 years ago |
|
fbb6e63c37 | 9 years ago |
|
84c909a5db | 9 years ago |
|
45e19bc7cc | 10 years ago |
|
233bc6ff16 | 10 years ago |
|
360b325ced | 11 years ago |
|
e93f98112b | 11 years ago |
|
05cb051bc8 | 11 years ago |
|
031cdd738a | 11 years ago |
|
c92ab077c0 | 11 years ago |
|
6c31389327 | 11 years ago |
|
a8d4f3c300 | 11 years ago |
|
ab029eae2f | 11 years ago |
|
447d0aae76 | 11 years ago |
|
4870158430 | 11 years ago |
|
0471b059a0 | 11 years ago |
|
5bbe50b481 | 11 years ago |
|
bda2749879 | 11 years ago |
|
028aa96b13 | 11 years ago |
|
2deda5b68a | 11 years ago |
|
ee7098457e | 11 years ago |
|
7a08960414 | 12 years ago |
|
89909747f1 | 12 years ago |
|
202e695e07 | 12 years ago |
|
48e8e79659 | 12 years ago |
38 changed files with 3006 additions and 310 deletions
@ -0,0 +1,8 @@ |
||||
Dockerfile |
||||
.git |
||||
npm-debug.log |
||||
node_modules |
||||
*.swp |
||||
*.swo |
||||
data |
||||
*.DS_Store |
@ -0,0 +1,2 @@ |
||||
**/*.min.js |
||||
config.js |
@ -0,0 +1,25 @@ |
||||
{ |
||||
"env": { |
||||
"es6": true, |
||||
"node": true |
||||
}, |
||||
"extends": "eslint:recommended", |
||||
"rules": { |
||||
"indent": [ |
||||
"error", |
||||
2 |
||||
], |
||||
"linebreak-style": [ |
||||
"error", |
||||
"unix" |
||||
], |
||||
"quotes": [ |
||||
"error", |
||||
"single" |
||||
], |
||||
"semi": [ |
||||
"error", |
||||
"always" |
||||
] |
||||
} |
||||
} |
@ -0,0 +1 @@ |
||||
* @toptal/site-acquisition-eng |
@ -0,0 +1,30 @@ |
||||
name: Close inactive issues and PRs |
||||
on: |
||||
workflow_dispatch: |
||||
schedule: |
||||
- cron: "30 1 * * *" |
||||
|
||||
jobs: |
||||
close-stale: |
||||
runs-on: ubuntu-latest |
||||
permissions: |
||||
issues: write |
||||
pull-requests: write |
||||
steps: |
||||
- uses: actions/stale@v3 |
||||
with: |
||||
days-before-stale: 30 |
||||
days-before-close: 14 |
||||
stale-issue-label: "stale" |
||||
stale-pr-label: "stale" |
||||
|
||||
exempt-issue-labels: backlog,triage,nostale |
||||
exempt-pr-labels: backlog,triage,nostale |
||||
|
||||
stale-pr-message: "This PR is stale because it has been open for 30 days with no activity." |
||||
close-pr-message: "This PR was closed because it has been inactive for 14 days since being marked as stale." |
||||
|
||||
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." |
||||
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." |
||||
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }} |
@ -0,0 +1,68 @@ |
||||
FROM node:14.8.0-stretch |
||||
|
||||
RUN mkdir -p /usr/src/app && \ |
||||
chown node:node /usr/src/app |
||||
|
||||
USER node:node |
||||
|
||||
WORKDIR /usr/src/app |
||||
|
||||
COPY --chown=node:node . . |
||||
|
||||
RUN npm install && \ |
||||
npm install redis@0.8.1 && \ |
||||
npm install pg@4.1.1 && \ |
||||
npm install memcached@2.2.2 && \ |
||||
npm install aws-sdk@2.738.0 && \ |
||||
npm install rethinkdbdash@2.3.31 |
||||
|
||||
ENV STORAGE_TYPE=memcached \ |
||||
STORAGE_HOST=127.0.0.1 \ |
||||
STORAGE_PORT=11211\ |
||||
STORAGE_EXPIRE_SECONDS=2592000\ |
||||
STORAGE_DB=2 \ |
||||
STORAGE_AWS_BUCKET= \ |
||||
STORAGE_AWS_REGION= \ |
||||
STORAGE_USENAME= \ |
||||
STORAGE_PASSWORD= \ |
||||
STORAGE_FILEPATH= |
||||
|
||||
ENV LOGGING_LEVEL=verbose \ |
||||
LOGGING_TYPE=Console \ |
||||
LOGGING_COLORIZE=true |
||||
|
||||
ENV HOST=0.0.0.0\ |
||||
PORT=7777\ |
||||
KEY_LENGTH=10\ |
||||
MAX_LENGTH=400000\ |
||||
STATIC_MAX_AGE=86400\ |
||||
RECOMPRESS_STATIC_ASSETS=true |
||||
|
||||
ENV KEYGENERATOR_TYPE=phonetic \ |
||||
KEYGENERATOR_KEYSPACE= |
||||
|
||||
ENV RATELIMITS_NORMAL_TOTAL_REQUESTS=500\ |
||||
RATELIMITS_NORMAL_EVERY_MILLISECONDS=60000 \ |
||||
RATELIMITS_WHITELIST_TOTAL_REQUESTS= \ |
||||
RATELIMITS_WHITELIST_EVERY_MILLISECONDS= \ |
||||
# comma separated list for the whitelisted \ |
||||
RATELIMITS_WHITELIST=example1.whitelist,example2.whitelist \ |
||||
\ |
||||
RATELIMITS_BLACKLIST_TOTAL_REQUESTS= \ |
||||
RATELIMITS_BLACKLIST_EVERY_MILLISECONDS= \ |
||||
# comma separated list for the blacklisted \ |
||||
RATELIMITS_BLACKLIST=example1.blacklist,example2.blacklist |
||||
ENV DOCUMENTS=about=./about.md |
||||
|
||||
EXPOSE ${PORT} |
||||
STOPSIGNAL SIGINT |
||||
ENTRYPOINT [ "bash", "docker-entrypoint.sh" ] |
||||
|
||||
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s \ |
||||
--retries=3 CMD [ "sh", "-c", "echo -n 'curl localhost:7777... '; \ |
||||
(\ |
||||
curl -sf localhost:7777 > /dev/null\ |
||||
) && echo OK || (\ |
||||
echo Fail && exit 2\ |
||||
)"] |
||||
CMD ["npm", "start"] |
@ -0,0 +1,12 @@ |
||||
version: '3.0' |
||||
services: |
||||
haste-server: |
||||
build: . |
||||
environment: |
||||
- STORAGE_TYPE=memcached |
||||
- STORAGE_HOST=memcached |
||||
- STORAGE_PORT=11211 |
||||
ports: |
||||
- 7777:7777 |
||||
memcached: |
||||
image: memcached:latest |
@ -0,0 +1,108 @@ |
||||
const { |
||||
HOST, |
||||
PORT, |
||||
KEY_LENGTH, |
||||
MAX_LENGTH, |
||||
STATIC_MAX_AGE, |
||||
RECOMPRESS_STATIC_ASSETS, |
||||
STORAGE_TYPE, |
||||
STORAGE_HOST, |
||||
STORAGE_PORT, |
||||
STORAGE_EXPIRE_SECONDS, |
||||
STORAGE_DB, |
||||
STORAGE_AWS_BUCKET, |
||||
STORAGE_AWS_REGION, |
||||
STORAGE_PASSWORD, |
||||
STORAGE_USERNAME, |
||||
STORAGE_FILEPATH, |
||||
LOGGING_LEVEL, |
||||
LOGGING_TYPE, |
||||
LOGGING_COLORIZE, |
||||
KEYGENERATOR_TYPE, |
||||
KEY_GENERATOR_KEYSPACE, |
||||
RATE_LIMITS_NORMAL_TOTAL_REQUESTS, |
||||
RATE_LIMITS_NORMAL_EVERY_MILLISECONDS, |
||||
RATE_LIMITS_WHITELIST_TOTAL_REQUESTS, |
||||
RATE_LIMITS_WHITELIST_EVERY_MILLISECONDS, |
||||
RATE_LIMITS_WHITELIST, |
||||
RATE_LIMITS_BLACKLIST_TOTAL_REQUESTS, |
||||
RATE_LIMITS_BLACKLIST_EVERY_MILLISECONDS, |
||||
RATE_LIMITS_BLACKLIST, |
||||
DOCUMENTS, |
||||
} = process.env; |
||||
|
||||
const config = { |
||||
host: HOST, |
||||
port: Number(PORT), |
||||
|
||||
keyLength: Number(KEY_LENGTH), |
||||
|
||||
maxLength: Number(MAX_LENGTH), |
||||
|
||||
staticMaxAge: Number(STATIC_MAX_AGE), |
||||
|
||||
recompressStaticAssets: RECOMPRESS_STATIC_ASSETS, |
||||
|
||||
logging: [ |
||||
{ |
||||
level: LOGGING_LEVEL, |
||||
type: LOGGING_TYPE, |
||||
colorize: LOGGING_COLORIZE, |
||||
}, |
||||
], |
||||
|
||||
keyGenerator: { |
||||
type: KEYGENERATOR_TYPE, |
||||
keyspace: KEY_GENERATOR_KEYSPACE, |
||||
}, |
||||
|
||||
rateLimits: { |
||||
whitelist: RATE_LIMITS_WHITELIST ? RATE_LIMITS_WHITELIST.split(",") : [], |
||||
blacklist: RATE_LIMITS_BLACKLIST ? RATE_LIMITS_BLACKLIST.split(",") : [], |
||||
categories: { |
||||
normal: { |
||||
totalRequests: RATE_LIMITS_NORMAL_TOTAL_REQUESTS, |
||||
every: RATE_LIMITS_NORMAL_EVERY_MILLISECONDS, |
||||
}, |
||||
whitelist: |
||||
RATE_LIMITS_WHITELIST_EVERY_MILLISECONDS || |
||||
RATE_LIMITS_WHITELIST_TOTAL_REQUESTS |
||||
? { |
||||
totalRequests: RATE_LIMITS_WHITELIST_TOTAL_REQUESTS, |
||||
every: RATE_LIMITS_WHITELIST_EVERY_MILLISECONDS, |
||||
} |
||||
: null, |
||||
blacklist: |
||||
RATE_LIMITS_BLACKLIST_EVERY_MILLISECONDS || |
||||
RATE_LIMITS_BLACKLIST_TOTAL_REQUESTS |
||||
? { |
||||
totalRequests: RATE_LIMITS_WHITELIST_TOTAL_REQUESTS, |
||||
every: RATE_LIMITS_BLACKLIST_EVERY_MILLISECONDS, |
||||
} |
||||
: null, |
||||
}, |
||||
}, |
||||
|
||||
storage: { |
||||
type: STORAGE_TYPE, |
||||
host: STORAGE_HOST, |
||||
port: Number(STORAGE_PORT), |
||||
expire: Number(STORAGE_EXPIRE_SECONDS), |
||||
bucket: STORAGE_AWS_BUCKET, |
||||
region: STORAGE_AWS_REGION, |
||||
connectionUrl: `postgres://${STORAGE_USERNAME}:${STORAGE_PASSWORD}@${STORAGE_HOST}:${STORAGE_PORT}/${STORAGE_DB}`, |
||||
db: STORAGE_DB, |
||||
user: STORAGE_USERNAME, |
||||
password: STORAGE_PASSWORD, |
||||
path: STORAGE_FILEPATH, |
||||
}, |
||||
|
||||
documents: DOCUMENTS |
||||
? DOCUMENTS.split(",").reduce((acc, item) => { |
||||
const keyAndValueArray = item.replace(/\s/g, "").split("="); |
||||
return { ...acc, [keyAndValueArray[0]]: keyAndValueArray[1] }; |
||||
}, {}) |
||||
: null, |
||||
}; |
||||
|
||||
console.log(JSON.stringify(config)); |
@ -0,0 +1,9 @@ |
||||
#!/bin/bash |
||||
|
||||
# We use this file to translate environmental variables to .env files used by the application |
||||
|
||||
set -e |
||||
|
||||
node ./docker-entrypoint.js > ./config.js |
||||
|
||||
exec "$@" |
@ -0,0 +1,56 @@ |
||||
/*global require,module,process*/ |
||||
|
||||
var AWS = require('aws-sdk'); |
||||
var winston = require('winston'); |
||||
|
||||
var AmazonS3DocumentStore = function(options) { |
||||
this.expire = options.expire; |
||||
this.bucket = options.bucket; |
||||
this.client = new AWS.S3({region: options.region}); |
||||
}; |
||||
|
||||
AmazonS3DocumentStore.prototype.get = function(key, callback, skipExpire) { |
||||
var _this = this; |
||||
|
||||
var req = { |
||||
Bucket: _this.bucket, |
||||
Key: key |
||||
}; |
||||
|
||||
_this.client.getObject(req, function(err, data) { |
||||
if(err) { |
||||
callback(false); |
||||
} |
||||
else { |
||||
callback(data.Body.toString('utf-8')); |
||||
if (_this.expire && !skipExpire) { |
||||
winston.warn('amazon s3 store cannot set expirations on keys'); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
AmazonS3DocumentStore.prototype.set = function(key, data, callback, skipExpire) { |
||||
var _this = this; |
||||
|
||||
var req = { |
||||
Bucket: _this.bucket, |
||||
Key: key, |
||||
Body: data, |
||||
ContentType: 'text/plain' |
||||
}; |
||||
|
||||
_this.client.putObject(req, function(err, data) { |
||||
if (err) { |
||||
callback(false); |
||||
} |
||||
else { |
||||
callback(true); |
||||
if (_this.expire && !skipExpire) { |
||||
winston.warn('amazon s3 store cannot set expirations on keys'); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
module.exports = AmazonS3DocumentStore; |
@ -0,0 +1,89 @@ |
||||
/*global require,module,process*/ |
||||
|
||||
const Datastore = require('@google-cloud/datastore'); |
||||
const winston = require('winston'); |
||||
|
||||
class GoogleDatastoreDocumentStore { |
||||
|
||||
// Create a new store with options
|
||||
constructor(options) { |
||||
this.kind = "Haste"; |
||||
this.expire = options.expire; |
||||
this.datastore = new Datastore(); |
||||
} |
||||
|
||||
// Save file in a key
|
||||
set(key, data, callback, skipExpire) { |
||||
var expireTime = (skipExpire || this.expire === undefined) ? null : new Date(Date.now() + this.expire * 1000); |
||||
|
||||
var taskKey = this.datastore.key([this.kind, key]) |
||||
var task = { |
||||
key: taskKey, |
||||
data: [ |
||||
{ |
||||
name: 'value', |
||||
value: data, |
||||
excludeFromIndexes: true |
||||
}, |
||||
{ |
||||
name: 'expiration', |
||||
value: expireTime |
||||
} |
||||
] |
||||
}; |
||||
|
||||
this.datastore.insert(task).then(() => { |
||||
callback(true); |
||||
}) |
||||
.catch(err => { |
||||
callback(false); |
||||
}); |
||||
} |
||||
|
||||
// Get a file from a key
|
||||
get(key, callback, skipExpire) { |
||||
var taskKey = this.datastore.key([this.kind, key]) |
||||
|
||||
this.datastore.get(taskKey).then((entity) => { |
||||
if (skipExpire || entity[0]["expiration"] == null) { |
||||
callback(entity[0]["value"]); |
||||
} |
||||
else { |
||||
// check for expiry
|
||||
if (entity[0]["expiration"] < new Date()) { |
||||
winston.info("document expired", {key: key, expiration: entity[0]["expiration"], check: new Date(null)}); |
||||
callback(false); |
||||
} |
||||
else { |
||||
// update expiry
|
||||
var task = { |
||||
key: taskKey, |
||||
data: [ |
||||
{ |
||||
name: 'value', |
||||
value: entity[0]["value"], |
||||
excludeFromIndexes: true |
||||
}, |
||||
{ |
||||
name: 'expiration', |
||||
value: new Date(Date.now() + this.expire * 1000) |
||||
} |
||||
] |
||||
}; |
||||
this.datastore.update(task).then(() => { |
||||
}) |
||||
.catch(err => { |
||||
winston.error("failed to update expiration", {error: err}); |
||||
}); |
||||
callback(entity[0]["value"]); |
||||
} |
||||
} |
||||
}) |
||||
.catch(err => { |
||||
winston.error("Error retrieving value from Google Datastore", {error: err}); |
||||
callback(false); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
module.exports = GoogleDatastoreDocumentStore; |
@ -1,45 +1,54 @@ |
||||
var memcached = require('memcache'); |
||||
var winston = require('winston'); |
||||
|
||||
// Create a new store with options
|
||||
var MemcachedDocumentStore = function(options) { |
||||
this.expire = options.expire; |
||||
if (!MemcachedDocumentStore.client) { |
||||
MemcachedDocumentStore.connect(options); |
||||
const memcached = require('memcached'); |
||||
const winston = require('winston'); |
||||
|
||||
class MemcachedDocumentStore { |
||||
|
||||
// Create a new store with options
|
||||
constructor(options) { |
||||
this.expire = options.expire; |
||||
|
||||
const host = options.host || '127.0.0.1'; |
||||
const port = options.port || 11211; |
||||
const url = `${host}:${port}`; |
||||
this.connect(url); |
||||
} |
||||
|
||||
// Create a connection
|
||||
connect(url) { |
||||
this.client = new memcached(url); |
||||
|
||||
winston.info(`connecting to memcached on ${url}`); |
||||
|
||||
this.client.on('failure', function(error) { |
||||
winston.info('error connecting to memcached', {error}); |
||||
}); |
||||
} |
||||
}; |
||||
|
||||
// Create a connection
|
||||
MemcachedDocumentStore.connect = function(options) { |
||||
var host = options.host || '127.0.0.1'; |
||||
var port = options.port || 11211; |
||||
this.client = new memcached.Client(port, host); |
||||
this.client.connect(); |
||||
this.client.on('connect', function() { |
||||
winston.info('connected to memcached on ' + host + ':' + port); |
||||
}); |
||||
this.client.on('error', function(e) { |
||||
winston.info('error connecting to memcached', { error: e }); |
||||
}); |
||||
}; |
||||
|
||||
// Save file in a key
|
||||
MemcachedDocumentStore.prototype.set = |
||||
function(key, data, callback, skipExpire) { |
||||
MemcachedDocumentStore.client.set(key, data, function(err, reply) { |
||||
err ? callback(false) : callback(true); |
||||
}, skipExpire ? 0 : this.expire); |
||||
}; |
||||
|
||||
// Get a file from a key
|
||||
MemcachedDocumentStore.prototype.get = function(key, callback, skipExpire) { |
||||
var _this = this; |
||||
MemcachedDocumentStore.client.get(key, function(err, reply) { |
||||
callback(err ? false : reply); |
||||
if (_this.expire && !skipExpire) { |
||||
winston.warn('store does not currently push forward expirations on GET'); |
||||
} |
||||
}); |
||||
}; |
||||
|
||||
// Save file in a key
|
||||
set(key, data, callback, skipExpire) { |
||||
this.client.set(key, data, skipExpire ? 0 : this.expire || 0, (error) => { |
||||
callback(!error); |
||||
}); |
||||
} |
||||
|
||||
// Get a file from a key
|
||||
get(key, callback, skipExpire) { |
||||
this.client.get(key, (error, data) => { |
||||
const value = error ? false : data; |
||||
|
||||
callback(value); |
||||
|
||||
// Update the key so that the expiration is pushed forward
|
||||
if (value && !skipExpire) { |
||||
this.set(key, data, (updateSucceeded) => { |
||||
if (!updateSucceeded) { |
||||
winston.error('failed to update expiration on GET', {key}); |
||||
} |
||||
}, skipExpire); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
} |
||||
|
||||
module.exports = MemcachedDocumentStore; |
||||
|
@ -0,0 +1,88 @@ |
||||
|
||||
|
||||
var MongoClient = require('mongodb').MongoClient, |
||||
winston = require('winston'); |
||||
|
||||
var MongoDocumentStore = function (options) { |
||||
this.expire = options.expire; |
||||
this.connectionUrl = process.env.DATABASE_URl || options.connectionUrl; |
||||
}; |
||||
|
||||
MongoDocumentStore.prototype.set = function (key, data, callback, skipExpire) { |
||||
var now = Math.floor(new Date().getTime() / 1000), |
||||
that = this; |
||||
|
||||
this.safeConnect(function (err, db) { |
||||
if (err) |
||||
return callback(false); |
||||
|
||||
db.collection('entries').update({ |
||||
'entry_id': key, |
||||
$or: [ |
||||
{ expiration: -1 }, |
||||
{ expiration: { $gt: now } } |
||||
] |
||||
}, { |
||||
'entry_id': key, |
||||
'value': data, |
||||
'expiration': that.expire && !skipExpire ? that.expire + now : -1 |
||||
}, { |
||||
upsert: true |
||||
}, function (err, existing) { |
||||
if (err) { |
||||
winston.error('error persisting value to mongodb', { error: err }); |
||||
return callback(false); |
||||
} |
||||
|
||||
callback(true); |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
MongoDocumentStore.prototype.get = function (key, callback, skipExpire) { |
||||
var now = Math.floor(new Date().getTime() / 1000), |
||||
that = this; |
||||
|
||||
this.safeConnect(function (err, db) { |
||||
if (err) |
||||
return callback(false); |
||||
|
||||
db.collection('entries').findOne({ |
||||
'entry_id': key, |
||||
$or: [ |
||||
{ expiration: -1 }, |
||||
{ expiration: { $gt: now } } |
||||
] |
||||
}, function (err, entry) { |
||||
if (err) { |
||||
winston.error('error persisting value to mongodb', { error: err }); |
||||
return callback(false); |
||||
} |
||||
|
||||
callback(entry === null ? false : entry.value); |
||||
|
||||
if (entry !== null && entry.expiration !== -1 && that.expire && !skipExpire) { |
||||
db.collection('entries').update({ |
||||
'entry_id': key |
||||
}, { |
||||
$set: { |
||||
'expiration': that.expire + now |
||||
} |
||||
}, function (err, result) { }); |
||||
} |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
MongoDocumentStore.prototype.safeConnect = function (callback) { |
||||
MongoClient.connect(this.connectionUrl, function (err, db) { |
||||
if (err) { |
||||
winston.error('error connecting to mongodb', { error: err }); |
||||
callback(err); |
||||
} else { |
||||
callback(undefined, db); |
||||
} |
||||
}); |
||||
}; |
||||
|
||||
module.exports = MongoDocumentStore; |
@ -0,0 +1,80 @@ |
||||
/*global require,module,process*/ |
||||
|
||||
var winston = require('winston'); |
||||
const {Pool} = require('pg'); |
||||
|
||||
// create table entries (id serial primary key, key varchar(255) not null, value text not null, expiration int, unique(key));
|
||||
|
||||
// A postgres document store
|
||||
var PostgresDocumentStore = function (options) { |
||||
this.expireJS = parseInt(options.expire, 10); |
||||
|
||||
const connectionString = process.env.DATABASE_URL || options.connectionUrl; |
||||
this.pool = new Pool({connectionString}); |
||||
}; |
||||
|
||||
PostgresDocumentStore.prototype = { |
||||
|
||||
// Set a given key
|
||||
set: function (key, data, callback, skipExpire) { |
||||
var now = Math.floor(new Date().getTime() / 1000); |
||||
var that = this; |
||||
this.safeConnect(function (err, client, done) { |
||||
if (err) { return callback(false); } |
||||
client.query('INSERT INTO entries (key, value, expiration) VALUES ($1, $2, $3)', [ |
||||
key, |
||||
data, |
||||
that.expireJS && !skipExpire ? that.expireJS + now : null |
||||
], function (err) { |
||||
if (err) { |
||||
winston.error('error persisting value to postgres', { error: err }); |
||||
return callback(false); |
||||
} |
||||
callback(true); |
||||
done(); |
||||
}); |
||||
}); |
||||
}, |
||||
|
||||
// Get a given key's data
|
||||
get: function (key, callback, skipExpire) { |
||||
var now = Math.floor(new Date().getTime() / 1000); |
||||
var that = this; |
||||
this.safeConnect(function (err, client, done) { |
||||
if (err) { return callback(false); } |
||||
client.query('SELECT id,value,expiration from entries where KEY = $1 and (expiration IS NULL or expiration > $2)', [key, now], function (err, result) { |
||||
if (err) { |
||||
winston.error('error retrieving value from postgres', { error: err }); |
||||
return callback(false); |
||||
} |
||||
callback(result.rows.length ? result.rows[0].value : false); |
||||
if (result.rows.length && that.expireJS && !skipExpire) { |
||||
client.query('UPDATE entries SET expiration = $1 WHERE ID = $2', [ |
||||
that.expireJS + now, |
||||
result.rows[0].id |
||||
], function (err) { |
||||
if (!err) { |
||||
done(); |
||||
} |
||||
}); |
||||
} else { |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
}, |
||||
|
||||
// A connection wrapper
|
||||
safeConnect: function (callback) { |
||||
this.pool.connect((error, client, done) => { |
||||
if (error) { |
||||
winston.error('error connecting to postgres', {error}); |
||||
callback(error); |
||||
} else { |
||||
callback(undefined, client, done); |
||||
} |
||||
}); |
||||
} |
||||
}; |
||||
|
||||
module.exports = PostgresDocumentStore; |
@ -0,0 +1,46 @@ |
||||
const crypto = require('crypto'); |
||||
const rethink = require('rethinkdbdash'); |
||||
const winston = require('winston'); |
||||
|
||||
const md5 = (str) => { |
||||
const md5sum = crypto.createHash('md5'); |
||||
md5sum.update(str); |
||||
return md5sum.digest('hex'); |
||||
}; |
||||
|
||||
class RethinkDBStore { |
||||
constructor(options) { |
||||
this.client = rethink({ |
||||
silent: true, |
||||
host: options.host || '127.0.0.1', |
||||
port: options.port || 28015, |
||||
db: options.db || 'haste', |
||||
user: options.user || 'admin', |
||||
password: options.password || '' |
||||
}); |
||||
} |
||||
|
||||
set(key, data, callback) { |
||||
this.client.table('uploads').insert({ id: md5(key), data: data }).run((error) => { |
||||
if (error) { |
||||
callback(false); |
||||
winston.error('failed to insert to table', error); |
||||
return; |
||||
} |
||||
callback(true); |
||||
}); |
||||
} |
||||
|
||||
get(key, callback) { |
||||
this.client.table('uploads').get(md5(key)).run((error, result) => { |
||||
if (error || !result) { |
||||
callback(false); |
||||
if (error) winston.error('failed to insert to table', error); |
||||
return; |
||||
} |
||||
callback(result.data); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
module.exports = RethinkDBStore; |
@ -0,0 +1,32 @@ |
||||
const fs = require('fs'); |
||||
|
||||
module.exports = class DictionaryGenerator { |
||||
|
||||
constructor(options, readyCallback) { |
||||
// Check options format
|
||||
if (!options) throw Error('No options passed to generator'); |
||||
if (!options.path) throw Error('No dictionary path specified in options'); |
||||
|
||||
// Load dictionary
|
||||
fs.readFile(options.path, 'utf8', (err, data) => { |
||||
if (err) throw err; |
||||
|
||||
this.dictionary = data.split(/[\n\r]+/); |
||||
|
||||
if (readyCallback) readyCallback(); |
||||
}); |
||||
} |
||||
|
||||
// Generates a dictionary-based key, of keyLength words
|
||||
createKey(keyLength) { |
||||
let text = ''; |
||||
|
||||
for (let i = 0; i < keyLength; i++) { |
||||
const index = Math.floor(Math.random() * this.dictionary.length); |
||||
text += this.dictionary[index]; |
||||
} |
||||
|
||||
return text; |
||||
} |
||||
|
||||
}; |
@ -1,32 +1,27 @@ |
||||
// Draws inspiration from pwgen and http://tools.arantius.com/password
|
||||
var PhoneticKeyGenerator = function(options) { |
||||
// No options
|
||||
}; |
||||
|
||||
// Generate a phonetic key
|
||||
PhoneticKeyGenerator.prototype.createKey = function(keyLength) { |
||||
var text = ''; |
||||
for (var i = 0; i < keyLength; i++) { |
||||
text += (i % 2 == 0) ? this.randConsonant() : this.randVowel(); |
||||
} |
||||
return text; |
||||
const randOf = (collection) => { |
||||
return () => { |
||||
return collection[Math.floor(Math.random() * collection.length)]; |
||||
}; |
||||
}; |
||||
|
||||
PhoneticKeyGenerator.consonants = 'bcdfghjklmnpqrstvwxy'; |
||||
PhoneticKeyGenerator.vowels = 'aeiou'; |
||||
// Helper methods to get an random vowel or consonant
|
||||
const randVowel = randOf('aeiou'); |
||||
const randConsonant = randOf('bcdfghjklmnpqrstvwxyz'); |
||||
|
||||
// Get an random vowel
|
||||
PhoneticKeyGenerator.prototype.randVowel = function() { |
||||
return PhoneticKeyGenerator.vowels[ |
||||
Math.floor(Math.random() * PhoneticKeyGenerator.vowels.length) |
||||
]; |
||||
}; |
||||
module.exports = class PhoneticKeyGenerator { |
||||
|
||||
// Get an random consonant
|
||||
PhoneticKeyGenerator.prototype.randConsonant = function() { |
||||
return PhoneticKeyGenerator.consonants[ |
||||
Math.floor(Math.random() * PhoneticKeyGenerator.consonants.length) |
||||
]; |
||||
}; |
||||
// Generate a phonetic key of alternating consonant & vowel
|
||||
createKey(keyLength) { |
||||
let text = ''; |
||||
const start = Math.round(Math.random()); |
||||
|
||||
module.exports = PhoneticKeyGenerator; |
||||
for (let i = 0; i < keyLength; i++) { |
||||
text += (i % 2 == start) ? randConsonant() : randVowel(); |
||||
} |
||||
|
||||
return text; |
||||
} |
||||
|
||||
}; |
||||
|
@ -1,19 +1,20 @@ |
||||
var RandomKeyGenerator = function(options) { |
||||
if (!options) { |
||||
options = {}; |
||||
module.exports = class RandomKeyGenerator { |
||||
|
||||
// Initialize a new generator with the given keySpace
|
||||
constructor(options = {}) { |
||||
this.keyspace = options.keyspace || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; |
||||
} |
||||
this.keyspace = options.keyspace || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; |
||||
}; |
||||
|
||||
// Generate a random key
|
||||
RandomKeyGenerator.prototype.createKey = function(keyLength) { |
||||
var text = ''; |
||||
var index; |
||||
for (var i = 0; i < keyLength; i++) { |
||||
index = Math.floor(Math.random() * this.keyspace.length); |
||||
text += this.keyspace.charAt(index); |
||||
// Generate a key of the given length
|
||||
createKey(keyLength) { |
||||
var text = ''; |
||||
|
||||
for (var i = 0; i < keyLength; i++) { |
||||
const index = Math.floor(Math.random() * this.keyspace.length); |
||||
text += this.keyspace.charAt(index); |
||||
} |
||||
|
||||
return text; |
||||
} |
||||
return text; |
||||
}; |
||||
|
||||
module.exports = RandomKeyGenerator; |
||||
}; |
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,34 @@ |
||||
/* global describe, it */ |
||||
|
||||
const assert = require('assert'); |
||||
|
||||
const fs = require('fs'); |
||||
|
||||
const Generator = require('../../lib/key_generators/dictionary'); |
||||
|
||||
describe('DictionaryGenerator', function() { |
||||
describe('options', function() { |
||||
it('should throw an error if given no options', () => { |
||||
assert.throws(() => { |
||||
new Generator(); |
||||
}, Error); |
||||
}); |
||||
|
||||
it('should throw an error if given no path', () => { |
||||
assert.throws(() => { |
||||
new Generator({}); |
||||
}, Error); |
||||
}); |
||||
}); |
||||
describe('generation', function() { |
||||
it('should return a key of the proper number of words from the given dictionary', () => { |
||||
const path = '/tmp/haste-server-test-dictionary'; |
||||
const words = ['cat']; |
||||
fs.writeFileSync(path, words.join('\n')); |
||||
|
||||
const gen = new Generator({path}, () => { |
||||
assert.equal('catcatcat', gen.createKey(3)); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
@ -0,0 +1,35 @@ |
||||
/* global describe, it */ |
||||
|
||||
const assert = require('assert'); |
||||
|
||||
const Generator = require('../../lib/key_generators/phonetic'); |
||||
|
||||
const vowels = 'aeiou'; |
||||
const consonants = 'bcdfghjklmnpqrstvwxyz'; |
||||
|
||||
describe('PhoneticKeyGenerator', () => { |
||||
describe('generation', () => { |
||||
it('should return a key of the proper length', () => { |
||||
const gen = new Generator(); |
||||
assert.equal(6, gen.createKey(6).length); |
||||
}); |
||||
|
||||
it('should alternate consonants and vowels', () => { |
||||
const gen = new Generator(); |
||||
|
||||
const key = gen.createKey(3); |
||||
|
||||
// if it starts with a consonant, we expect cvc
|
||||
// if it starts with a vowel, we expect vcv
|
||||
if(consonants.includes(key[0])) { |
||||
assert.ok(consonants.includes(key[0])); |
||||
assert.ok(consonants.includes(key[2])); |
||||
assert.ok(vowels.includes(key[1])); |
||||
} else { |
||||
assert.ok(vowels.includes(key[0])); |
||||
assert.ok(vowels.includes(key[2])); |
||||
assert.ok(consonants.includes(key[1])); |
||||
} |
||||
}); |
||||
}); |
||||
}); |
@ -0,0 +1,24 @@ |
||||
/* global describe, it */ |
||||
|
||||
const assert = require('assert'); |
||||
|
||||
const Generator = require('../../lib/key_generators/random'); |
||||
|
||||
describe('RandomKeyGenerator', () => { |
||||
describe('generation', () => { |
||||
it('should return a key of the proper length', () => { |
||||
const gen = new Generator(); |
||||
assert.equal(gen.createKey(6).length, 6); |
||||
}); |
||||
|
||||
it('should use a key from the given keyset if given', () => { |
||||
const gen = new Generator({keyspace: 'A'}); |
||||
assert.equal(gen.createKey(6), 'AAAAAA'); |
||||
}); |
||||
|
||||
it('should not use a key from the given keyset if not given', () => { |
||||
const gen = new Generator({keyspace: 'A'}); |
||||
assert.ok(!gen.createKey(6).includes('B')); |
||||
}); |
||||
}); |
||||
}); |
Loading…
Reference in new issue