Compare commits
143 Commits
production
...
master
Author | SHA1 | Date |
---|---|---|
Ceda EI | 9b59c4c89b | |
Konstantin Rybakov | 00d84614c2 | |
Konstantin Rybakov | 52e7cef7ef | |
Sueqkjs | fbff1bc201 | |
Denis Berezin | 7af15cc32d | |
Denis Berezin | 7f397ce753 | |
Denis Berezin | 8f8b039f65 | |
Denis Berezin | eeaf2d7b18 | |
Justen Martin | db0b7d6444 | |
lksz | db6e7603f9 | |
Ceda EI | 20fb7f9bc2 | |
Bruno Saboia | ad5d7549d7 | |
Ceda EI | 991f26e871 | |
Ceda EI | e2293900de | |
John Crepezzi | 5d2965ffc5 | |
John Crepezzi | f255928af7 | |
John Crepezzi | a108dbadc5 | |
John Crepezzi | c409aca080 | |
John Crepezzi | 219424550b | |
John Crepezzi | f147acb51c | |
John Crepezzi | 9a692ed652 | |
John Crepezzi | 3a17c86a0f | |
John Crepezzi | 677a22987a | |
John Crepezzi | 89d912c6ff | |
John Crepezzi | 4cac6713ef | |
John Crepezzi | f3b0de745b | |
John Crepezzi | cc8a99752f | |
John Crepezzi | 6853d077e7 | |
John Crepezzi | 80a2b6f0dd | |
John Crepezzi | 4f68b3d7d6 | |
John Crepezzi | ef0ca40533 | |
John Crepezzi | f372ef18de | |
John Crepezzi | 181a3a2bfa | |
John Crepezzi | 61d08afb3b | |
John Crepezzi | 1ba025328d | |
John Crepezzi | a79fb39f54 | |
John Crepezzi | 3a72d74537 | |
John Crepezzi | e9ae74b7a9 | |
John Crepezzi | c305e9a83d | |
dependabot[bot] | 16bce4c83d | |
John Crepezzi | 661997cd73 | |
John Crepezzi | 159f989d08 | |
emil-lengman | 139df62ec4 | |
emil-lengman | bae6387bb7 | |
emil-lengman | bb7b9571a7 | |
emil-lengman | a4dc29fb2b | |
emil-lengman | 342f56ce1a | |
emil-lengman | 05ecc90764 | |
emil-lengman | 69cf505a90 | |
emil-lengman | 9f41993566 | |
emil-lengman | 5c9311fb85 | |
emil-lengman | 5a8d52a5e3 | |
emil-lengman | 0f145b4444 | |
emil-lengman | aef4bb5edb | |
emil-lengman | 36c854ef1b | |
emil-lengman | edd428ff37 | |
emil-lengman | 0612ba001e | |
emil-lengman | 064680003d | |
emil-lengman | 655f2af45a | |
Reece Dunham | ce03749c2f | |
Ceda EI | 24ed412f50 | |
Ceda EI | 7bd0fcc621 | |
Ceda EI | e718672b58 | |
epdn | f6084b4339 | |
John Crepezzi | 9b0a5ff0a3 | |
Ceda EI | a4ad0e1fa6 | |
Ceda EI | af28e0c5d9 | |
Ceda EI | 240a9f7fde | |
Ceda EI | dca3237a71 | |
Ceda EI | a541630848 | |
Ceda EI | e067323714 | |
Jacob Parker | 1fff48568f | |
Jacob Parker | b4c666fbcf | |
John Crepezzi | b866c33c93 | |
Mikołaj Pich | 035cf0e91e | |
John Crepezzi | f3838ab4a8 | |
John Crepezzi | bf2b1c957a | |
Yuan Gao | 86bbc1899d | |
Yuan Gao | d41d7491d4 | |
Yuan | 5fb43eb67c | |
Yuan | 1eeef4ede4 | |
Yuan | ebc749c5e0 | |
Yuan | b0bbb72f35 | |
Yuan | 2213c3874a | |
Yuan | 6ebd72a86c | |
Yuan | b6814a1445 | |
Yuan | e3d18efdc6 | |
Yuan | 869fb65738 | |
Yuan Gao | 56b939124e | |
John Crepezzi | ee1c1c0856 | |
John Crepezzi | b087ac8dd1 | |
John Crepezzi | d922667f56 | |
Jacob Gunther | 5f6fefa7a6 | |
John Crepezzi | faa7e679ca | |
Jacob Gunther | cd3bf26dbe | |
Jacob Gunther | 830dc1bc43 | |
Jacob Gunther | dc0f151a7f | |
John Crepezzi | 7f625e22f7 | |
John Crepezzi | 528b7b07a8 | |
Razzeee | 2b81e67ce7 | |
John Crepezzi | 827e7b51b5 | |
Kevin Händel | 16d529e935 | |
John Crepezzi | ad7702aaf4 | |
Viktor Szépe | f5fbc8d19e | |
John Crepezzi | 0a8923bf12 | |
Martin | 4d572a2ec0 | |
Martin | d9a53d3e6e | |
John Crepezzi | 8da37ea5de | |
Jacob Gunther | ff0fccd6c2 | |
John Crepezzi | 63c4576633 | |
Jacob Gunther | b31d143bcd | |
Jacob Gunther | 0d8aec8d61 | |
Jacob Gunther | 1f9fdd205d | |
Jacob Gunther | cdd0cf3739 | |
PassTheMayo | ba5c6b8d16 | |
John Crepezzi | cfef588283 | |
John Crepezzi | 318c5f7ba6 | |
John Crepezzi | ee03e7cd78 | |
John Crepezzi | 3b6934e348 | |
John Crepezzi | f161cc33b4 | |
John Crepezzi | 40f1f2588e | |
John Crepezzi | e4e025f67e | |
John Crepezzi | e12805a8aa | |
John Crepezzi | e76c845f16 | |
John Crepezzi | 072418695e | |
John Crepezzi | 584b66bc66 | |
Sebastian Sterk | f8db455f74 | |
John Crepezzi | f19c5d1049 | |
John Crepezzi | c5b859ec98 | |
John Crepezzi | 2ee93a7409 | |
John Crepezzi | bf1dbb68b8 | |
John Crepezzi | cf28e23d8e | |
John Crepezzi | 5939dec185 | |
John Crepezzi | 3ed1d775ac | |
John Crepezzi | 87b1c76aaf | |
John Crepezzi | 4599203bdf | |
Klas af Geijerstam | d66bc9a6c4 | |
Klas af Geijerstam | 80f0618736 | |
Klas af Geijerstam | ac2bceefbb | |
Klas af Geijerstam | dbf4f6b5dd | |
Klas af Geijerstam | 8e9205cecc | |
Klas af Geijerstam | e54a860172 | |
Evan Steinkerchner | d3db5e2a5d |
|
@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,3 +4,4 @@ node_modules
|
||||||
*.swo
|
*.swo
|
||||||
data
|
data
|
||||||
*.DS_Store
|
*.DS_Store
|
||||||
|
config.json
|
||||||
|
|
|
@ -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"]
|
194
README.md
194
README.md
|
@ -31,16 +31,16 @@ STDOUT. Check the README there for more details and usages.
|
||||||
1. Download the package, and expand it
|
1. Download the package, and expand it
|
||||||
2. Explore the settings inside of config.js, but the defaults should be good
|
2. Explore the settings inside of config.js, but the defaults should be good
|
||||||
3. `npm install`
|
3. `npm install`
|
||||||
4. `npm start`
|
4. `npm start` (you may specify an optional `<config-path>` as well)
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
|
|
||||||
* `host` - the host the server runs on (default localhost)
|
* `host` - the host the server runs on (default localhost)
|
||||||
* `port` - the port the server runs on (default 7777)
|
* `port` - the port the server runs on (default 7777)
|
||||||
* `keyLength` - the length of the keys to user (default 10)
|
* `keyLength` - the length of the keys to user (default 10)
|
||||||
* `maxLength` - maximum length of a paste (default none)
|
* `maxLength` - maximum length of a paste (default 400000)
|
||||||
* `staticMaxAge` - max age for static assets (86400)
|
* `staticMaxAge` - max age for static assets (86400)
|
||||||
* `recompressStatisAssets` - whether or not to compile static js assets (true)
|
* `recompressStaticAssets` - whether or not to compile static js assets (true)
|
||||||
* `documents` - static documents to serve (ex: http://hastebin.com/about.com)
|
* `documents` - static documents to serve (ex: http://hastebin.com/about.com)
|
||||||
in addition to static assets. These will never expire.
|
in addition to static assets. These will never expire.
|
||||||
* `storage` - storage options (see below)
|
* `storage` - storage options (see below)
|
||||||
|
@ -52,7 +52,7 @@ STDOUT. Check the README there for more details and usages.
|
||||||
|
|
||||||
When present, the `rateLimits` option enables built-in rate limiting courtesy
|
When present, the `rateLimits` option enables built-in rate limiting courtesy
|
||||||
of `connect-ratelimit`. Any of the options supported by that library can be
|
of `connect-ratelimit`. Any of the options supported by that library can be
|
||||||
used and set in `config.json`.
|
used and set in `config.js`.
|
||||||
|
|
||||||
See the README for [connect-ratelimit](https://github.com/dharmafly/connect-ratelimit)
|
See the README for [connect-ratelimit](https://github.com/dharmafly/connect-ratelimit)
|
||||||
for more information!
|
for more information!
|
||||||
|
@ -97,7 +97,9 @@ something like:
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Where `path` represents where you want the files stored
|
where `path` represents where you want the files stored.
|
||||||
|
|
||||||
|
File storage currently does not support paste expiration, you can follow [#191](https://github.com/seejohnrun/haste-server/issues/191) for status updates.
|
||||||
|
|
||||||
### Redis
|
### Redis
|
||||||
|
|
||||||
|
@ -152,11 +154,33 @@ or post.
|
||||||
|
|
||||||
All of which are optional except `type` with very logical default values.
|
All of which are optional except `type` with very logical default values.
|
||||||
|
|
||||||
|
### MongoDB
|
||||||
|
|
||||||
|
To use mongodb storage you must install the 'mongodb' package in npm
|
||||||
|
|
||||||
|
`npm install mongodb`
|
||||||
|
|
||||||
|
Once you've done that, your config section should look like:
|
||||||
|
|
||||||
|
``` json
|
||||||
|
{
|
||||||
|
"type": "mongo",
|
||||||
|
"connectionUrl": "mongodb://localhost:27017/database"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also just set the environment variable for `DATABASE_URL` to your database connection url.
|
||||||
|
|
||||||
|
Unlike with postgres you do NOT have to create the table in your mongo database prior to running.
|
||||||
|
|
||||||
|
You can also set an `expire` option to the number of seconds to expire keys in.
|
||||||
|
This is off by default, but will constantly kick back expirations on each view or post.
|
||||||
|
|
||||||
### Memcached
|
### Memcached
|
||||||
|
|
||||||
To use memcached storage you must install the `memcache` package via npm
|
To use memcache storage you must install the `memcached` package via npm
|
||||||
|
|
||||||
`npm install memcache`
|
`npm install memcached`
|
||||||
|
|
||||||
Once you've done that, your config section should look like:
|
Once you've done that, your config section should look like:
|
||||||
|
|
||||||
|
@ -174,6 +198,162 @@ forward on GETs.
|
||||||
|
|
||||||
All of which are optional except `type` with very logical default values.
|
All of which are optional except `type` with very logical default values.
|
||||||
|
|
||||||
|
### RethinkDB
|
||||||
|
|
||||||
|
To use the RethinkDB storage system, you must install the `rethinkdbdash` package via npm
|
||||||
|
|
||||||
|
`npm install rethinkdbdash`
|
||||||
|
|
||||||
|
Once you've done that, your config section should look like this:
|
||||||
|
|
||||||
|
``` json
|
||||||
|
{
|
||||||
|
"type": "rethinkdb",
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"port": 28015,
|
||||||
|
"db": "haste"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In order for this to work, the database must be pre-created before the script is ran.
|
||||||
|
Also, you must create an `uploads` table, which will store all the data for uploads.
|
||||||
|
|
||||||
|
You can optionally add the `user` and `password` properties to use a user system.
|
||||||
|
|
||||||
|
### Google Datastore
|
||||||
|
|
||||||
|
To use the Google Datastore storage system, you must install the `@google-cloud/datastore` package via npm
|
||||||
|
|
||||||
|
`npm install @google-cloud/datastore`
|
||||||
|
|
||||||
|
Once you've done that, your config section should look like this:
|
||||||
|
|
||||||
|
``` json
|
||||||
|
{
|
||||||
|
"type": "google-datastore"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Authentication is handled automatically by [Google Cloud service account credentials](https://cloud.google.com/docs/authentication/getting-started), by providing authentication details to the GOOGLE_APPLICATION_CREDENTIALS environmental variable.
|
||||||
|
|
||||||
|
### Amazon S3
|
||||||
|
|
||||||
|
To use [Amazon S3](https://aws.amazon.com/s3/) as a storage system, you must
|
||||||
|
install the `aws-sdk` package via npm:
|
||||||
|
|
||||||
|
`npm install aws-sdk`
|
||||||
|
|
||||||
|
Once you've done that, your config section should look like this:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "amazon-s3",
|
||||||
|
"bucket": "your-bucket-name",
|
||||||
|
"region": "us-east-1"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Authentication is handled automatically by the client. Check
|
||||||
|
[Amazon's documentation](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-credentials-node.html)
|
||||||
|
for more information. You will need to grant your role these permissions to
|
||||||
|
your bucket:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Action": [
|
||||||
|
"s3:GetObject",
|
||||||
|
"s3:PutObject"
|
||||||
|
],
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Resource": "arn:aws:s3:::your-bucket-name-goes-here/*"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
### Build image
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build --tag haste-server .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run container
|
||||||
|
|
||||||
|
For this example we will run haste-server, and connect it to a redis server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --name haste-server-container --env STORAGE_TYPE=redis --env STORAGE_HOST=redis-server --env STORAGE_PORT=6379 haste-server
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use docker-compose example
|
||||||
|
|
||||||
|
There is an example `docker-compose.yml` which runs haste-server together with memcached
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
The docker image is configured using environmental variables as you can see in the example above.
|
||||||
|
|
||||||
|
Here is a list of all the environment variables
|
||||||
|
|
||||||
|
### Storage
|
||||||
|
|
||||||
|
| Name | Default value | Description |
|
||||||
|
| :--------------------: | :-----------: | :-----------------------------------------------------------------------------------------------------------: |
|
||||||
|
| STORAGE_TYPE | memcached | Type of storage . Accepted values: "memcached","redis","postgres","rethinkdb", "amazon-s3", and "file" |
|
||||||
|
| STORAGE_HOST | 127.0.0.1 | Storage host. Applicable for types: memcached, redis, postgres, and rethinkdb |
|
||||||
|
| STORAGE_PORT | 11211 | Port on the storage host. Applicable for types: memcached, redis, postgres, and rethinkdb |
|
||||||
|
| STORAGE_EXPIRE_SECONDS | 2592000 | Number of seconds to expire keys in. Applicable for types. Redis, postgres, memcached. `expire` option to the |
|
||||||
|
| STORAGE_DB | 2 | The name of the database. Applicable for redis, postgres, and rethinkdb |
|
||||||
|
| STORAGE_PASSWORD | | Password for database. Applicable for redis, postges, rethinkdb . |
|
||||||
|
| STORAGE_USERNAME | | Database username. Applicable for postgres, and rethinkdb |
|
||||||
|
| STORAGE_AWS_BUCKET | | Applicable for amazon-s3. This is the name of the S3 bucket |
|
||||||
|
| STORAGE_AWS_REGION | | Applicable for amazon-s3. The region in which the bucket is located |
|
||||||
|
| STORAGE_FILEPATH | | Path to file to save data to. Applicable for type file |
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
|
||||||
|
| Name | Default value | Description |
|
||||||
|
| :---------------: | :-----------: | :---------: |
|
||||||
|
| LOGGING_LEVEL | verbose | |
|
||||||
|
| LOGGING_TYPE= | Console |
|
||||||
|
| LOGGING_COLORIZE= | true |
|
||||||
|
|
||||||
|
### Basics
|
||||||
|
|
||||||
|
| Name | Default value | Description |
|
||||||
|
| :----------------------: | :--------------: | :---------------------------------------------------------------------------------------: |
|
||||||
|
| HOST | 0.0.0.0 | The hostname which the server answers on |
|
||||||
|
| PORT | 7777 | The port on which the server is running |
|
||||||
|
| KEY_LENGTH | 10 | the length of the keys to user |
|
||||||
|
| MAX_LENGTH | 400000 | maximum length of a paste |
|
||||||
|
| STATIC_MAX_AGE | 86400 | max age for static assets |
|
||||||
|
| RECOMPRESS_STATIC_ASSETS | true | whether or not to compile static js assets |
|
||||||
|
| KEYGENERATOR_TYPE | phonetic | Type of key generator. Acceptable values: "phonetic", or "random" |
|
||||||
|
| KEYGENERATOR_KEYSPACE | | keySpace argument is a string of acceptable characters |
|
||||||
|
| DOCUMENTS | about=./about.md | Comma separated list of static documents to serve. ex: \n about=./about.md,home=./home.md |
|
||||||
|
|
||||||
|
### Rate limits
|
||||||
|
|
||||||
|
| Name | Default value | Description |
|
||||||
|
| :----------------------------------: | :-----------------------------------: | :--------------------------------------------------------------------------------------: |
|
||||||
|
| RATELIMITS_NORMAL_TOTAL_REQUESTS | 500 | By default anyone uncategorized will be subject to 500 requests in the defined timespan. |
|
||||||
|
| RATELIMITS_NORMAL_EVERY_MILLISECONDS | 60000 | The timespan to allow the total requests for uncategorized users |
|
||||||
|
| RATELIMITS_WHITELIST_TOTAL_REQUESTS | | By default client names in the whitelist will not have their requests limited. |
|
||||||
|
| RATELIMITS_WHITELIST_EVERY_SECONDS | | By default client names in the whitelist will not have their requests limited. |
|
||||||
|
| RATELIMITS_WHITELIST | example1.whitelist,example2.whitelist | Comma separated list of the clients which are in the whitelist pool |
|
||||||
|
| RATELIMITS_BLACKLIST_TOTAL_REQUESTS | | By default client names in the blacklist will be subject to 0 requests per hours. |
|
||||||
|
| RATELIMITS_BLACKLIST_EVERY_SECONDS | | By default client names in the blacklist will be subject to 0 requests per hours |
|
||||||
|
| RATELIMITS_BLACKLIST | example1.blacklist,example2.blacklist | Comma separated list of the clients which are in the blacklistpool. |
|
||||||
|
|
||||||
## Author
|
## Author
|
||||||
|
|
||||||
John Crepezzi <john.crepezzi@gmail.com>
|
John Crepezzi <john.crepezzi@gmail.com>
|
||||||
|
|
40
about.md
40
about.md
|
@ -15,45 +15,19 @@ To make a new entry, click "New" (or type 'control + n')
|
||||||
|
|
||||||
## From the Console
|
## From the Console
|
||||||
|
|
||||||
Most of the time I want to show you some text, it's coming from my current
|
[bin-client](git.webionite.com/ceda_ei/bin-client)
|
||||||
console session. We should make it really easy to take code from the console
|
|
||||||
and send it to people.
|
|
||||||
|
|
||||||
`cat something | haste` # http://hastebin.com/1238193
|
Add the following to your bashrc/zshrc
|
||||||
|
```
|
||||||
You can even take this a step further, and cut out the last step of copying the
|
export MKR_BIN='https://bin.webionite.com/'
|
||||||
URL with:
|
export HASTEBIN=1
|
||||||
|
```
|
||||||
* osx: `cat something | haste | pbcopy`
|
|
||||||
* linux: `cat something | haste | xsel`
|
|
||||||
* windows: check out [WinHaste](https://github.com/ajryan/WinHaste)
|
|
||||||
|
|
||||||
After running that, the STDOUT output of `cat something` will show up at a URL
|
|
||||||
which has been conveniently copied to your clipboard.
|
|
||||||
|
|
||||||
That's all there is to that, and you can install it with `gem install haste`
|
|
||||||
right now.
|
|
||||||
* osx: you will need to have an up to date version of Xcode
|
|
||||||
* linux: you will need to have rubygems and ruby-devel installed
|
|
||||||
|
|
||||||
## Duration
|
|
||||||
|
|
||||||
Pastes will stay for 30 days from their last view. They may be removed earlier
|
|
||||||
and without notice.
|
|
||||||
|
|
||||||
## Privacy
|
|
||||||
|
|
||||||
While the contents of hastebin.com are not directly crawled by any search robot
|
|
||||||
that obeys "robots.txt", there should be no great expectation of privacy. Post
|
|
||||||
things at your own risk. Not responsible for any loss of data or removed
|
|
||||||
pastes.
|
|
||||||
|
|
||||||
## Open Source
|
## Open Source
|
||||||
|
|
||||||
Haste can easily be installed behind your network, and it's all open source!
|
Haste can easily be installed behind your network, and it's all open source!
|
||||||
|
|
||||||
* [haste-client](https://github.com/seejohnrun/haste-client)
|
* [haste-server](https://git.webionite.com/Webionite/haste-server)
|
||||||
* [haste-server](https://github.com/seejohnrun/haste-server)
|
|
||||||
|
|
||||||
## Author
|
## Author
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
version: '3.0'
|
||||||
|
services:
|
||||||
|
haste-server:
|
||||||
|
build: .
|
||||||
|
networks:
|
||||||
|
- db-network
|
||||||
|
environment:
|
||||||
|
- STORAGE_TYPE=memcached
|
||||||
|
- STORAGE_HOST=memcached
|
||||||
|
- STORAGE_PORT=11211
|
||||||
|
ports:
|
||||||
|
- 7777:7777
|
||||||
|
memcached:
|
||||||
|
image: memcached:latest
|
||||||
|
networks:
|
||||||
|
- db-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
db-network:
|
|
@ -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: PORT,
|
||||||
|
|
||||||
|
keyLength: KEY_LENGTH,
|
||||||
|
|
||||||
|
maxLength: MAX_LENGTH,
|
||||||
|
|
||||||
|
staticMaxAge: 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: STORAGE_PORT,
|
||||||
|
expire: 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 "$@"
|
|
@ -16,34 +16,56 @@ var DocumentHandler = function(options) {
|
||||||
DocumentHandler.defaultKeyLength = 10;
|
DocumentHandler.defaultKeyLength = 10;
|
||||||
|
|
||||||
// Handle retrieving a document
|
// Handle retrieving a document
|
||||||
DocumentHandler.prototype.handleGet = function(key, response, skipExpire) {
|
DocumentHandler.prototype.handleGet = function(request, response, config) {
|
||||||
|
const key = request.params.id.split('.')[0];
|
||||||
|
const skipExpire = !!config.documents[key];
|
||||||
|
|
||||||
this.store.get(key, function(ret) {
|
this.store.get(key, function(ret) {
|
||||||
if (ret) {
|
if (ret) {
|
||||||
winston.verbose('retrieved document', { key: key });
|
winston.verbose('retrieved document', { key: key });
|
||||||
response.writeHead(200, { 'content-type': 'application/json' });
|
response.writeHead(200, { 'content-type': 'application/json' });
|
||||||
|
if (request.method === 'HEAD') {
|
||||||
|
response.end();
|
||||||
|
} else {
|
||||||
response.end(JSON.stringify({ data: ret, key: key }));
|
response.end(JSON.stringify({ data: ret, key: key }));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
winston.warn('document not found', { key: key });
|
winston.warn('document not found', { key: key });
|
||||||
response.writeHead(404, { 'content-type': 'application/json' });
|
response.writeHead(404, { 'content-type': 'application/json' });
|
||||||
|
if (request.method === 'HEAD') {
|
||||||
|
response.end();
|
||||||
|
} else {
|
||||||
response.end(JSON.stringify({ message: 'Document not found.' }));
|
response.end(JSON.stringify({ message: 'Document not found.' }));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}, skipExpire);
|
}, skipExpire);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle retrieving the raw version of a document
|
// Handle retrieving the raw version of a document
|
||||||
DocumentHandler.prototype.handleRawGet = function(key, response, skipExpire) {
|
DocumentHandler.prototype.handleRawGet = function(request, response, config) {
|
||||||
|
const key = request.params.id.split('.')[0];
|
||||||
|
const skipExpire = !!config.documents[key];
|
||||||
|
|
||||||
this.store.get(key, function(ret) {
|
this.store.get(key, function(ret) {
|
||||||
if (ret) {
|
if (ret) {
|
||||||
winston.verbose('retrieved raw document', { key: key });
|
winston.verbose('retrieved raw document', { key: key });
|
||||||
response.writeHead(200, { 'content-type': 'text/plain' });
|
response.writeHead(200, { 'content-type': 'text/plain; charset=UTF-8' });
|
||||||
|
if (request.method === 'HEAD') {
|
||||||
|
response.end();
|
||||||
|
} else {
|
||||||
response.end(ret);
|
response.end(ret);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
winston.warn('raw document not found', { key: key });
|
winston.warn('raw document not found', { key: key });
|
||||||
response.writeHead(404, { 'content-type': 'application/json' });
|
response.writeHead(404, { 'content-type': 'application/json' });
|
||||||
|
if (request.method === 'HEAD') {
|
||||||
|
response.end();
|
||||||
|
} else {
|
||||||
response.end(JSON.stringify({ message: 'Document not found.' }));
|
response.end(JSON.stringify({ message: 'Document not found.' }));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}, skipExpire);
|
}, skipExpire);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -69,8 +91,7 @@ DocumentHandler.prototype.handlePost = function (request, response) {
|
||||||
_this.chooseKey(function (key) {
|
_this.chooseKey(function (key) {
|
||||||
_this.store.set(key, buffer, function (res) {
|
_this.store.set(key, buffer, function (res) {
|
||||||
if (res) {
|
if (res) {
|
||||||
var ip = request.headers['x-forwarded-for'] || request.ip;
|
winston.verbose('added document', { key: key });
|
||||||
winston.verbose('added document', { key: key, ip: ip });
|
|
||||||
response.writeHead(200, { 'content-type': 'application/json' });
|
response.writeHead(200, { 'content-type': 'application/json' });
|
||||||
response.end(JSON.stringify({ key: key }));
|
response.end(JSON.stringify({ key: key }));
|
||||||
}
|
}
|
||||||
|
@ -124,7 +145,7 @@ DocumentHandler.prototype.chooseKey = function(callback) {
|
||||||
} else {
|
} else {
|
||||||
callback(key);
|
callback(key);
|
||||||
}
|
}
|
||||||
});
|
}, true); // Don't bump expirations when key searching
|
||||||
};
|
};
|
||||||
|
|
||||||
DocumentHandler.prototype.acceptableKey = function() {
|
DocumentHandler.prototype.acceptableKey = function() {
|
||||||
|
|
|
@ -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');
|
const memcached = require('memcached');
|
||||||
var winston = require('winston');
|
const winston = require('winston');
|
||||||
|
|
||||||
|
class MemcachedDocumentStore {
|
||||||
|
|
||||||
// Create a new store with options
|
// Create a new store with options
|
||||||
var MemcachedDocumentStore = function(options) {
|
constructor(options) {
|
||||||
this.expire = options.expire;
|
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
|
// Create a connection
|
||||||
MemcachedDocumentStore.connect = function(options) {
|
connect(url) {
|
||||||
var host = options.host || '127.0.0.1';
|
this.client = new memcached(url);
|
||||||
var port = options.port || 11211;
|
|
||||||
this.client = new memcached.Client(port, host);
|
winston.info(`connecting to memcached on ${url}`);
|
||||||
this.client.connect();
|
|
||||||
this.client.on('connect', function() {
|
this.client.on('failure', function(error) {
|
||||||
winston.info('connected to memcached on ' + host + ':' + port);
|
winston.info('error connecting to memcached', {error});
|
||||||
});
|
});
|
||||||
this.client.on('error', function(e) {
|
}
|
||||||
winston.info('error connecting to memcached', { error: e });
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Save file in a key
|
// Save file in a key
|
||||||
MemcachedDocumentStore.prototype.set =
|
set(key, data, callback, skipExpire) {
|
||||||
function(key, data, callback, skipExpire) {
|
this.client.set(key, data, skipExpire ? 0 : this.expire || 0, (error) => {
|
||||||
MemcachedDocumentStore.client.set(key, data, function(err, reply) {
|
callback(!error);
|
||||||
err ? callback(false) : callback(true);
|
});
|
||||||
}, skipExpire ? 0 : this.expire);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Get a file from a key
|
// Get a file from a key
|
||||||
MemcachedDocumentStore.prototype.get = function(key, callback, skipExpire) {
|
get(key, callback, skipExpire) {
|
||||||
var _this = this;
|
this.client.get(key, (error, data) => {
|
||||||
MemcachedDocumentStore.client.get(key, function(err, reply) {
|
const value = error ? false : data;
|
||||||
callback(err ? false : reply);
|
|
||||||
if (_this.expire && !skipExpire) {
|
callback(value);
|
||||||
winston.warn('store does not currently push forward expirations on GET');
|
|
||||||
|
// 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;
|
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;
|
|
@ -1,14 +1,16 @@
|
||||||
/*global require,module,process*/
|
/*global require,module,process*/
|
||||||
|
|
||||||
var postgres = require('pg');
|
|
||||||
var winston = require('winston');
|
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));
|
// create table entries (id serial primary key, key varchar(255) not null, value text not null, expiration int, unique(key));
|
||||||
|
|
||||||
// A postgres document store
|
// A postgres document store
|
||||||
var PostgresDocumentStore = function (options) {
|
var PostgresDocumentStore = function (options) {
|
||||||
this.expireJS = options.expire;
|
this.expireJS = options.expire;
|
||||||
this.connectionUrl = process.env.DATABASE_URL || options.connectionUrl;
|
|
||||||
|
const connectionString = process.env.DATABASE_URL || options.connectionUrl;
|
||||||
|
this.pool = new Pool({connectionString});
|
||||||
};
|
};
|
||||||
|
|
||||||
PostgresDocumentStore.prototype = {
|
PostgresDocumentStore.prototype = {
|
||||||
|
@ -23,7 +25,7 @@ PostgresDocumentStore.prototype = {
|
||||||
key,
|
key,
|
||||||
data,
|
data,
|
||||||
that.expireJS && !skipExpire ? that.expireJS + now : null
|
that.expireJS && !skipExpire ? that.expireJS + now : null
|
||||||
], function (err, result) {
|
], function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
winston.error('error persisting value to postgres', { error: err });
|
winston.error('error persisting value to postgres', { error: err });
|
||||||
return callback(false);
|
return callback(false);
|
||||||
|
@ -50,7 +52,7 @@ PostgresDocumentStore.prototype = {
|
||||||
client.query('UPDATE entries SET expiration = $1 WHERE ID = $2', [
|
client.query('UPDATE entries SET expiration = $1 WHERE ID = $2', [
|
||||||
that.expireJS + now,
|
that.expireJS + now,
|
||||||
result.rows[0].id
|
result.rows[0].id
|
||||||
], function (err, result) {
|
], function (err) {
|
||||||
if (!err) {
|
if (!err) {
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
|
@ -64,16 +66,15 @@ PostgresDocumentStore.prototype = {
|
||||||
|
|
||||||
// A connection wrapper
|
// A connection wrapper
|
||||||
safeConnect: function (callback) {
|
safeConnect: function (callback) {
|
||||||
postgres.connect(this.connectionUrl, function (err, client, done) {
|
this.pool.connect((error, client, done) => {
|
||||||
if (err) {
|
if (error) {
|
||||||
winston.error('error connecting to postgres', { error: err });
|
winston.error('error connecting to postgres', {error});
|
||||||
callback(err);
|
callback(error);
|
||||||
} else {
|
} else {
|
||||||
callback(undefined, client, done);
|
callback(undefined, client, done);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = PostgresDocumentStore;
|
module.exports = PostgresDocumentStore;
|
||||||
|
|
|
@ -29,7 +29,12 @@ RedisDocumentStore.connect = function(options) {
|
||||||
if (options.password) {
|
if (options.password) {
|
||||||
RedisDocumentStore.client.auth(options.password);
|
RedisDocumentStore.client.auth(options.password);
|
||||||
}
|
}
|
||||||
RedisDocumentStore.client.select(index, function(err, reply) {
|
|
||||||
|
RedisDocumentStore.client.on('error', function(err) {
|
||||||
|
winston.error('redis disconnected', err);
|
||||||
|
});
|
||||||
|
|
||||||
|
RedisDocumentStore.client.select(index, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
winston.error(
|
winston.error(
|
||||||
'error connecting to redis index ' + index,
|
'error connecting to redis index ' + index,
|
||||||
|
@ -46,7 +51,7 @@ RedisDocumentStore.connect = function(options) {
|
||||||
// Save file in a key
|
// Save file in a key
|
||||||
RedisDocumentStore.prototype.set = function(key, data, callback, skipExpire) {
|
RedisDocumentStore.prototype.set = function(key, data, callback, skipExpire) {
|
||||||
var _this = this;
|
var _this = this;
|
||||||
RedisDocumentStore.client.set(key, data, function(err, reply) {
|
RedisDocumentStore.client.set(key, data, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(false);
|
callback(false);
|
||||||
}
|
}
|
||||||
|
@ -62,7 +67,7 @@ RedisDocumentStore.prototype.set = function(key, data, callback, skipExpire) {
|
||||||
// Expire a key in expire time if set
|
// Expire a key in expire time if set
|
||||||
RedisDocumentStore.prototype.setExpiration = function(key) {
|
RedisDocumentStore.prototype.setExpiration = function(key) {
|
||||||
if (this.expire) {
|
if (this.expire) {
|
||||||
RedisDocumentStore.client.expire(key, this.expire, function(err, reply) {
|
RedisDocumentStore.client.expire(key, this.expire, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
winston.error('failed to set expiry on key: ' + key);
|
winston.error('failed to set expiry on key: ' + key);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,33 +1,27 @@
|
||||||
// Draws inspiration from pwgen and http://tools.arantius.com/password
|
// Draws inspiration from pwgen and http://tools.arantius.com/password
|
||||||
var PhoneticKeyGenerator = function(options) {
|
|
||||||
// No options
|
const randOf = (collection) => {
|
||||||
|
return () => {
|
||||||
|
return collection[Math.floor(Math.random() * collection.length)];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generate a phonetic key
|
// Helper methods to get an random vowel or consonant
|
||||||
PhoneticKeyGenerator.prototype.createKey = function(keyLength) {
|
const randVowel = randOf('aeiou');
|
||||||
var text = '';
|
const randConsonant = randOf('bcdfghjklmnpqrstvwxyz');
|
||||||
var start = Math.round(Math.random());
|
|
||||||
for (var i = 0; i < keyLength; i++) {
|
module.exports = class PhoneticKeyGenerator {
|
||||||
text += (i % 2 == start) ? this.randConsonant() : this.randVowel();
|
|
||||||
|
// Generate a phonetic key of alternating consonant & vowel
|
||||||
|
createKey(keyLength) {
|
||||||
|
let text = '';
|
||||||
|
const start = Math.round(Math.random());
|
||||||
|
|
||||||
|
for (let i = 0; i < keyLength; i++) {
|
||||||
|
text += (i % 2 == start) ? randConsonant() : randVowel();
|
||||||
}
|
}
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
PhoneticKeyGenerator.consonants = 'bcdfghjklmnpqrstvwxyz';
|
|
||||||
PhoneticKeyGenerator.vowels = 'aeiou';
|
|
||||||
|
|
||||||
// Get an random vowel
|
|
||||||
PhoneticKeyGenerator.prototype.randVowel = function() {
|
|
||||||
return PhoneticKeyGenerator.vowels[
|
|
||||||
Math.floor(Math.random() * PhoneticKeyGenerator.vowels.length)
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get an random consonant
|
|
||||||
PhoneticKeyGenerator.prototype.randConsonant = function() {
|
|
||||||
return PhoneticKeyGenerator.consonants[
|
|
||||||
Math.floor(Math.random() * PhoneticKeyGenerator.consonants.length)
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = PhoneticKeyGenerator;
|
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
var RandomKeyGenerator = function(options) {
|
module.exports = class RandomKeyGenerator {
|
||||||
if (!options) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
this.keyspace = options.keyspace || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generate a random key
|
// Initialize a new generator with the given keySpace
|
||||||
RandomKeyGenerator.prototype.createKey = function(keyLength) {
|
constructor(options = {}) {
|
||||||
|
this.keyspace = options.keyspace || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a key of the given length
|
||||||
|
createKey(keyLength) {
|
||||||
var text = '';
|
var text = '';
|
||||||
var index;
|
|
||||||
for (var i = 0; i < keyLength; i++) {
|
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);
|
text += this.keyspace.charAt(index);
|
||||||
}
|
}
|
||||||
return text;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = RandomKeyGenerator;
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
22
package.json
22
package.json
|
@ -14,23 +14,21 @@
|
||||||
},
|
},
|
||||||
"main": "haste",
|
"main": "haste",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"busboy": "0.2.4",
|
||||||
|
"connect": "^3.7.0",
|
||||||
"connect-ratelimit": "0.0.7",
|
"connect-ratelimit": "0.0.7",
|
||||||
"connect-route": "0.1.5",
|
"connect-route": "0.1.5",
|
||||||
"connect": "3.4.1",
|
"pg": "^8.0.0",
|
||||||
"st": "1.1.0",
|
"redis": "0.8.1",
|
||||||
"winston": "0.6.2",
|
"redis-url": "0.1.0",
|
||||||
"uglify-js": "1.3.3",
|
"st": "^2.0.0",
|
||||||
"busboy": "0.2.4",
|
"uglify-js": "3.1.6",
|
||||||
"pg": "4.1.1"
|
"winston": "^2.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"mocha": "*",
|
"mocha": "^8.1.3"
|
||||||
"should": "*"
|
|
||||||
},
|
},
|
||||||
"bundledDependencies": [],
|
"bundledDependencies": [],
|
||||||
"engines": {
|
|
||||||
"node": "0.10.35"
|
|
||||||
},
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"haste-server": "./server.js"
|
"haste-server": "./server.js"
|
||||||
},
|
},
|
||||||
|
@ -44,6 +42,6 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node server.js",
|
"start": "node server.js",
|
||||||
"test": "mocha -r should spec/*"
|
"test": "mocha --recursive"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,8 +33,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"storage": {
|
"storage": {
|
||||||
"type": "postgres",
|
"type": "file"
|
||||||
"expire": 2592000
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"documents": {
|
"documents": {
|
62
server.js
62
server.js
|
@ -1,7 +1,8 @@
|
||||||
|
// vim: set ts=2 sw=2 sts=2 et:
|
||||||
var http = require('http');
|
var http = require('http');
|
||||||
var url = require('url');
|
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
|
|
||||||
|
var uglify = require('uglify-js');
|
||||||
var winston = require('winston');
|
var winston = require('winston');
|
||||||
var connect = require('connect');
|
var connect = require('connect');
|
||||||
var route = require('connect-route');
|
var route = require('connect-route');
|
||||||
|
@ -11,7 +12,7 @@ var connect_rate_limit = require('connect-ratelimit');
|
||||||
var DocumentHandler = require('./lib/document_handler');
|
var DocumentHandler = require('./lib/document_handler');
|
||||||
|
|
||||||
// Load the configuration and set some defaults
|
// Load the configuration and set some defaults
|
||||||
var config = JSON.parse(fs.readFileSync('./config.js', 'utf8'));
|
var config = require('./config.json');
|
||||||
config.port = process.env.PORT || config.port || 7777;
|
config.port = process.env.PORT || config.port || 7777;
|
||||||
config.host = process.env.HOST || config.host || 'localhost';
|
config.host = process.env.HOST || config.host || 'localhost';
|
||||||
|
|
||||||
|
@ -19,7 +20,10 @@ config.host = process.env.HOST || config.host || 'localhost';
|
||||||
if (config.logging) {
|
if (config.logging) {
|
||||||
try {
|
try {
|
||||||
winston.remove(winston.transports.Console);
|
winston.remove(winston.transports.Console);
|
||||||
} catch(er) { }
|
} catch(e) {
|
||||||
|
/* was not present */
|
||||||
|
}
|
||||||
|
|
||||||
var detail, type;
|
var detail, type;
|
||||||
for (var i = 0; i < config.logging.length; i++) {
|
for (var i = 0; i < config.logging.length; i++) {
|
||||||
detail = config.logging[i];
|
detail = config.logging[i];
|
||||||
|
@ -52,21 +56,14 @@ else {
|
||||||
|
|
||||||
// Compress the static javascript assets
|
// Compress the static javascript assets
|
||||||
if (config.recompressStaticAssets) {
|
if (config.recompressStaticAssets) {
|
||||||
var jsp = require("uglify-js").parser;
|
|
||||||
var pro = require("uglify-js").uglify;
|
|
||||||
var list = fs.readdirSync('./static');
|
var list = fs.readdirSync('./static');
|
||||||
for (var i = 0; i < list.length; i++) {
|
for (var j = 0; j < list.length; j++) {
|
||||||
var item = list[i];
|
var item = list[j];
|
||||||
var orig_code, ast;
|
if ((item.indexOf('.js') === item.length - 3) && (item.indexOf('.min.js') === -1)) {
|
||||||
if ((item.indexOf('.js') === item.length - 3) &&
|
var dest = item.substring(0, item.length - 3) + '.min' + item.substring(item.length - 3);
|
||||||
(item.indexOf('.min.js') === -1)) {
|
var orig_code = fs.readFileSync('./static/' + item, 'utf8');
|
||||||
dest = item.substring(0, item.length - 3) + '.min' +
|
|
||||||
item.substring(item.length - 3);
|
fs.writeFileSync('./static/' + dest, uglify.minify(orig_code).code, 'utf8');
|
||||||
orig_code = fs.readFileSync('./static/' + item, 'utf8');
|
|
||||||
ast = jsp.parse(orig_code);
|
|
||||||
ast = pro.ast_mangle(ast);
|
|
||||||
ast = pro.ast_squeeze(ast);
|
|
||||||
fs.writeFileSync('./static/' + dest, pro.gen_code(ast), 'utf8');
|
|
||||||
winston.info('compressed ' + item + ' into ' + dest);
|
winston.info('compressed ' + item + ' into ' + dest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,23 +107,30 @@ if (config.rateLimits) {
|
||||||
app.use(connect_rate_limit(config.rateLimits));
|
app.use(connect_rate_limit(config.rateLimits));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function raw(request, response) {
|
||||||
|
return documentHandler.handleRawGet(request, response, config);
|
||||||
|
}
|
||||||
|
|
||||||
// first look at API calls
|
// first look at API calls
|
||||||
app.use(route(function(router) {
|
app.use(route(function(router) {
|
||||||
// get raw documents - support getting with extension
|
// get raw documents - support getting with extension
|
||||||
router.get('/raw/:id', function(request, response, next) {
|
router.get('/raw/:id', raw);
|
||||||
var key = request.params.id.split('.')[0];
|
router.get('/:id/raw', raw);
|
||||||
var skipExpire = !!config.documents[key];
|
router.head('/raw/:id', raw);
|
||||||
return documentHandler.handleRawGet(key, response, skipExpire);
|
router.head('/:id/raw', raw);
|
||||||
});
|
|
||||||
// add documents
|
// add documents
|
||||||
router.post('/documents', function(request, response, next) {
|
|
||||||
|
router.post('/documents', function(request, response) {
|
||||||
return documentHandler.handlePost(request, response);
|
return documentHandler.handlePost(request, response);
|
||||||
});
|
});
|
||||||
|
|
||||||
// get documents
|
// get documents
|
||||||
router.get('/documents/:id', function(request, response, next) {
|
router.get('/documents/:id', function(request, response) {
|
||||||
var key = request.params.id.split('.')[0];
|
return documentHandler.handleGet(request, response, config);
|
||||||
var skipExpire = !!config.documents[key];
|
});
|
||||||
return documentHandler.handleGet(key, response, skipExpire);
|
|
||||||
|
router.head('/documents/:id', function(request, response) {
|
||||||
|
return documentHandler.handleGet(request, response, config);
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -142,8 +146,12 @@ app.use(connect_st({
|
||||||
// so route it back to /
|
// so route it back to /
|
||||||
app.use(route(function(router) {
|
app.use(route(function(router) {
|
||||||
router.get('/:id', function(request, response, next) {
|
router.get('/:id', function(request, response, next) {
|
||||||
|
if (request.headers.accept && request.headers.accept.includes('html')) {
|
||||||
request.sturl = '/';
|
request.sturl = '/';
|
||||||
next();
|
next();
|
||||||
|
} else {
|
||||||
|
return raw(request, response);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
body {
|
body {
|
||||||
background: #002B36;
|
background: #fcfcfc;
|
||||||
padding: 20px 50px;
|
padding: 20px 50px;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ body {
|
||||||
textarea {
|
textarea {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 0px;
|
border: 0px;
|
||||||
color: #fff;
|
color: #000;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -17,6 +17,8 @@ textarea {
|
||||||
outline: none;
|
outline: none;
|
||||||
resize: none;
|
resize: none;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* the line numbers */
|
/* the line numbers */
|
||||||
|
@ -25,12 +27,13 @@ textarea {
|
||||||
color: #7d7d7d;
|
color: #7d7d7d;
|
||||||
z-index: -1000;
|
z-index: -1000;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 20px;
|
/* top: 20px; */
|
||||||
left: 0px;
|
left: 0px;
|
||||||
width: 30px; /* 30 to get 20 away from box */
|
width: 30px; /* 30 to get 20 away from box */
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* code box when locked */
|
/* code box when locked */
|
||||||
|
@ -42,8 +45,8 @@ textarea {
|
||||||
border: 0px;
|
border: 0px;
|
||||||
outline: none;
|
outline: none;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
padding-right: 360px;
|
|
||||||
overflow: inherit;
|
overflow: inherit;
|
||||||
|
background: #fcfcfc;
|
||||||
}
|
}
|
||||||
|
|
||||||
#box code {
|
#box code {
|
||||||
|
@ -54,32 +57,35 @@ textarea {
|
||||||
/* key */
|
/* key */
|
||||||
|
|
||||||
#key {
|
#key {
|
||||||
position: fixed;
|
display: flex;
|
||||||
top: 0px;
|
justify-content: space-between;
|
||||||
right: 0px;
|
width: 100%;
|
||||||
z-index: +1000; /* watch out */
|
position: sticky;
|
||||||
|
flex-wrap: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
#box1 {
|
#box1 {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background: #00222b;
|
background: #fcfcfc;
|
||||||
}
|
}
|
||||||
|
|
||||||
#box2 {
|
#box2 {
|
||||||
background: #08323c;
|
display: flex;
|
||||||
|
justify-content: right;
|
||||||
|
background: #fcfcfc;
|
||||||
font-size: 0px;
|
font-size: 0px;
|
||||||
padding: 0px 5px;
|
padding: 0px 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#box1 a.logo, #box1 a.logo:visited {
|
a.logo, a.logo:visited {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background: url(logo.png);
|
background: url(logo.png);
|
||||||
width: 126px;
|
width: 126px;
|
||||||
|
min-width: 126px;
|
||||||
height: 42px;
|
height: 42px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#box1 a.logo:hover {
|
a.logo:hover {
|
||||||
background-position: 0 bottom;
|
background-position: 0 bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,18 +119,26 @@ textarea {
|
||||||
}
|
}
|
||||||
|
|
||||||
#box3, #messages li {
|
#box3, #messages li {
|
||||||
background: #173e48;
|
position: absolute;
|
||||||
|
right: 100px;
|
||||||
|
background: #fcfcfc;
|
||||||
font-family: Helvetica, sans-serif;
|
font-family: Helvetica, sans-serif;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 14px;
|
line-height: 14px;
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#box3 .label, #messages li {
|
#box3 .label {
|
||||||
color: #fff;
|
color: #000;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#messages li {
|
||||||
|
color: #FFF;
|
||||||
|
right: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
#box3 .shortcut {
|
#box3 .shortcut {
|
||||||
color: #c4dce3;
|
color: #c4dce3;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
@ -152,16 +166,12 @@ textarea {
|
||||||
#box2 .button-picture{ border-width: 0; font-size: inherit; }
|
#box2 .button-picture{ border-width: 0; font-size: inherit; }
|
||||||
|
|
||||||
#messages {
|
#messages {
|
||||||
position:fixed;
|
|
||||||
top:0px;
|
|
||||||
right:138px;
|
|
||||||
margin:0;
|
margin:0;
|
||||||
padding:0;
|
padding:0;
|
||||||
width:400px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#messages li {
|
#messages li {
|
||||||
background:rgba(23,62,72,0.8);
|
background:rgba(252,252,252,0.8);
|
||||||
margin:0 auto;
|
margin:0 auto;
|
||||||
list-style:none;
|
list-style:none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
/* global $, hljs, window, document */
|
||||||
|
|
||||||
///// represents a single document
|
///// represents a single document
|
||||||
|
|
||||||
var haste_document = function() {
|
var haste_document = function() {
|
||||||
|
@ -42,10 +44,10 @@ haste_document.prototype.load = function(key, callback, lang) {
|
||||||
value: high.value,
|
value: high.value,
|
||||||
key: key,
|
key: key,
|
||||||
language: high.language || lang,
|
language: high.language || lang,
|
||||||
lineCount: res.data.split("\n").length
|
lineCount: res.data.split('\n').length
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
error: function(err) {
|
error: function() {
|
||||||
callback(false);
|
callback(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -62,7 +64,7 @@ haste_document.prototype.save = function(data, callback) {
|
||||||
type: 'post',
|
type: 'post',
|
||||||
data: data,
|
data: data,
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
contentType: 'application/json; charset=utf-8',
|
contentType: 'text/plain; charset=utf-8',
|
||||||
success: function(res) {
|
success: function(res) {
|
||||||
_this.locked = true;
|
_this.locked = true;
|
||||||
_this.key = res.key;
|
_this.key = res.key;
|
||||||
|
@ -71,7 +73,7 @@ haste_document.prototype.save = function(data, callback) {
|
||||||
value: high.value,
|
value: high.value,
|
||||||
key: res.key,
|
key: res.key,
|
||||||
language: high.language,
|
language: high.language,
|
||||||
lineCount: data.split("\n").length
|
lineCount: data.split('\n').length
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
error: function(res) {
|
error: function(res) {
|
||||||
|
@ -168,8 +170,7 @@ haste.extensionMap = {
|
||||||
lua: 'lua', pas: 'delphi', java: 'java', cpp: 'cpp', cc: 'cpp', m: 'objectivec',
|
lua: 'lua', pas: 'delphi', java: 'java', cpp: 'cpp', cc: 'cpp', m: 'objectivec',
|
||||||
vala: 'vala', sql: 'sql', sm: 'smalltalk', lisp: 'lisp', ini: 'ini',
|
vala: 'vala', sql: 'sql', sm: 'smalltalk', lisp: 'lisp', ini: 'ini',
|
||||||
diff: 'diff', bash: 'bash', sh: 'bash', tex: 'tex', erl: 'erlang', hs: 'haskell',
|
diff: 'diff', bash: 'bash', sh: 'bash', tex: 'tex', erl: 'erlang', hs: 'haskell',
|
||||||
md: 'markdown', txt: '', coffee: 'coffee', json: 'javascript',
|
md: 'markdown', txt: '', coffee: 'coffee', swift: 'swift'
|
||||||
swift: 'swift'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Look up the extension preferred for a type
|
// Look up the extension preferred for a type
|
||||||
|
@ -276,7 +277,7 @@ haste.prototype.configureButtons = function() {
|
||||||
$where: $('#box2 .new'),
|
$where: $('#box2 .new'),
|
||||||
label: 'New',
|
label: 'New',
|
||||||
shortcut: function(evt) {
|
shortcut: function(evt) {
|
||||||
return evt.ctrlKey && evt.keyCode === 78
|
return evt.ctrlKey && evt.keyCode === 78;
|
||||||
},
|
},
|
||||||
shortcutDescription: 'control + n',
|
shortcutDescription: 'control + n',
|
||||||
action: function() {
|
action: function() {
|
||||||
|
@ -331,14 +332,14 @@ haste.prototype.configureButton = function(options) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Show the label
|
// Show the label
|
||||||
options.$where.mouseenter(function(evt) {
|
options.$where.mouseenter(function() {
|
||||||
$('#box3 .label').text(options.label);
|
$('#box3 .label').text(options.label);
|
||||||
$('#box3 .shortcut').text(options.shortcutDescription || '');
|
$('#box3 .shortcut').text(options.shortcutDescription || '');
|
||||||
$('#box3').show();
|
$('#box3').show();
|
||||||
$(this).append($('#pointer').remove().show());
|
$(this).append($('#pointer').remove().show());
|
||||||
});
|
});
|
||||||
// Hide the label
|
// Hide the label
|
||||||
options.$where.mouseleave(function(evt) {
|
options.$where.mouseleave(function() {
|
||||||
$('#box3').hide();
|
$('#box3').hide();
|
||||||
$('#pointer').hide();
|
$('#pointer').hide();
|
||||||
});
|
});
|
||||||
|
@ -371,7 +372,7 @@ $(function() {
|
||||||
// For browsers like Internet Explorer
|
// For browsers like Internet Explorer
|
||||||
if (document.selection) {
|
if (document.selection) {
|
||||||
this.focus();
|
this.focus();
|
||||||
sel = document.selection.createRange();
|
var sel = document.selection.createRange();
|
||||||
sel.text = myValue;
|
sel.text = myValue;
|
||||||
this.focus();
|
this.focus();
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 14 KiB |
File diff suppressed because one or more lines are too long
|
@ -1,13 +1,12 @@
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
|
<title>Webionite hastebin</title>
|
||||||
<title>hastebin</title>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="stylesheet" type="text/css" href="solarized_dark.css"/>
|
<link rel="stylesheet" type="text/css" href="tomorrow.css"/>
|
||||||
<link rel="stylesheet" type="text/css" href="application.css"/>
|
<link rel="stylesheet" type="text/css" href="application.css"/>
|
||||||
|
|
||||||
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
|
<script type="text/javascript" src="jquery.min.js"></script>
|
||||||
<script type="text/javascript" src="highlight.min.js"></script>
|
<script type="text/javascript" src="highlight.min.js"></script>
|
||||||
<script type="text/javascript" src="application.min.js"></script>
|
<script type="text/javascript" src="application.min.js"></script>
|
||||||
|
|
||||||
|
@ -39,30 +38,27 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<ul id="messages"></ul>
|
|
||||||
|
|
||||||
<div id="key">
|
<div id="key">
|
||||||
<div id="pointer" style="display:none;"></div>
|
|
||||||
<div id="box1">
|
|
||||||
<a href="/about.md" class="logo"></a>
|
<a href="/about.md" class="logo"></a>
|
||||||
</div>
|
<div>
|
||||||
|
<div id="pointer" style="display:none;"></div>
|
||||||
<div id="box2">
|
<div id="box2">
|
||||||
<button class="save function button-picture">Save</button>
|
<button class="save function button-picture">Save</button>
|
||||||
<button class="new function button-picture">New</button>
|
<button class="new function button-picture">New</button>
|
||||||
<button class="duplicate function button-picture">Duplicate & Edit</button>
|
<button class="duplicate function button-picture">Duplicate & Edit</button>
|
||||||
<button class="raw function button-picture">Just Text</button>
|
<button class="raw function button-picture">Just Text</button>
|
||||||
<button class="twitter function button-picture">Twitter</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="box3" style="display:none;">
|
<div id="box3" style="display:none;">
|
||||||
<div class="label"></div>
|
<div class="label"></div>
|
||||||
<div class="shortcut"></div>
|
<div class="shortcut"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<ul id="messages"></ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="linenos"></div>
|
<div id="linenos"></div>
|
||||||
<pre id="box" style="display:none;" class="hljs" tabindex="0"><code></code></pre>
|
<pre id="box" style="display:none;" class="hljs" tabindex="0"><code></code></pre>
|
||||||
<textarea spellcheck="false" style="display:none;"></textarea>
|
<textarea spellcheck="false" style="display:none;"></textarea>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
File diff suppressed because one or more lines are too long
BIN
static/logo.png
BIN
static/logo.png
Binary file not shown.
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 5.0 KiB |
|
@ -1,84 +0,0 @@
|
||||||
/*
|
|
||||||
|
|
||||||
Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull <sourdrums@gmail.com>
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
.hljs {
|
|
||||||
display: block;
|
|
||||||
overflow-x: auto;
|
|
||||||
padding: 0.5em;
|
|
||||||
background: #002b36;
|
|
||||||
color: #839496;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-comment,
|
|
||||||
.hljs-quote {
|
|
||||||
color: #586e75;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Solarized Green */
|
|
||||||
.hljs-keyword,
|
|
||||||
.hljs-selector-tag,
|
|
||||||
.hljs-addition {
|
|
||||||
color: #859900;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Solarized Cyan */
|
|
||||||
.hljs-number,
|
|
||||||
.hljs-string,
|
|
||||||
.hljs-meta .hljs-meta-string,
|
|
||||||
.hljs-literal,
|
|
||||||
.hljs-doctag,
|
|
||||||
.hljs-regexp {
|
|
||||||
color: #2aa198;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Solarized Blue */
|
|
||||||
.hljs-title,
|
|
||||||
.hljs-section,
|
|
||||||
.hljs-name,
|
|
||||||
.hljs-selector-id,
|
|
||||||
.hljs-selector-class {
|
|
||||||
color: #268bd2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Solarized Yellow */
|
|
||||||
.hljs-attribute,
|
|
||||||
.hljs-attr,
|
|
||||||
.hljs-variable,
|
|
||||||
.hljs-template-variable,
|
|
||||||
.hljs-class .hljs-title,
|
|
||||||
.hljs-type {
|
|
||||||
color: #b58900;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Solarized Orange */
|
|
||||||
.hljs-symbol,
|
|
||||||
.hljs-bullet,
|
|
||||||
.hljs-subst,
|
|
||||||
.hljs-meta,
|
|
||||||
.hljs-meta .hljs-keyword,
|
|
||||||
.hljs-selector-attr,
|
|
||||||
.hljs-selector-pseudo,
|
|
||||||
.hljs-link {
|
|
||||||
color: #cb4b16;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Solarized Red */
|
|
||||||
.hljs-built_in,
|
|
||||||
.hljs-deletion {
|
|
||||||
color: #dc322f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-formula {
|
|
||||||
background: #073642;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-emphasis {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-strong {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
|
||||||
|
|
||||||
|
/* Tomorrow Comment */
|
||||||
|
.hljs-comment,
|
||||||
|
.hljs-quote {
|
||||||
|
color: #8e908c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tomorrow Red */
|
||||||
|
.hljs-variable,
|
||||||
|
.hljs-template-variable,
|
||||||
|
.hljs-tag,
|
||||||
|
.hljs-name,
|
||||||
|
.hljs-selector-id,
|
||||||
|
.hljs-selector-class,
|
||||||
|
.hljs-regexp,
|
||||||
|
.hljs-deletion {
|
||||||
|
color: #c82829;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tomorrow Orange */
|
||||||
|
.hljs-number,
|
||||||
|
.hljs-built_in,
|
||||||
|
.hljs-builtin-name,
|
||||||
|
.hljs-literal,
|
||||||
|
.hljs-type,
|
||||||
|
.hljs-params,
|
||||||
|
.hljs-meta,
|
||||||
|
.hljs-link {
|
||||||
|
color: #f5871f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tomorrow Yellow */
|
||||||
|
.hljs-attribute {
|
||||||
|
color: #eab700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tomorrow Green */
|
||||||
|
.hljs-string,
|
||||||
|
.hljs-symbol,
|
||||||
|
.hljs-bullet,
|
||||||
|
.hljs-addition {
|
||||||
|
color: #718c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tomorrow Blue */
|
||||||
|
.hljs-title,
|
||||||
|
.hljs-section {
|
||||||
|
color: #4271ae;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tomorrow Purple */
|
||||||
|
.hljs-keyword,
|
||||||
|
.hljs-selector-tag {
|
||||||
|
color: #8959a8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs {
|
||||||
|
display: block;
|
||||||
|
overflow-x: auto;
|
||||||
|
background: white;
|
||||||
|
color: #4d4d4c;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-emphasis {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-strong {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
|
@ -1,3 +1,7 @@
|
||||||
|
/* global describe, it */
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
var DocumentHandler = require('../lib/document_handler');
|
var DocumentHandler = require('../lib/document_handler');
|
||||||
var Generator = require('../lib/key_generators/random');
|
var Generator = require('../lib/key_generators/random');
|
||||||
|
|
||||||
|
@ -8,13 +12,13 @@ describe('document_handler', function() {
|
||||||
it('should choose a key of the proper length', function() {
|
it('should choose a key of the proper length', function() {
|
||||||
var gen = new Generator();
|
var gen = new Generator();
|
||||||
var dh = new DocumentHandler({ keyLength: 6, keyGenerator: gen });
|
var dh = new DocumentHandler({ keyLength: 6, keyGenerator: gen });
|
||||||
dh.acceptableKey().length.should.equal(6);
|
assert.equal(6, dh.acceptableKey().length);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should choose a default key length', function() {
|
it('should choose a default key length', function() {
|
||||||
var gen = new Generator();
|
var gen = new Generator();
|
||||||
var dh = new DocumentHandler({ keyGenerator: gen });
|
var dh = new DocumentHandler({ keyGenerator: gen });
|
||||||
dh.keyLength.should.equal(DocumentHandler.defaultKeyLength);
|
assert.equal(dh.keyLength, DocumentHandler.defaultKeyLength);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
|
@ -0,0 +1,33 @@
|
||||||
|
/* global describe, it */
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const Generator = require('../../lib/key_generators/dictionary');
|
||||||
|
|
||||||
|
describe('RandomKeyGenerator', function() {
|
||||||
|
describe('randomKey', 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);
|
||||||
|
});
|
||||||
|
|
||||||
|
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,27 @@
|
||||||
|
/* global describe, it */
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
const Generator = require('../../lib/key_generators/phonetic');
|
||||||
|
|
||||||
|
const vowels = 'aeiou';
|
||||||
|
const consonants = 'bcdfghjklmnpqrstvwxyz';
|
||||||
|
|
||||||
|
describe('RandomKeyGenerator', () => {
|
||||||
|
describe('randomKey', () => {
|
||||||
|
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);
|
||||||
|
|
||||||
|
assert.ok(consonants.includes(key[0]));
|
||||||
|
assert.ok(consonants.includes(key[2]));
|
||||||
|
assert.ok(vowels.includes(key[1]));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,19 @@
|
||||||
|
/* global describe, it */
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
const Generator = require('../../lib/key_generators/random');
|
||||||
|
|
||||||
|
describe('RandomKeyGenerator', () => {
|
||||||
|
describe('randomKey', () => {
|
||||||
|
it('should return a key of the proper length', () => {
|
||||||
|
const gen = new Generator();
|
||||||
|
assert.equal(6, gen.createKey(6).length);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use a key from the given keyset if given', () => {
|
||||||
|
const gen = new Generator({keyspace: 'A'});
|
||||||
|
assert.equal('AAAAAA', gen.createKey(6));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,8 +1,12 @@
|
||||||
var RedisDocumentStore = require('../lib/document_stores/redis');
|
/* global it, describe, afterEach */
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
var winston = require('winston');
|
var winston = require('winston');
|
||||||
winston.remove(winston.transports.Console);
|
winston.remove(winston.transports.Console);
|
||||||
|
|
||||||
|
var RedisDocumentStore = require('../lib/document_stores/redis');
|
||||||
|
|
||||||
describe('redis_document_store', function() {
|
describe('redis_document_store', function() {
|
||||||
|
|
||||||
/* reconnect to redis on each test */
|
/* reconnect to redis on each test */
|
||||||
|
@ -19,7 +23,7 @@ describe('redis_document_store', function() {
|
||||||
var store = new RedisDocumentStore({ expire: 10 });
|
var store = new RedisDocumentStore({ expire: 10 });
|
||||||
store.set('hello1', 'world', function() {
|
store.set('hello1', 'world', function() {
|
||||||
RedisDocumentStore.client.ttl('hello1', function(err, res) {
|
RedisDocumentStore.client.ttl('hello1', function(err, res) {
|
||||||
res.should.be.above(1);
|
assert.ok(res > 1);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -29,7 +33,7 @@ describe('redis_document_store', function() {
|
||||||
var store = new RedisDocumentStore({ expire: 10 });
|
var store = new RedisDocumentStore({ expire: 10 });
|
||||||
store.set('hello2', 'world', function() {
|
store.set('hello2', 'world', function() {
|
||||||
RedisDocumentStore.client.ttl('hello2', function(err, res) {
|
RedisDocumentStore.client.ttl('hello2', function(err, res) {
|
||||||
res.should.equal(-1);
|
assert.equal(-1, res);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
}, true);
|
}, true);
|
||||||
|
@ -37,9 +41,9 @@ describe('redis_document_store', function() {
|
||||||
|
|
||||||
it('should not set an expiration when expiration is off', function(done) {
|
it('should not set an expiration when expiration is off', function(done) {
|
||||||
var store = new RedisDocumentStore({ expire: false });
|
var store = new RedisDocumentStore({ expire: false });
|
||||||
store.set('hello3', 'world', function(worked) {
|
store.set('hello3', 'world', function() {
|
||||||
RedisDocumentStore.client.ttl('hello3', function(err, res) {
|
RedisDocumentStore.client.ttl('hello3', function(err, res) {
|
||||||
res.should.equal(-1);
|
assert.equal(-1, res);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
Loading…
Reference in New Issue