diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..4dceab5 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +static/vendor/**/*.js diff --git a/.eslintrc.yml b/.eslintrc.yml index 92f3504..c024fb3 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -7,29 +7,44 @@ parserOptions: extends: - eslint:recommended -# see https://eslint.org/docs/user-guide/configuring#specifying-globals -globals: - # $: false - # moment: false # TODO: use `import` to import this, and remove this exception - # global: false - # module: false - db: false - process: false # for webpack process.env variables - rules: no-console: warn no-unused-vars: [error, { argsIgnorePattern: "^_" }] - no-useless-escape: warn # TODO: remove (so this is an error) + no-useless-escape: warn # TODO: remove (so this is an error) -# Server-side: overrides: - files: [ "*.js", "models/*.js", "routes/*.js", "scripts/*.js" ] - env: - browser: false - node: true - globals: - Promise: false - module: false - require: false - rules: - no-console: off + # Client: + - files: [ "routes/*.js", "static/**/*.js" ] + globals: + $: false + ABE_API_URI: false + SUBMIT_URL: true + db: false + showdown: false + # defined and used in admin.js: + tableColumns: true + # defined in server.js: + io: true + # defined and used in board_carousel: + $activeItem: true + $carousel: true + carouselSelector: true + # defined in vendor files: + Cookies: false + Dropzone: false + rules: + # FIXME: remove these exceptions, to turn them back into errors + no-undef: warn + no-unused-vars: warn + no-redeclare: warn + # Server: + - files: [ "server.js", "models/*.js", "routes/*.js", "scripts/*.js" ] + env: + browser: false + node: true + globals: + Promise: false + module: false + require: false + rules: + no-console: off diff --git a/.markdownlintrc b/.markdownlintrc new file mode 100644 index 0000000..6d66d59 --- /dev/null +++ b/.markdownlintrc @@ -0,0 +1,10 @@ +{ + "default": true, + + "line-length": { "line_length": 1000 }, + "no-trailing-punctuation": false, + "ul-style": false, // * instead of - + + // FIXME: remove the instance of this + "no-empty-links": false +} diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..380b0d4 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +language: node_js +node_js: + - "9.3" + +cache: yarn + +script: + - yarn lint diff --git a/README.md b/README.md index 22512fa..903c36d 100644 --- a/README.md +++ b/README.md @@ -1,63 +1,83 @@ -### Table of Contents -* [Current Status](#status) - * [License](#license) - * [Future Work](#future-work) - * [Credits](#credits) -* [Make Your Own](#make-your-own) - * [Setup](#setup) - * [Development](#development) - * [Database backup & sync](#database-backup-&-sync) -* [Contributing](#contributing) - * [To-do and Bugs](#to-do) -* [Operating](#operating) -* [Directory Structure](#directory-structure) -* [API](#api) +# FUTUREboard + +[![Build Status](https://travis-ci.org/olinlibrary/futureboard.svg?branch=dev)](https://travis-ci.org/olinlibrary/futureboard?branch=dev) + +FUTUREboard is a digital signage platform for sharing of media – including +images, GIFs, and videos — supplemented by information about events happening on +campus. + +## Table of Contents + +- [Status](#status) + - [To Do](#to-do) + - [Top Priorities](#top-priorities) + - [And MORE!](#and-more) + - [Possible Extensions](#possible-extensions) +- [Make Your Own](#make-your-own) + - [Setup](#setup) + - [Development](#development) + - [Database](#database) +- [Contributing](#contributing) +- [Operating](#operating) +- [Directory Structure](#directory-structure) +- [API](#api) + - [/api/](#api) + - [/api/bobs](#apibobs) + - [/api/bobs/active](#apibobsactive) + - [/api/bobs/[bobId]](#apibobsbobid) + - [/api/bobs/[bobId]/votes](#apibobsbobidvotes) + - [/api/bobs/[bobId]/flags](#apibobsbobidflags) + - [/api/flavors](#apiflavors) + - [/api/flavors/[flavorID : flavorName]](#apiflavorsflavorid-flavorname) + - [/api/tags](#apitags) + - [/api/tags/[tagID]](#apitagstagid) + - [Socket.io](#socketio) +- [Notes on AWS](#notes-on-aws) + - [Credits](#credits) +- [License](#license) ## Status -FUTUREboard is a digital signage platform for sharing of media including Images, GIFs, Videos, supplemented by information about events happening on campus. - -### License -This project is licensed under the MIT License, a ["short and simple permissive license with conditions only requiring preservation of copyright and license notices."](https://github.com/olinlibrary/futureboard/blob/master/LICENSE) - ### To Do + #### Top Priorities -- [ ] Create Easy Means of Setting up Local Environment for frontend -- [ ] Setup Dev branch for continuous integration -- [x] Server-side resizing images and videos, EXIF-orientation fix, creating thumnails of originals -- [ ] Edit Bob page -- [ ] Unit Testing, Test-driven development -- [x] Authentication -- [ ] Rooms for different content on different screens and/or privacy associated with posts -- [x] Administration Page / "Who is moderating?" -- [ ] Logistics, Setup procedures -- [ ] Open a feedback channel / Encourage new pull requests! + +* [ ] Create Easy Means of Setting up Local Environment for frontend +* [ ] Setup Dev branch for continuous integration +* [x] Server-side resizing images and videos, EXIF-orientation fix, creating thumbnails of originals +* [ ] Edit Bob page +* [ ] Unit Testing, Test-driven development +* [x] Authentication +* [ ] Rooms for different content on different screens and/or privacy associated with posts +* [x] Administration Page / "Who is moderating?" +* [ ] Logistics, Setup procedures +* [ ] Open a feedback channel / Encourage new pull requests! #### And MORE! -- [ ] Integrate with A.B.E. to get event data (add socket emit or push notifications for new events) -- [ ] Commenting/Threading/Replying on contents -- [ ] Smoother Touch Swipe -- [ ] Support more views & clean up css(bobbles, pinterest kinda UI) -- [x] Favicon -- [ ] Better autoscroll / display of events -- [ ] Replace Materialize carousel - seek alternatives or make your own for better performance & extendability + +* [ ] Integrate with A.B.E. to get event data (add socket emit or push notifications for new events) +* [ ] Commenting/Threading/Replying on contents +* [ ] Smoother Touch Swipe +* [ ] Support more views & clean up css (bobbles, Pinterest-like UI) +* [x] Favicon +* [ ] Better autoscroll / display of events +* [ ] Replace Materialize carousel - seek alternatives or make your own for better performance & extendability #### Possible Extensions + * Commenting/Threading on content * Rooms for different content on different screens and/or privacy associated with posts * Scaling * Physical interactions with board (buttons or otherwise) * Get push notifications (socket) from ABE (talk to the ABE team) - -### Credits -This project is a product of Software of Summer 2017! Thank you to Jeff and Oliver for ongoing mentorship and to the fellow students for feedback and support. Also a big thanks to the participants of the first ever Library Potluck who interacted with the board and gave feedback. - ## Make Your Own + ### Setup + To install your own version of FUTUREboard: fork this repo, clone it so you have local access, enter the directory (`cd FORWARDboard`) and then run `npm install`, which will automatically install the npm modules listed in package.json. This project was deployed with [Heroku](https://heroku.com) using a Mongo database hosted by [mLab](https://mlab.com/). Our app is set to access the external services using keys stored as environment variables. You'll need to run the following code with each variable replaced with your real key. -``` +```shell export MONGODB_URI=""; ``` @@ -71,34 +91,39 @@ local ABE instance: run the local ABE server. 2. Set the `ABE_API_URI` environment variable to `http://localhost:3000`. -Once you've set these environment variables, you should be able to run the app locally by executing `sudo -E npm start` (which runs `node server.js` as specified in `package.json`) from the root of the repo directory. The `-E` flag allows us to use the above environment variables when running with sudo permissions. This will serve the app at [http://localhost:80](http://localhost). +Once you've set these environment variables, you should be able to run the app +locally by executing `npm start` from the root of the repo directory. (This runs +`node server.js`, as specified in `package.json`). This will serve the app at +. NOTE: You'll need to [add these as environment variables to your Heroku instance](https://devcenter.heroku.com/articles/heroku-local#set-up-your-local-environment-variables) as well. Depending on your setup, you may want to have a separate database for local vs. production, but for just getting the app running it's not a crime to use the same. -Setting up AWS: We use s3 to store media, and have lambda functions to resize images and transcode video. +Setting up AWS: We use AWS S3 to store media, and have AWS Lambda functions to resize images and transcode video. We used [aws-lambda-ffmpeg](https://github.com/binoculars/aws-lambda-ffmpeg) and [aws-lambda-image](https://github.com/ysugimoto/aws-lambda-image) for resizing the media. Install aws-lambda-ffmpeg first, because its installer fails if you try to use existing buckets. You can also install it with temporary buckets and then change the settings in the aws console. Create two buckets, an 'upload' bucket and a 'media' bucket. Users will upload to the 'upload' bucket, triggering a lambda function to process the file and save it to your 'media' bucket. Currently, images are prepended with 'img-' and video is with 'vid-' to differentiate lambda triggers. Create AWS keys for your app and set the following environment variables: -``` -export `ACCESS_KEY_ID=your access_key_id' -export 'SECRET_ACCESS_KEY=your secret_access_key' +```shell +export ACCESS_KEY_ID=your access_key_id +export SECRET_ACCESS_KEY=your secret_access_key ``` -Finally set up the 'media' bucket to publish to an SNS topic on ObjectCreate, and create an HTTPS subscription to `/aws/MediaStatusSNS`. You will have to confirm this once you spin up the heroku instance. +Finally set up the 'media' bucket to publish to an SNS topic on ObjectCreate, and create an HTTPS subscription to *url*`/aws/MediaStatusSNS`. You will have to confirm this once you spin up the heroku instance. Now that you've got these variables set, you need to get the [Heroku toolbelt](https://devcenter.heroku.com/articles/getting-started-with-nodejs#set-up) set up. Once logged in through the command line interface, run `heroku git:remote -a `. Now when you've committed changes to Github, you can [push to Heroku](https://devcenter.heroku.com/articles/getting-started-with-nodejs#push-local-changes) by running `git push heroku master`. If everything was done correctly, your app should deploy and you can access it at `.herokuapp.com`. - - ### Development + A tricky part about working with external services on your local server is that you need a publicly accessible URL for them to send packets to, so you need to have some form of staging server or tunneling software like Ngrok ([tutorial for using Ngrok with Twilio, it's pretty quick to get started!](https://www.twilio.com/blog/2013/10/test-your-webhooks-locally-with-ngrok.html)). The latter is recommended as it makes development more natural (test changes without new deployment) but is less permanent than a staging server managed by Heroku. A combination is ideal but extra work, that's all up to you! As far as database administration, a pro of using mLab is having an interface for sifting through db records and doing general management rather than through the command line. Currently our app has a db admin interface so this functionality is less necessary. ### Database -It is very likely that your local server will be running with a local mongoDB. -To export the online mLab databse, simply navigage to /scripts, then run the bash script [importDB.sh](./scripts/importDB.sh) with three input arguments listed below. -``` + +It is very likely that your local server will be running with a local MongoDB. + +To export the online mLab database: navigate to `/scripts`; then run the bash script [importDB.sh](./scripts/importDB.sh) with three input arguments listed below. + +```shell # To Run: # sh importDB.sh @@ -113,8 +138,8 @@ mongodump -h ds113063.mlab.com:13063 -d heroku_w45g6cd6 -u $1 -p $2 -o mLabDump; mongorestore --drop -d $3 mLabDump/heroku_w45g6cd6; ``` - ## Contributing + To contribute, fork this repo and clone it to your local development environment. When making edits, make sure to base off of the `dev` branch by first running `git checkout dev` and ensuring that it's up to date with this repo `git pull upstream dev`. Once you're up to date, create a new branch off `dev` with `git checkout -b ` and make any edits you'd like to make. Run `npm run lint` to verify your code formatting. @@ -123,23 +148,23 @@ Once you've made the changes you'd like to make, ensure that they are committed After all of that, go ahead and open a new pull request onto the upstream version of dev! This pull request should have a title describing the changes at a very high level and a description that gets into more details. These details should include what changes have been made and why. Then request that a contributor on the main repo review your code. Once suggestions have been made, make necessary edits and confirm once again that everything looks good. If yes, your reviewer can go ahead and merge it in! - ## Operating + First start the mongodb server: -`sudo service mongod start` or `mongod` (To restart, `sudo service mongod restart`) +`sudo service mongod start` or `mongod`. +(To restart, `sudo service mongod restart`.) Set environment variables for AWS: -`ACCESS_KEY_ID -SECRET_ACCESS_KEY` - -Once you've setup your own version of the app, run `sudo -E **nodejs server.**js` to get it running. +```shell +export ACCESS_KEY_ID= +export SECRET_ACCESS_KEY= +``` -## For Frontend, templates, css: - -To test each component of the template, I recommend that +Once you've set up your own version of the app, run `npm start` to get it running. ## Directory Structure + ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) `Backend` ![#c5f015](https://placehold.it/15/c5f015/000000?text=+) `Frontend` ![#1589F0](https://placehold.it/15/1589F0/000000?text=+) `Assets` @@ -152,7 +177,7 @@ To test each component of the template, I recommend that * [tag.js](./models/tag.js) * [wrapper.js](./models/wrapper.js) : Wrapper for all models * ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) -[routes](./routes) + [routes](./routes) * [api.js](./routes/api.js) : Routes for API * [awsapi.js](./routes/awsapi.js) : Routes/Configuration for aws services * [browser.js](./routes/browser.js) : Routes for rendering templates/views @@ -160,16 +185,16 @@ To test each component of the template, I recommend that * [sockets.js](./routes/sockets.js) : socket.io routes * ![#5d646b](https://placehold.it/15/5d646b/000000?text=+)[twilio.js](./routes/twilio.js) :twilio(MMS service) routes * ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) -[scripts](./scripts) : custom node.js scripts for mongoDB + [scripts](./scripts) : custom node.js scripts for mongoDB * [exportDB.js](./scripts/exportDB.js) : exports current mongoDB into JSON * [importDB.js](./scripts/importDB.js) : imports JSON into current mongoDB * [importDB.sh](./scripts/importDB.sh) : bash script that imports the online mLAB db * ![#5d646b](https://placehold.it/15/5d646b/000000?text=+) -[physicalInterface](./physicalInterface) : Arduino Physical Controller Inteface + [physicalInterface](./physicalInterface) : Arduino Physical Controller Inteface * [CapacitiveSensorSketch.ino](./physicalInterface/CapacitiveSensorSketch.ino) * [serialController.js](./physicalInterface/serialController.js) * ![#c5f015](https://placehold.it/15/c5f015/000000?text=+) -[templates](./templates) : html templates for pages + [templates](./templates) : html templates for pages * [admin.html](./templates/admin.html) * [api.html](./templates/api.html) * [views.html](./templates/views.html) : display all available views @@ -181,9 +206,9 @@ To test each component of the template, I recommend that * ![#5d646b](https://placehold.it/15/5d646b/000000?text=+) [controller.html](./templates/controller.html) * ![#5d646b](https://placehold.it/15/5d646b/000000?text=+) [editbob.html](./templates/editbob.html) * ![#c5f015](https://placehold.it/15/c5f015/000000?text=+) -[static](./static) + [static](./static) * ![#1589F0](https://placehold.it/15/1589F0/000000?text=+) - [vendor](./static/vendor) + [vendor](./static/vendor) * [socket.io.js](./static/vendor/socket.io.js) * [jquery.min.js](./static/vendor/jquery.min.js) * [date.min.js](./static/vendor/date.min.js) : date parser @@ -193,7 +218,7 @@ To test each component of the template, I recommend that * [materialize.min.css](./static/vendor/materialize.min.css) : Materialize Framework for css * [showdown.min.js](./static/vendor/showdown.min.js) : Markdown Parser * ![#c5f015](https://placehold.it/15/c5f015/000000?text=+) - [css](./static/css) + [css](./static/css) * fonts files (DINOT) * [admin.css](./static/css/admin.css) * [board.css](./static/css/board.css) @@ -204,94 +229,109 @@ To test each component of the template, I recommend that * [upload.css](./static/css/upload.css) * ![#5d646b](https://placehold.it/15/5d646b/000000?text=+) [controller.css](./templates/controller.css) * ![#c5f015](https://placehold.it/15/c5f015/000000?text=+) - [js](./static/js) + [js](./static/js) * [admin.js](./static/js/admin.js) * [board.js](./static/js/board.js) * [controller.js](./static/js/controller.js) * [editbob.js](./static/js/editbob.js) - * ![#1589F0](https://placehold.it/15/1589F0/000000?text=+) -[package.json](./package.json) : npm module information + [package.json](./package.json) : npm module information * ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) -[server.js](./server.js) : contains server logics, run this to start node server + [server.js](./server.js) : contains server logics, run this to start node server +## API +~Strikethrough~ indicates unimplemented functionality. -## API -~Strikethrough~ indicates functionality not implemented yet -Note: Flagged bobs are never returned, except on `/bobs/flagged` +Note: Flagged bobs are never returned, except on `/bobs/flagged`. ### /api/ -| HTTP Method | Action | -| ------------- | ------------- | -| GET | retrieve this documentation | - +| HTTP Method | Action | +|-------------|-----------------------------| +| GET | retrieve this documentation | ### /api/bobs -| HTTP Method | Action | -| ------------- | ------------- | -| GET | retrieve a list of all bobs | -| POST | create a new bob object | + +| HTTP Method | Action | +|-------------|-----------------------------| +| GET | retrieve a list of all bobs | +| POST | create a new bob object | ### /api/bobs/active -| HTTP Method | Action | -| ------------- | ------------- | -| GET | retrieve a list of active bobs (The definition of active is still extremely fluid) | + +| HTTP Method | Action | +|-------------|------------------------------------------------------------------------------------| +| GET | retrieve a list of active bobs (The definition of active is still extremely fluid) | ### /api/bobs/[bobId] -| HTTP Method | Action | -| ------------- | ------------- | -| GET | retrieve bob object with id bobId | -| PUT | update the bob object | -| DELETE | delete the bob object (Requires auth) | + +| HTTP Method | Action | +|-------------|---------------------------------------| +| GET | retrieve bob object with id bobId | +| PUT | update the bob object | +| DELETE | delete the bob object (Requires auth) | ### /api/bobs/[bobId]/votes -| HTTP Method | Action | -| ------------- | ------------- | -| GET | retrieve number of votes bob with bobId has | -| POST | add one upvote to the bob | + +| HTTP Method | Action | +|-------------|---------------------------------------------| +| GET | retrieve number of votes bob with bobId has | +| POST | add one up-vote to the bob | ### /api/bobs/[bobId]/flags -| HTTP Method | Action | -| ------------- | ------------- | -| GET | retrieve flag value of bob | -| POST | flag a bob | + +| HTTP Method | Action | +|-------------|----------------------------| +| GET | retrieve flag value of bob | +| POST | flag a bob | ### /api/flavors -| HTTP Method | Action | -| ------------- | ------------- | -| GET | retrieve all flavors | -| ~POST~ | create a new flavor object | + +| HTTP Method | Action | +|-------------|----------------------------| +| GET | retrieve all flavors | +| ~POST~ | create a new flavor object | ### /api/flavors/[flavorID : flavorName] -| HTTP Method | Action | -| ------------- | ------------- | -| GET | retrieve a flavor object by name or id | -| ~PUT~ | update the flavor object | + +| HTTP Method | Action | +|-------------|----------------------------------------| +| GET | retrieve a flavor object by name or id | +| ~PUT~ | update the flavor object | ### /api/tags -| HTTP Method | Action | -| ------------- | ------------- | -| GET | retrieve all tags | -| ~POST~ | create a new tag | + +| HTTP Method | Action | +|-------------|-------------------| +| GET | retrieve all tags | +| ~POST~ | create a new tag | ### /api/tags/[tagID] -| HTTP Method | Action | -| ------------- | ------------- | -| GET | retrieve a tag by name or id | -| ~PUT~ | update the tag | +| HTTP Method | Action | +|-------------|------------------------------| +| GET | retrieve a tag by name or id | +| ~PUT~ | update the tag | + +### Socket.io + +| Socket Name | Input | Method | +|------------------|-----------------------------------|--------------------------------------| +| 'add_element' | bob object | Create a new bob | +| 'update_element' | bob object | Update the bob with the same bob id | +| 'upvote' | `{ id: bobid, votes: num_votes }` | Set bob with id `bobid` to num_votes | +| 'delete' | bob id | remove bob immediately | -## Socket.io -| Socket Name | Input | Method | -| --- | --- | --- | -| 'add_element' | bob object | Create a new bob | -| 'update_element' | bob object | Update the bob with the same bobid | -| 'upvote' | { id: bobid, votes: num_votes } | Set bob with id bobid to num_votes | -| 'delete' | bobid | remove bob immediately | +## Notes on AWS +We tried using AWS to handle media storage and transcoding, but in the future I would warn against this. We had set up two buckets - an upload bucket and a media bucket. Lambda functions were supposed to resize and transcode images and video into the media bucket, and then move the original files into a 'original/' folder in the media bucket. AWS is a big challenge, and we could not demo with this functionality because we ran into an error when adding triggers to the video transcoding lambda. Right now we use the source media on FUTUREboard, which does not work well on mobile and low power computers (like rasp pi). Google cloud allows transparent image resizing with url parameters ([docs](https://cloud.google.com/appengine/docs/standard/php/refdocs/classes/google.appengine.api.cloud_storage.CloudStorageTools#method_getImageServingUrl)). Alternatively, you can spin up a worker service on heroku to do all image and video resizing. -## Notes on AWS: -We tried using AWS to handle media storage and transcoding, but in the future I would warn against this. We had set up two buckets - an upload bucket and a media bucket. Lambda functions were supposed to resize and transcode images and video into the media bucket, and then move the original files into a 'original/' folder in the media bucket. AWS is a big challenge, and we could not demo with this functionality because we ran into an error when adding triggers to the video transcoding lambda. Right now we use the source media on futureboard, which does not work well on mobile and low power computers (like rasp pi). Google cloud allows transparent image resizing with url parameters ([docs](https://cloud.google.com/appengine/docs/standard/php/refdocs/classes/google.appengine.api.cloud_storage.CloudStorageTools#method_getImageServingUrl)). Alternatively, you can spin up a worker service on heroku to do all image and video resizing. +### Credits + +This project is a product of Software of Summer 2017! Thank you to Jeff and Oliver for ongoing mentorship and to the fellow students for feedback and support. Also a big thanks to the participants of the first ever Library Potluck who interacted with the board and gave feedback. + +## License + +This project is licensed under the MIT License, a ["short and simple permissive license with conditions only requiring preservation of copyright and license notices."](https://github.com/olinlibrary/futureboard/blob/master/LICENSE) diff --git a/lambdas/aws-lambda-ffmpeg/README.md b/lambdas/aws-lambda-ffmpeg/README.md index 072b633..776a03b 100644 --- a/lambdas/aws-lambda-ffmpeg/README.md +++ b/lambdas/aws-lambda-ffmpeg/README.md @@ -5,70 +5,85 @@ [![Known Vulnerabilities](https://snyk.io/test/github/binoculars/aws-lambda-ffmpeg/badge.svg)](https://snyk.io/test/github/binoculars/aws-lambda-ffmpeg) [![Greenkeeper badge](https://badges.greenkeeper.io/binoculars/aws-lambda-ffmpeg.svg)](https://greenkeeper.io/) -- Master: [![Build Status](https://travis-ci.org/binoculars/aws-lambda-ffmpeg.svg?branch=master)](https://travis-ci.org/binoculars/aws-lambda-ffmpeg) -- Develop: [![Build Status](https://travis-ci.org/binoculars/aws-lambda-ffmpeg.svg?branch=develop)](https://travis-ci.org/binoculars/aws-lambda-ffmpeg) +* Master: [![Build Status](https://travis-ci.org/binoculars/aws-lambda-ffmpeg.svg?branch=master)](https://travis-ci.org/binoculars/aws-lambda-ffmpeg) +* Develop: [![Build Status](https://travis-ci.org/binoculars/aws-lambda-ffmpeg.svg?branch=develop)](https://travis-ci.org/binoculars/aws-lambda-ffmpeg) -The different platforms have different naming conventions for their services. To simplify this, listed below is a *proposed* table of generalized terms that are platform-independent. +The different platforms have different naming conventions for their services. To simplify this, listed below is a _proposed_ table of generalized terms that are platform-independent. -| Term | Amazon Web Services | Microsoft Azure | Google Cloud Platform | -| --- | --- | --- | --- | -| Function | Lambda Function | Azure Function | Cloud Function | -| Storage Location | S3 Bucket | Storage Container | GCS Bucket | -| Storage Path | S3 Key | Blob Name | GCS File | +| Term | Amazon Web Services | Microsoft Azure | Google Cloud Platform | +| ---------------- | ------------------- | ----------------- | --------------------- | +| Function | Lambda Function | Azure Function | Cloud Function | +| Storage Location | S3 Bucket | Storage Container | GCS Bucket | +| Storage Path | S3 Key | Blob Name | GCS File | # Function Process Overview -1. A video file is uploaded to the source storage location -1. A notification event triggers the function -1. The function downloads the video file from the source location -1. Streams the video through FFmpeg -1. Outputs a scaled video file and a thumbnail image -1. Uploads both files to the destination bucket + +1. A video file is uploaded to the source storage location +2. A notification event triggers the function +3. The function downloads the video file from the source location +4. Streams the video through FFmpeg +5. Outputs a scaled video file and a thumbnail image +6. Uploads both files to the destination bucket # Supported Platforms -- [x] Amazon Web Services (aws) Lambda -- [x] Google Cloud Platform (gcp) Cloud Functions (Alpha) -- [ ] IBM (ibm) OpenWhisk (Not started) -- [ ] Microsoft Azure (msa) Functions (Still some work to do here) + +* [x] Amazon Web Services (aws) Lambda +* [x] Google Cloud Platform (gcp) Cloud Functions (Alpha) +* [ ] IBM (ibm) OpenWhisk (Not started) +* [ ] Microsoft Azure (msa) Functions (Still some work to do here) # Setup -1. Install node.js, preferably through [nvm](/creationix/nvm). Each platform service uses a specific version of Node.js. -1. Clone this repo `git clone ...` -1. Run `npm install` -1. Create your function code's storage location (or choose an existing one) -1. Update the platform-specific configuration JSON file (see below), and/or modify the code file for your purposes -1. Run Gulp (see below) -1. Invoke the function by uploading a video to your source storage location. + +1. Install [Node.js](https://nodejs.org/en/), preferably through + [nvm](/creationix/nvm). Each platform service uses a specific version of + Node.js. +2. Clone this repo: `git clone ...` +3. Run `yarn install` +4. Create your function code's storage location (or choose an existing one) +5. Update the platform-specific configuration JSON file (see below), and/or modify the code file for your purposes +6. Run Gulp (see below) +7. Invoke the function by uploading a video to your source storage location. ## Configuration + See [config_samples](config_samples/). At minimum, you need to modify: -- `functionBucket` - The name of the bucket where your the lambda function code will be uploaded to. It's necessary for CloudFormation. -- `sourceBucket` - The name of the bucket that will receive the videos and send them to the lambda for processing. -- `destinationBucket` - The name of the bucket that will be used to store the output video and thumbnail image. + +* `functionBucket` - The name of the bucket where your the lambda function code will be uploaded to. It's necessary for CloudFormation. +* `sourceBucket` - The name of the bucket that will receive the videos and send them to the lambda for processing. +* `destinationBucket` - The name of the bucket that will be used to store the output video and thumbnail image. ## Local Testing ### Unit Tests -- Run `npm test` + +TODO: Unit tests are not implemented. + +* Run `yarn test` ### Integration Tests -- [Install FFmpeg locally](https://ffmpeg.org/download.html) or use the [compilation guide](https://trac.ffmpeg.org/wiki/CompilationGuide) -- Edit `event/{platform}.json` and run `node test/{platform}.js`, where platform is (aws|msa|gcp) -- When switching among the platforms, reinstall the node modules if the runtime supports a different version of Node.js. -- See the platform-specific notes + +* [Install FFmpeg locally](https://ffmpeg.org/download.html) or use the [compilation guide](https://trac.ffmpeg.org/wiki/CompilationGuide) +* Edit `event/{platform}.json` and run `node test/{platform}.js`, where platform is (aws|msa|gcp) +* When switching among the platforms, reinstall the node modules if the runtime supports a different version of Node.js. +* See the platform-specific notes ## Gotchas -- Gzipping videos will cause Safari errors in playback. Don't enable gzip unless you don't care about supporting Safari. + +* Gzipping videos will cause Safari errors in playback. Don't enable gzip unless you don't care about supporting Safari. # Platform-specific notes + ## AWS Lambda -- [Version information](https://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html) -- Pick the largest memory allocation. This is mostly CPU bound, but Lambda bundles memory and CPU allocation together. Memory size is 1536 by default, in the CloudFormation template. Testing with different videos and sizes should give you a good idea if it meets your requirements. Total execution time is limited! -- The object key from the event is URL encoded. Spaces in the filenames might be replaced with `+` so be aware of this and handle errors appropriately. If you try to download the file with the AWS SDK for JavaScript like in this example, without handling this, it will throw an error. -- Not handling errors with `context.fail(error)` will cause the function to run until the timeout is reached. + +* [Version information](https://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html) +* Pick the largest memory allocation. This is mostly CPU bound, but Lambda bundles memory and CPU allocation together. Memory size is 1536 by default, in the CloudFormation template. Testing with different videos and sizes should give you a good idea if it meets your requirements. Total execution time is limited! +* The object key from the event is URL encoded. Spaces in the filenames might be replaced with `+` so be aware of this and handle errors appropriately. If you try to download the file with the AWS SDK for JavaScript like in this example, without handling this, it will throw an error. +* Not handling errors with `context.fail(error)` will cause the function to run until the timeout is reached. ### Example local testing script + ```bash # Environment variables export AWS_ACCESS_KEY_ID=AKIDEXAMPLE @@ -89,12 +104,15 @@ node node_modules/babel-cli/bin/babel-node.js test/aws.js ### Gulp #### Task: `aws:create-cfn-bucket` + Creates the CloudFormation for your CloudFormation template and Lambda function code. **Run this once**. Set the `CFN_S3_BUCKET` environment variable to the name of the bucket you want to create. + ```bash CFN_S3_BUCKET=cloudformation-bucket gulp aws:create-cfn-bucket ``` #### Environment Settings + The following environment variables must be set prior to using the rest of the gulp commands ```bash @@ -109,37 +127,46 @@ export VIDEO_MAX_DURATION='30' # must be a number ``` #### Task: `aws:default` + Everything you need to get started. Note: You can change the stack name by setting environment variable `STACK_NAME`. -- Runs the `aws:build-upload` task -- Runs the `aws:deployStack` task + +* Runs the `aws:build-upload` task +* Runs the `aws:deployStack` task #### Task: `aws:build-upload` -- Builds `dist.zip` - - Downloads and extracts FFmpeg binaries - - Transpiles, installs dependencies, and copies configuration -- Uploads `dist.zip` to the function's S3 bucket + +* Builds `dist.zip` + * Downloads and extracts FFmpeg binaries + * Transpiles, installs dependencies, and copies configuration +* Uploads `dist.zip` to the function's S3 bucket #### Task: `aws:deployStack` -- Creates or updates the CloudFormation stack which includes: - - The lambda function's execution role and policy - - The lambda function - - The source bucket (where videos are uploaded to), including the notification configuration - - The destination bucket (where videos and thumbnails go after they are processed) + +* Creates or updates the CloudFormation stack which includes: + * The lambda function's execution role and policy + * The lambda function + * The source bucket (where videos are uploaded to), including the notification configuration + * The destination bucket (where videos and thumbnails go after they are processed) #### Task: `aws:update` + Run after modifying anything in the function or configuration, if you've already created the stack. This will rebuild `dist.zip`, upload it to S3, and update the lambda function created during the CloudFormation stack creation. ## Google Cloud Functions + See the [quickstart guide](https://cloud.google.com/functions/quickstart). ### Gulp + Note: you must have the gcloud CLI tool installed. #### Task: `gcp:default` -- Builds everything into the `build/` directory -- Deploys the function. Note: GCF does the `npm install` on the server-side, so there is no need to build a zip file. + +* Builds everything into the `build/` directory +* Deploys the function. Note: GCF does the `npm install` on the server-side, so there is no need to build a zip file. ### Example local testing script + ```bash # Environment variables export GCLOUD_PROJECT=example-project-name @@ -152,12 +179,15 @@ node node_modules/babel-cli/bin/babel-node.js --presets es2015 test/gcp.js ``` ## IBM OpenWhisk (not started, HELP WANTED) + See [the OpenWhisk repo](/openwhisk/openwhisk) ## Microsoft Azure Functions (in progress, HELP WANTED) + See [Azure functions reference](https://azure.microsoft.com/en-us/documentation/articles/functions-reference-node/). ### Example local testing script + ```bash # Environment variables export AZURE_STORAGE_CONNECTION_STRING=... # copy from azure console @@ -169,6 +199,7 @@ node node_modules/babel-cli/bin/babel-node.js --presets es2015-node5 test/aws.js ``` # Contributing + Submit issues if you find bugs or something is unclear. Pull requests are even better, especially if you can make something more generalized. **If you use it, :star: it!** diff --git a/package.json b/package.json index 1ad37d9..1b31025 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,11 @@ "socket.io": "^2.0.3" }, "devDependencies": { - "eslint": "^4.19.1" + "eslint": "^4.19.1", + "markdownlint-cli": "^0.8.1" }, "scripts": { - "lint": "eslint --cache *.js lambda models routes scripts", + "lint": "eslint --cache *.js lambda models routes scripts static && markdownlint README.md", "test": "echo \"Error: no test specified\" && exit 1", "start": "node server.js" }, diff --git a/routes/api.js b/routes/api.js index 057af89..7a90fe2 100644 --- a/routes/api.js +++ b/routes/api.js @@ -222,10 +222,10 @@ module.exports = function(io, db) { // Get all flavors function GETflavors(req, res) { db.Flavors.getFlavors().then(function success(data) { - res.send(data); - }, function error(err) { - res.status(500).send(err); - }); + res.send(data); + }, function error(err) { + res.status(500).send(err); + }); } // Get one flavor by name or id @@ -236,32 +236,32 @@ module.exports = function(io, db) { db.Flavors.getFlavor({ _id: db.ObjectId(req.params.flavorname) }).then(function success(data) { res.send(data); }, function error(err) { - res.status(500).send(err); + res.status(500).send(err); }); } else { res.send(data); } - }, function error(err) { - res.status(500).send(err); - }); + }, function error(err) { + res.status(500).send(err); + }); } // Get all tags function GETtags(req, res) { - db.Tag.getTags().then(function success(data) { - res.send(data); - }, function error(err) { - res.status(500).send(err); - }); - } + db.Tag.getTags().then(function success(data) { + res.send(data); + }, function error(err) { + res.status(500).send(err); + }); + } // Get a single tag by id function GETtag(req, res) { - db.Tag.getTag({ _id: db.ObjectId(req.params.tagid) }).then(function success(data) { - res.send(data); - }, function error(err) { - res.status(500).send(err); - }); + db.Tag.getTag({ _id: db.ObjectId(req.params.tagid) }).then(function success(data) { + res.send(data); + }, function error(err) { + res.status(500).send(err); + }); } router.GETallBobs = GETallBobs; diff --git a/routes/browser.js b/routes/browser.js index 43353c3..2679407 100644 --- a/routes/browser.js +++ b/routes/browser.js @@ -27,7 +27,7 @@ module.exports = function (api, rootDir) { router.route('/views/events').get(ensureAuthenticated, function(req, res) { res.sendFile(rootDir + '/templates/events.html'); }); - router.route('/views/basic').get(ensureAuthenticated, function(req, res) { + router.route('/views/expo').get(ensureAuthenticated, function(req, res) { res.sendFile(rootDir + '/templates/stream-basic.html'); }); router.route('/upload').get(function (req, res) { diff --git a/server.js b/server.js index 9197e00..d5ea72a 100644 --- a/server.js +++ b/server.js @@ -14,8 +14,13 @@ if(process.env.NODE_ENV === 'production'){ } const port = process.env.PORT || 8080; -http.listen(port, function() { - console.log("FORWARDboard running over http on port", port); +const server = http.listen(port, function() { + let host = server.address().address; + // replace IPv6 wildcard by a recognizable URL, that can be used in a browser + // address bar + host = host.replace(/^::$/, '0.0.0.0'); + // Printed thus, some terminals display a clickable link + console.log('FORWARDboard is running at http://%s:%s/', host, server.address().port); }); diff --git a/static/css/stream-only.css b/static/css/stream-only.css index 986d629..0cc548b 100644 --- a/static/css/stream-only.css +++ b/static/css/stream-only.css @@ -33,7 +33,8 @@ html { font-family: "DINOT-Bold", "DINOT-BoldItalic", "DINOT-CondMediIta", "DINOT-Italic", "DINOT-LightItalic", "DINOT"; } .row .col{ - padding:0; + background:black; + padding:0; } body { overflow:hidden; @@ -44,15 +45,25 @@ body { /*navbar settings*/ nav { /*box margin*/ - background-color:#009BDF; + /*background-color:#23b4c2;*/ + background-color:black; font-family: "DINOT-BoldItalic"; - /*transform: skewX(-20deg);*/ + /*transform: skewX(-20deg);*/; + margin-top:1em; + margin-bottom:1em; +} +nav ul a{ + color:#eee; } - nav .brand-logo{ font-size: 50px; + color:#eee; } +.nav-wrapper{ + padding-left:2em; + padding-right:2em; +} /*navbar clock setting*/ #clock{ diff --git a/static/css/upload.css b/static/css/upload.css index 92ea4ea..94a3ed3 100644 --- a/static/css/upload.css +++ b/static/css/upload.css @@ -75,7 +75,7 @@ form#dropzone-input { progress{ width:100%; - background-color:black: + background-color:black; } .dropzone .dz-message{ diff --git a/static/js/board_carousel.js b/static/js/board_carousel.js index edbd09f..89daaa2 100644 --- a/static/js/board_carousel.js +++ b/static/js/board_carousel.js @@ -54,7 +54,7 @@ function createBoardElement(bob) { case 'Video': $html.addClass('video-bobble ').attr("id", bob._id) - .append(($('