Compare commits
216 Commits
codemirror
...
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 |
|
abb49f2cf3 | 12 years ago |
|
d1cd2a5213 | 13 years ago |
|
27317844e0 | 13 years ago |
|
ee74e2fa90 | 13 years ago |
|
5d8bd2e6f8 | 13 years ago |
|
cd4c7aeab8 | 13 years ago |
|
e37c3cf1b9 | 13 years ago |
|
8858bab985 | 13 years ago |
|
afb0c332cc | 13 years ago |
|
82c58c5c0c | 13 years ago |
|
46bdd27431 | 13 years ago |
|
1adfba1a37 | 13 years ago |
|
54e55b1b0d | 13 years ago |
|
08d37cc7f7 | 13 years ago |
|
aa781957e8 | 13 years ago |
|
c00477c93c | 13 years ago |
|
035f09ac05 | 13 years ago |
|
36e00bb29e | 13 years ago |
|
10623873e8 | 13 years ago |
|
e536ba1019 | 13 years ago |
|
85fc36d710 | 13 years ago |
|
5d5ae164f3 | 13 years ago |
|
79309c75df | 13 years ago |
|
4b58c8d356 | 13 years ago |
|
8f0d6260b0 | 13 years ago |
|
93a83a35da | 13 years ago |
|
4efc5d47d9 | 13 years ago |
|
ff8ef54e34 | 13 years ago |
|
814a49812a | 13 years ago |
|
e0610bc1be | 13 years ago |
|
962976c204 | 13 years ago |
|
16080bdc16 | 13 years ago |
|
20ce741341 | 13 years ago |
39 changed files with 3087 additions and 361 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'); |
||||
const memcached = require('memcached'); |
||||
const winston = require('winston'); |
||||
|
||||
class MemcachedDocumentStore { |
||||
|
||||
// Create a new store with options
|
||||
var MemcachedDocumentStore = function(options) { |
||||
constructor(options) { |
||||
this.expire = options.expire; |
||||
if (!MemcachedDocumentStore.client) { |
||||
MemcachedDocumentStore.connect(options); |
||||
|
||||
const host = options.host || '127.0.0.1'; |
||||
const port = options.port || 11211; |
||||
const url = `${host}:${port}`; |
||||
this.connect(url); |
||||
} |
||||
}; |
||||
|
||||
// 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 }); |
||||
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}); |
||||
}); |
||||
}; |
||||
} |
||||
|
||||
// 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); |
||||
}; |
||||
set(key, data, callback, skipExpire) { |
||||
this.client.set(key, data, skipExpire ? 0 : this.expire || 0, (error) => { |
||||
callback(!error); |
||||
}); |
||||
} |
||||
|
||||
// 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'); |
||||
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'; |
||||
}; |
||||
} |
||||
|
||||
// Generate a random key
|
||||
RandomKeyGenerator.prototype.createKey = function(keyLength) { |
||||
// Generate a key of the given length
|
||||
createKey(keyLength) { |
||||
var text = ''; |
||||
var index; |
||||
|
||||
for (var i = 0; i < keyLength; i++) { |
||||
index = Math.floor(Math.random() * this.keyspace.length); |
||||
const index = Math.floor(Math.random() * this.keyspace.length); |
||||
text += this.keyspace.charAt(index); |
||||
} |
||||
|
||||
return text; |
||||
}; |
||||
} |
||||
|
||||
module.exports = RandomKeyGenerator; |
||||
}; |
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,52 +1,47 @@ |
||||
{ |
||||
|
||||
"name": "haste", |
||||
"version": "0.0.1", |
||||
|
||||
"version": "0.1.0", |
||||
"private": true, |
||||
|
||||
"description": "Private Paste", |
||||
|
||||
"keywords": [ "paste", "pastebin" ], |
||||
|
||||
"description": "Private Pastebin Server", |
||||
"keywords": [ |
||||
"paste", |
||||
"pastebin" |
||||
], |
||||
"author": { |
||||
"name": "John Crepezzi", |
||||
"email": "john.crepezzi@gmail.com", |
||||
"url": "http://seejohncode.com/" |
||||
}, |
||||
|
||||
"main": "haste", |
||||
|
||||
"dependencies": { |
||||
"winston": "*", |
||||
"connect": "< 2", |
||||
"uglify-js": "*" |
||||
"busboy": "0.2.4", |
||||
"connect": "^3.7.0", |
||||
"connect-ratelimit": "0.0.7", |
||||
"connect-route": "0.1.5", |
||||
"pg": "^8.0.0", |
||||
"redis": "0.8.1", |
||||
"redis-url": "0.1.0", |
||||
"st": "^2.0.0", |
||||
"uglify-js": "3.1.6", |
||||
"winston": "^2.0.0" |
||||
}, |
||||
|
||||
"devDependencies": { |
||||
"mocha": "*", |
||||
"should": "*" |
||||
"mocha": "^8.1.3" |
||||
}, |
||||
|
||||
"bundledDependencies": [], |
||||
|
||||
"engines": { |
||||
"node": "*" |
||||
}, |
||||
|
||||
"bin": { |
||||
"haste-server": "./server.js" |
||||
}, |
||||
|
||||
"files": [ "server.js", "lib", "static" ], |
||||
|
||||
"files": [ |
||||
"server.js", |
||||
"lib", |
||||
"static" |
||||
], |
||||
"directories": { |
||||
"lib": "./lib" |
||||
}, |
||||
|
||||
"scripts": { |
||||
"start": "node server.js", |
||||
"test": "mocha -r should spec/*" |
||||
"test": "mocha --recursive" |
||||
} |
||||
|
||||
} |
||||
|
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