├── assets
├── templates
│ └── .gitkeep
├── favicon.ico
├── images
│ ├── balls.png
│ ├── steps.png
│ ├── fhq-500.png
│ ├── GreyBall.png
│ ├── SvExRules.png
│ ├── TradeRules.png
│ ├── ballflairs.png
│ ├── eggflairs.png
│ ├── spinda_500.png
│ ├── HatchFlairs.png
│ ├── TradeFlairs.png
│ ├── old_man_403.png
│ ├── ribbonFlairs.png
│ └── judging-scatterbug.png
├── tools
│ ├── darkmode.png
│ ├── greasemonkey.png
│ └── tools.ejs
├── fonts
│ ├── glyphicons-halflings-regular.eot
│ ├── glyphicons-halflings-regular.ttf
│ └── glyphicons-halflings-regular.woff
├── styles
│ ├── bootstrap
│ │ ├── mixins
│ │ │ ├── center-block.less
│ │ │ ├── text-emphasis.less
│ │ │ ├── size.less
│ │ │ ├── opacity.less
│ │ │ ├── background-variant.less
│ │ │ ├── text-overflow.less
│ │ │ ├── tab-focus.less
│ │ │ ├── resize.less
│ │ │ ├── labels.less
│ │ │ ├── progress-bar.less
│ │ │ ├── reset-filter.less
│ │ │ ├── nav-divider.less
│ │ │ ├── alerts.less
│ │ │ ├── nav-vertical-align.less
│ │ │ ├── responsive-visibility.less
│ │ │ ├── pagination.less
│ │ │ ├── border-radius.less
│ │ │ ├── panels.less
│ │ │ ├── list-group.less
│ │ │ ├── hide-text.less
│ │ │ ├── clearfix.less
│ │ │ ├── table-row.less
│ │ │ ├── image.less
│ │ │ ├── buttons.less
│ │ │ ├── forms.less
│ │ │ └── grid-framework.less
│ │ ├── wells.less
│ │ ├── breadcrumbs.less
│ │ ├── responsive-embed.less
│ │ ├── component-animations.less
│ │ ├── close.less
│ │ ├── thumbnails.less
│ │ ├── utilities.less
│ │ ├── media.less
│ │ ├── pager.less
│ │ ├── mixins.less
│ │ ├── bootstrap.less
│ │ ├── jumbotron.less
│ │ ├── badges.less
│ │ ├── labels.less
│ │ ├── code.less
│ │ ├── grid.less
│ │ ├── alerts.less
│ │ ├── print.less
│ │ ├── pagination.less
│ │ ├── progress-bars.less
│ │ ├── tooltip.less
│ │ └── scaffolding.less
│ ├── fonts.less
│ └── spinners.less
├── search
│ ├── log
│ │ ├── result.ejs
│ │ ├── form.ejs
│ │ └── controller.js
│ ├── ref
│ │ ├── result.ejs
│ │ ├── controller.js
│ │ └── form.ejs
│ ├── user
│ │ ├── result.ejs
│ │ ├── form.ejs
│ │ └── controller.js
│ ├── modmail
│ │ ├── result.ejs
│ │ ├── form.ejs
│ │ └── controller.js
│ ├── search.module.js
│ ├── types.js
│ ├── README.md
│ ├── main.ejs
│ └── header.ejs
├── robots.txt
├── tooltip
│ ├── tooltip.view.html
│ ├── label.view.html
│ └── tooltip.module.js
├── views
│ ├── home
│ │ ├── addDiscord.ejs
│ │ ├── banlist.ejs
│ │ ├── profileInfo.ejs
│ │ ├── row.ejs
│ │ ├── applist.ejs
│ │ ├── viewreference.ejs
│ │ ├── header.ejs
│ │ └── banuser.ejs
│ ├── 403.ejs
│ ├── auth
│ │ └── index.ejs
│ ├── 500.ejs
│ ├── 404.ejs
│ ├── layout.ejs
│ └── privacyPolicy.ejs
├── markdown
│ ├── remapURLs.js
│ └── markdown.module.js
├── numberPadding.js
├── common
│ └── regexCommon.js
├── adminCtrl.js
├── app.js
└── ngReallyClick.js
├── test
├── .eslintrc
└── unit
│ ├── data
│ ├── friendCodes.json
│ ├── flairTexts.json
│ ├── markdownStrings.json
│ ├── flairCssClasses.json
│ ├── referenceFactory.js
│ └── users.json
│ └── markdown
│ └── remapURLs.test.js
├── config
├── locales
│ ├── de.json
│ ├── en.json
│ ├── fr.json
│ ├── es.json
│ └── _README.md
├── debug_vars.js
├── schedule.js
├── env
│ ├── development.js
│ └── production.js
├── connections.js
├── local.example.js
├── log.js
├── bootstrap.js
├── models.js
├── session.js
├── policies.js
├── i18n.js
├── express.js
├── csrf.js
└── globals.js
├── .sailsrc
├── api
├── models
│ ├── Sessions.js
│ ├── ModNote.js
│ ├── Application.js
│ ├── Comment.js
│ ├── PointLog.js
│ ├── Game.js
│ ├── ContestStats.js
│ ├── Event.js
│ ├── Flair.js
│ ├── Team.js
│ ├── Reference.js
│ ├── Modmail.js
│ └── User.js
├── policies
│ ├── isFlairMod.js
│ ├── isAdmin.js
│ ├── isPostMod.js
│ ├── passport.js
│ └── sessionAuth.js
├── controllers
│ ├── EventController.js
│ ├── SearchController.js
│ └── HomeController.js
├── .eslintrc
├── services
│ ├── Modmails.js
│ ├── Users.js
│ └── Usernotes.js
└── responses
│ ├── ok.js
│ ├── badRequest.js
│ ├── forbidden.js
│ ├── serverError.js
│ └── notFound.js
├── tasks
├── register
│ ├── test.js
│ ├── default.js
│ ├── prod.js
│ └── compileAssets.js
├── config
│ ├── browserify.js
│ ├── clean.js
│ ├── uglify.js
│ ├── cssmin.js
│ ├── eslint.js
│ ├── test.js
│ ├── concat.js
│ ├── less.js
│ ├── copy.js
│ └── watch.js
├── pipeline.js
└── README.md
├── .eslintrc
├── .gitignore
├── .travis.yml
├── .github
└── workflows
│ └── build.yml
├── app.js
├── Gruntfile.js
└── package.json
/assets/templates/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pokemontrades/flairhq/HEAD/assets/favicon.ico
--------------------------------------------------------------------------------
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "globals": {
3 | "it": true,
4 | "describe": true
5 | }
6 | }
--------------------------------------------------------------------------------
/assets/images/balls.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pokemontrades/flairhq/HEAD/assets/images/balls.png
--------------------------------------------------------------------------------
/assets/images/steps.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pokemontrades/flairhq/HEAD/assets/images/steps.png
--------------------------------------------------------------------------------
/assets/images/fhq-500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pokemontrades/flairhq/HEAD/assets/images/fhq-500.png
--------------------------------------------------------------------------------
/assets/tools/darkmode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pokemontrades/flairhq/HEAD/assets/tools/darkmode.png
--------------------------------------------------------------------------------
/config/locales/de.json:
--------------------------------------------------------------------------------
1 | {
2 | "Welcome": "Willkommen",
3 | "A brand new app.": "Eine neue App."
4 | }
5 |
--------------------------------------------------------------------------------
/config/locales/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "Welcome": "Welcome",
3 | "A brand new app.": "A brand new app."
4 | }
5 |
--------------------------------------------------------------------------------
/assets/images/GreyBall.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pokemontrades/flairhq/HEAD/assets/images/GreyBall.png
--------------------------------------------------------------------------------
/assets/images/SvExRules.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pokemontrades/flairhq/HEAD/assets/images/SvExRules.png
--------------------------------------------------------------------------------
/assets/images/TradeRules.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pokemontrades/flairhq/HEAD/assets/images/TradeRules.png
--------------------------------------------------------------------------------
/assets/images/ballflairs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pokemontrades/flairhq/HEAD/assets/images/ballflairs.png
--------------------------------------------------------------------------------
/assets/images/eggflairs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pokemontrades/flairhq/HEAD/assets/images/eggflairs.png
--------------------------------------------------------------------------------
/assets/images/spinda_500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pokemontrades/flairhq/HEAD/assets/images/spinda_500.png
--------------------------------------------------------------------------------
/assets/images/HatchFlairs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pokemontrades/flairhq/HEAD/assets/images/HatchFlairs.png
--------------------------------------------------------------------------------
/assets/images/TradeFlairs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pokemontrades/flairhq/HEAD/assets/images/TradeFlairs.png
--------------------------------------------------------------------------------
/assets/images/old_man_403.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pokemontrades/flairhq/HEAD/assets/images/old_man_403.png
--------------------------------------------------------------------------------
/assets/images/ribbonFlairs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pokemontrades/flairhq/HEAD/assets/images/ribbonFlairs.png
--------------------------------------------------------------------------------
/assets/tools/greasemonkey.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pokemontrades/flairhq/HEAD/assets/tools/greasemonkey.png
--------------------------------------------------------------------------------
/config/locales/fr.json:
--------------------------------------------------------------------------------
1 | {
2 | "Welcome": "Bienvenue",
3 | "A brand new app.": "Une toute nouvelle application."
4 | }
5 |
--------------------------------------------------------------------------------
/.sailsrc:
--------------------------------------------------------------------------------
1 | {
2 | "generators": {
3 | "modules": {}
4 | },
5 | "paths": {
6 | "views": "./assets/views"
7 | }
8 | }
--------------------------------------------------------------------------------
/config/locales/es.json:
--------------------------------------------------------------------------------
1 | {
2 | "Welcome": "Bienvenido",
3 | "A brand new app.": "Una aplicación de la nueva marca."
4 | }
5 |
--------------------------------------------------------------------------------
/api/models/Sessions.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | attributes: {
3 | session: "json",
4 | expires: "string"
5 | }
6 | };
7 |
--------------------------------------------------------------------------------
/assets/images/judging-scatterbug.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pokemontrades/flairhq/HEAD/assets/images/judging-scatterbug.png
--------------------------------------------------------------------------------
/api/policies/isFlairMod.js:
--------------------------------------------------------------------------------
1 | module.exports = (req, res, next) => Users.hasModPermission(req.user, 'flair') ? next() : res.forbidden();
2 |
--------------------------------------------------------------------------------
/api/policies/isAdmin.js:
--------------------------------------------------------------------------------
1 | module.exports = (req, res, next) => Users.hasModPermission(req.user, 'all') ? next() : res.forbidden('Not a mod');
2 |
--------------------------------------------------------------------------------
/assets/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pokemontrades/flairhq/HEAD/assets/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/assets/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pokemontrades/flairhq/HEAD/assets/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/assets/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pokemontrades/flairhq/HEAD/assets/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/tasks/register/test.js:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 | grunt.registerTask('test', [
3 | 'eslint',
4 | 'mochaTest'
5 | ]);
6 | };
7 |
--------------------------------------------------------------------------------
/tasks/register/default.js:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 | grunt.registerTask('default', [
3 | 'compileDev',
4 | 'focus:dev'
5 | ]);
6 | };
7 |
--------------------------------------------------------------------------------
/tasks/register/prod.js:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 | grunt.registerTask('prod', [
3 | 'compileProd',
4 | 'concat',
5 | 'cssmin'
6 | ]);
7 | };
8 |
--------------------------------------------------------------------------------
/api/policies/isPostMod.js:
--------------------------------------------------------------------------------
1 | module.exports = (req, res, next) => (Users.hasModPermission(req.user, 'posts') && Users.hasModPermission(req.user, 'wiki')) ? next() : res.forbidden("Not post mod");
2 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/center-block.less:
--------------------------------------------------------------------------------
1 | // Center-align a block level element
2 |
3 | .center-block() {
4 | display: block;
5 | margin-left: auto;
6 | margin-right: auto;
7 | }
8 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/text-emphasis.less:
--------------------------------------------------------------------------------
1 | // Typography
2 |
3 | .text-emphasis-variant(@color) {
4 | color: @color;
5 | a&:hover {
6 | color: darken(@color, 10%);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/size.less:
--------------------------------------------------------------------------------
1 | // Sizing shortcuts
2 |
3 | .size(@width; @height) {
4 | width: @width;
5 | height: @height;
6 | }
7 |
8 | .square(@size) {
9 | .size(@size; @size);
10 | }
11 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/opacity.less:
--------------------------------------------------------------------------------
1 | // Opacity
2 |
3 | .opacity(@opacity) {
4 | opacity: @opacity;
5 | // IE8 filter
6 | @opacity-ie: (@opacity * 100);
7 | filter: ~"alpha(opacity=@{opacity-ie})";
8 | }
9 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/background-variant.less:
--------------------------------------------------------------------------------
1 | // Contextual backgrounds
2 |
3 | .bg-variant(@color) {
4 | background-color: @color;
5 | a&:hover {
6 | background-color: darken(@color, 10%);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/assets/search/log/result.ejs:
--------------------------------------------------------------------------------
1 |
2 | /u/{{result.user}} - {{result.createdAt | date:"yyyy-MM-dd HH:mm:ss' GMT'Z"}}
3 |
4 |
5 |
6 | {{result.content}}
7 |
--------------------------------------------------------------------------------
/assets/search/ref/result.ejs:
--------------------------------------------------------------------------------
1 |
2 | /u/{{result.user}} and /u/{{result.user2}}
3 |
4 |
5 | {{result.description || result.gave + " for " + result.got}}
6 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/text-overflow.less:
--------------------------------------------------------------------------------
1 | // Text overflow
2 | // Requires inline-block or block for proper styling
3 |
4 | .text-overflow() {
5 | overflow: hidden;
6 | text-overflow: ellipsis;
7 | white-space: nowrap;
8 | }
9 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/tab-focus.less:
--------------------------------------------------------------------------------
1 | // WebKit-style focus
2 |
3 | .tab-focus() {
4 | // Default
5 | outline: thin dotted;
6 | // WebKit
7 | outline: 5px auto -webkit-focus-ring-color;
8 | outline-offset: -2px;
9 | }
10 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/resize.less:
--------------------------------------------------------------------------------
1 | // Resize anything
2 |
3 | .resizable(@direction) {
4 | resize: @direction; // Options: horizontal, vertical, both
5 | overflow: auto; // Per CSS3 UI, `resize` only applies when `overflow` isn't `visible`
6 | }
7 |
--------------------------------------------------------------------------------
/test/unit/data/friendCodes.json:
--------------------------------------------------------------------------------
1 | {
2 | "valid1":"0000-0000-0135",
3 | "valid2":"0000-0000-0165",
4 | "invalid1":"0000-0000-0000",
5 | "invalid2":"3333-3333-3333",
6 | "exceedsMaximum":"7777-7777-7777",
7 | "badFormat":"This is not a friend code."
8 | }
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/labels.less:
--------------------------------------------------------------------------------
1 | // Labels
2 |
3 | .label-variant(@color) {
4 | background-color: @color;
5 |
6 | &[href] {
7 | &:hover,
8 | &:focus {
9 | background-color: darken(@color, 10%);
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/assets/search/user/result.ejs:
--------------------------------------------------------------------------------
1 |
2 | /u/{{result._id}}
3 |
4 |
5 |
6 | /r/PokemonTrades: {{result.flair.ptrades.flair_text}}
7 | /r/SVExchange: {{result.flair.svex.flair_text}}
8 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/progress-bar.less:
--------------------------------------------------------------------------------
1 | // Progress bars
2 |
3 | .progress-bar-variant(@color) {
4 | background-color: @color;
5 |
6 | // Deprecated parent class requirement as of v3.2.0
7 | .progress-striped & {
8 | #gradient > .striped();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/config/debug_vars.js:
--------------------------------------------------------------------------------
1 | module.exports.debug = {
2 | reddit: false, // If true, redirects reddit-modifying actions to a debug subreddit
3 | subreddit: 'crownofnails' // The debug subreddit to redirect to
4 | };
5 |
6 | module.exports.version = require("../package.json").version;
7 |
--------------------------------------------------------------------------------
/assets/search/modmail/result.ejs:
--------------------------------------------------------------------------------
1 |
2 | {{result.subject}} by /u/{{result.author}}
3 |
4 |
5 | {{result.created_utc * 1000 | date:"yyyy-MM-dd HH:mm:ss' GMT'Z"}}
6 |
7 |
8 |
--------------------------------------------------------------------------------
/assets/robots.txt:
--------------------------------------------------------------------------------
1 | # The robots.txt file is used to control how search engines index your live URLs.
2 | # See http://www.robotstxt.org/wc/norobots.html for more information.
3 |
4 |
5 |
6 | # To prevent search engines from seeing the site altogether, uncomment the next two lines:
7 | # User-Agent: *
8 | # Disallow: /
9 |
--------------------------------------------------------------------------------
/assets/search/log/form.ejs:
--------------------------------------------------------------------------------
1 |
2 | Search keyword
3 |
6 |
--------------------------------------------------------------------------------
/assets/search/user/form.ejs:
--------------------------------------------------------------------------------
1 |
2 | Search keyword
3 |
6 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/reset-filter.less:
--------------------------------------------------------------------------------
1 | // Reset filters for IE
2 | //
3 | // When you need to remove a gradient background, do not forget to use this to reset
4 | // the IE filter for IE9 and below.
5 |
6 | .reset-filter() {
7 | filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)"));
8 | }
9 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/nav-divider.less:
--------------------------------------------------------------------------------
1 | // Horizontal dividers
2 | //
3 | // Dividers (basically an hr) within dropdowns and nav lists
4 |
5 | .nav-divider(@color: #e5e5e5) {
6 | height: 1px;
7 | margin: ((@line-height-computed / 2) - 1) 0;
8 | overflow: hidden;
9 | background-color: @color;
10 | }
11 |
--------------------------------------------------------------------------------
/assets/tooltip/tooltip.view.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/assets/search/search.module.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 |
5 | var angular = require("angular");
6 | var controller = require("./search.controller.js");
7 |
8 | var searchModule = angular.module("fapp.search", []);
9 | searchModule.controller("SearchController", ['$scope', '$timeout', controller]);
10 |
11 | module.exports = searchModule;
12 |
--------------------------------------------------------------------------------
/assets/views/home/addDiscord.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | You have joined Discord as <%= discordInfo.user.username %>#<%= discordInfo.user.discriminator %>
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/alerts.less:
--------------------------------------------------------------------------------
1 | // Alerts
2 |
3 | .alert-variant(@background; @border; @text-color) {
4 | background-color: @background;
5 | border-color: @border;
6 | color: @text-color;
7 |
8 | hr {
9 | border-top-color: darken(@border, 5%);
10 | }
11 | .alert-link {
12 | color: darken(@text-color, 10%);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/tasks/register/compileAssets.js:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 | grunt.registerTask('compileDev', [
3 | 'clean:dev',
4 | 'less:dev',
5 | 'copy:dev',
6 | 'browserify:dev'
7 | ]);
8 |
9 | grunt.registerTask('compileProd', [
10 | 'clean:dev',
11 | 'less:dev',
12 | 'copy:dev',
13 | 'browserify:dev',
14 | 'uglify'
15 | ]);
16 | };
17 |
--------------------------------------------------------------------------------
/tasks/config/browserify.js:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 |
3 | grunt.config.set('browserify', {
4 | dev: {
5 | files: {
6 | ".tmp/public/js/app.js": "assets/app.js"
7 | },
8 | options: {
9 | transform: [["babelify"]],
10 | watch: true
11 | }
12 | }
13 | });
14 |
15 | grunt.loadNpmTasks('grunt-browserify');
16 | };
--------------------------------------------------------------------------------
/api/models/ModNote.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Reference.js
3 | *
4 | * @description :: TODO: You might write a short summary of how this model works and what it represents here.
5 | * @docs :: http://sailsjs.org/#!documentation/models
6 | */
7 |
8 | module.exports = {
9 |
10 | attributes: {
11 | user: "string",
12 | refUser: "string",
13 | note: "string"
14 | }
15 | };
16 |
17 |
--------------------------------------------------------------------------------
/api/models/Application.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Application.js
3 | *
4 | * @description :: TODO: You might write a short summary of how this model works and what it represents here.
5 | * @docs :: http://sailsjs.org/#!documentation/models
6 | */
7 |
8 | module.exports = {
9 |
10 | attributes: {
11 | user: "string",
12 | flair: "string",
13 | sub: "string"
14 | }
15 | };
16 |
17 |
--------------------------------------------------------------------------------
/api/models/Comment.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Reference.js
3 | *
4 | * @description :: TODO: You might write a short summary of how this model works and what it represents here.
5 | * @docs :: http://sailsjs.org/#!documentation/models
6 | */
7 |
8 | module.exports = {
9 |
10 | attributes: {
11 | user: "string",
12 | user2: "string",
13 | message: "text"
14 | }
15 |
16 | };
17 |
18 |
--------------------------------------------------------------------------------
/assets/markdown/remapURLs.js:
--------------------------------------------------------------------------------
1 | module.exports = function (value) {
2 | var userRegex = /()(\/?\2)(<\/a>)/g;
3 | var userReplaced = value.replace(userRegex, "$1https://www.reddit.com/$2$3$4$5 ($1/$2$3FlairHQ$5)");
4 | var subRegex = /( )(\/?\2)(<\/a>)/g;
5 | return userReplaced.replace(subRegex, "$1https://www.reddit.com/$2$3$4$5");
6 | };
--------------------------------------------------------------------------------
/assets/search/modmail/form.ejs:
--------------------------------------------------------------------------------
1 |
2 | Search keyword
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/assets/numberPadding.js:
--------------------------------------------------------------------------------
1 | var ng = require("angular");
2 |
3 | ng.module('numberPaddingModule', []).filter('numberPadding', function () {
4 | return function (n, len) {
5 | var num = parseInt(n, 10);
6 | len = parseInt(len, 10);
7 | if (isNaN(num) || isNaN(len)) {
8 | return n;
9 | }
10 | num = '' + num;
11 | while (num.length < len) {
12 | num = '0' + num;
13 | }
14 | return num;
15 | };
16 | });
--------------------------------------------------------------------------------
/assets/tooltip/label.view.html:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/nav-vertical-align.less:
--------------------------------------------------------------------------------
1 | // Navbar vertical align
2 | //
3 | // Vertically center elements in the navbar.
4 | // Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin.
5 |
6 | .navbar-vertical-align(@element-height) {
7 | margin-top: ((@navbar-height - @element-height) / 2);
8 | margin-bottom: ((@navbar-height - @element-height) / 2);
9 | }
10 |
--------------------------------------------------------------------------------
/api/models/PointLog.js:
--------------------------------------------------------------------------------
1 | /**
2 | * PointLog.js
3 | *
4 | * @description :: TODO: You might write a short summary of how this model works and what it represents here.
5 | * @docs :: http://sailsjs.org/#!documentation/models
6 | */
7 |
8 | module.exports = {
9 | attributes: {
10 | time: "string",
11 | team: "string",
12 | from: "string",
13 | pointType: "string",
14 | reason: "string",
15 | points: "integer"
16 | }
17 | };
18 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/responsive-visibility.less:
--------------------------------------------------------------------------------
1 | // Responsive utilities
2 |
3 | //
4 | // More easily include all the states for responsive-utilities.less.
5 | .responsive-visibility() {
6 | display: block !important;
7 | table& { display: table; }
8 | tr& { display: table-row !important; }
9 | th&,
10 | td& { display: table-cell !important; }
11 | }
12 |
13 | .responsive-invisibility() {
14 | display: none !important;
15 | }
16 |
--------------------------------------------------------------------------------
/api/controllers/EventController.js:
--------------------------------------------------------------------------------
1 | /* global module, User, Event */
2 | /**
3 | * UserController
4 | *
5 | * @description :: Server-side logic for managing Users
6 | */
7 |
8 | module.exports = {
9 |
10 | get: function (req, res) {
11 | var appData = {
12 | limit: 20,
13 | sort: "createdAt DESC"
14 | };
15 |
16 | Event.find(appData).exec(function (err, events) {
17 | return res.ok(events);
18 | });
19 | }
20 | };
21 |
22 |
--------------------------------------------------------------------------------
/assets/search/log/controller.js:
--------------------------------------------------------------------------------
1 | /* global Search */
2 | module.exports = function (req, res) {
3 | var params = req.allParams();
4 | if (!params.keyword) {
5 | return res.view("../search/main", {searchType: 'log', searchTerm: ''});
6 | }
7 | var searchData = {
8 | keyword: params.keyword
9 | };
10 |
11 | searchData.skip = params.skip || 0;
12 |
13 | Search.logs(searchData, function (results) {
14 | return res.ok(results);
15 | });
16 | };
--------------------------------------------------------------------------------
/assets/search/user/controller.js:
--------------------------------------------------------------------------------
1 | /* global Search */
2 | module.exports = function (req, res) {
3 | var params = req.allParams();
4 | if (!params.keyword) {
5 | return res.view("../search/main", {searchType: 'user', searchTerm: ''});
6 | }
7 | var searchData = {
8 | keyword: params.keyword
9 | };
10 |
11 | searchData.skip = params.skip || 0;
12 |
13 | Search.users(searchData, function (results) {
14 | return res.ok(results);
15 | });
16 | };
--------------------------------------------------------------------------------
/api/models/Game.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Reference.js
3 | *
4 | * @description :: TODO: You might write a short summary of how this model works and what it represents here.
5 | * @docs :: http://sailsjs.org/#!documentation/models
6 | */
7 |
8 | module.exports = {
9 |
10 | attributes: {
11 | user: "string",
12 | ign: "string",
13 | tsv: {
14 | type: "int",
15 | max: 4095,
16 | min: -1,
17 | numeric: true
18 | }
19 | }
20 | };
21 |
22 |
--------------------------------------------------------------------------------
/assets/search/modmail/controller.js:
--------------------------------------------------------------------------------
1 | /* global Search */
2 | module.exports = function (req, res) {
3 | var params = req.allParams();
4 | if (!params.keyword) {
5 | return res.view("../search/main", {searchType: 'modmail', searchTerm: ''});
6 | }
7 | var searchData = {
8 | keyword: params.keyword
9 | };
10 |
11 | searchData.skip = params.skip || 0;
12 |
13 | Search.modmails(searchData, function (results) {
14 | return res.ok(results);
15 | });
16 | };
--------------------------------------------------------------------------------
/api/models/ContestStats.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ContestStats.js
3 | *
4 | * @description :: TODO: You might write a short summary of how this model works and what it represents here.
5 | * @docs :: http://sailsjs.org/#!documentation/models
6 | */
7 |
8 | module.exports = {
9 | attributes: {
10 | user: {
11 | type: "string",
12 | columnName: 'id',
13 | primaryKey: true
14 | },
15 | expPoints: "integer",
16 | battleWins: "integer"
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/assets/search/types.js:
--------------------------------------------------------------------------------
1 | var types = [
2 | {"short": "ref", "name": "References", controller: require("./ref/controller.js"), "modOnly": false},
3 | {"short": "user", "name": "Users", controller: require("./user/controller.js"), "modOnly": false},
4 | {"short": "log", "name": "Logs", controller: require("./log/controller.js"), "modOnly": true},
5 | {"short": "modmail", "name": "Modmails", controller: require("./modmail/controller.js"), "modOnly": true}
6 | ];
7 |
8 | module.exports = types;
--------------------------------------------------------------------------------
/assets/views/403.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Forbidden
5 |
6 |
7 |
8 |
9 |
10 |
11 | <% if (typeof error !== 'undefined') { %>
12 |
13 | <%- error %>
14 |
15 | <% } %>
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "globals": {
4 | "module": true,
5 | "require": true,
6 | "exports": true
7 | },
8 | "rules": {
9 | "strict": 0,
10 | "no-console": 0,
11 | "linebreak-style": [
12 | 2,
13 | "unix"
14 | ],
15 | "indent": [
16 | 2,
17 | 2
18 | ],
19 | "semi": [
20 | 2,
21 | "always"
22 | ]
23 | },
24 | "env": {
25 | "es6": true,
26 | "browser": true
27 | },
28 | "extends": "eslint:recommended"
29 | }
30 |
--------------------------------------------------------------------------------
/api/models/Event.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Event.js
3 | *
4 | * @description :: TODO: You might write a short summary of how this model works and what it represents here.
5 | * @docs :: http://sailsjs.org/#!documentation/models
6 | */
7 |
8 | module.exports = {
9 |
10 | attributes: {
11 | user: "string",
12 | type: {
13 | type: "string",
14 | enum: [
15 | "flairTextChange",
16 | "flairCssChange",
17 | "banUser",
18 | "discordJoin"
19 | ]
20 | },
21 | content: "string"
22 | }
23 | };
24 |
25 |
--------------------------------------------------------------------------------
/tasks/config/clean.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Clean files and folders.
3 | *
4 | * ---------------------------------------------------------------
5 | *
6 | * This grunt task is configured to clean out the contents in the .tmp/public of your
7 | * sails project.
8 | *
9 | * For usage docs see:
10 | * https://github.com/gruntjs/grunt-contrib-clean
11 | */
12 | module.exports = function (grunt) {
13 |
14 | grunt.config.set('clean', {
15 | dev: ['.tmp/public/**'],
16 | build: ['www']
17 | });
18 |
19 | grunt.loadNpmTasks('grunt-contrib-clean');
20 | };
21 |
--------------------------------------------------------------------------------
/assets/views/auth/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
Welcome to FlairHQ
7 |
This is a site that helps with the application of flairs on Reddit.
8 |
To get started,
9 | login with Reddit and then start adding your trades.
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/pagination.less:
--------------------------------------------------------------------------------
1 | // Pagination
2 |
3 | .pagination-size(@padding-vertical; @padding-horizontal; @font-size; @border-radius) {
4 | > li {
5 | > a,
6 | > span {
7 | padding: @padding-vertical @padding-horizontal;
8 | font-size: @font-size;
9 | }
10 | &:first-child {
11 | > a,
12 | > span {
13 | .border-left-radius(@border-radius);
14 | }
15 | }
16 | &:last-child {
17 | > a,
18 | > span {
19 | .border-right-radius(@border-radius);
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tasks/config/uglify.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Minify files with UglifyJS.
3 | *
4 | * ---------------------------------------------------------------
5 | *
6 | * Minifies client-side javascript `assets`.
7 | *
8 | * For usage docs see:
9 | * https://github.com/gruntjs/grunt-contrib-uglify
10 | *
11 | */
12 | module.exports = function (grunt) {
13 |
14 | grunt.config.set('uglify', {
15 | dist: {
16 | src: ['.tmp/public/js/app.js'],
17 | dest: '.tmp/public/min/production.min.js'
18 | }
19 | });
20 |
21 | grunt.loadNpmTasks('grunt-contrib-uglify');
22 | };
23 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/border-radius.less:
--------------------------------------------------------------------------------
1 | // Single side border-radius
2 |
3 | .border-top-radius(@radius) {
4 | border-top-right-radius: @radius;
5 | border-top-left-radius: @radius;
6 | }
7 | .border-right-radius(@radius) {
8 | border-bottom-right-radius: @radius;
9 | border-top-right-radius: @radius;
10 | }
11 | .border-bottom-radius(@radius) {
12 | border-bottom-right-radius: @radius;
13 | border-bottom-left-radius: @radius;
14 | }
15 | .border-left-radius(@radius) {
16 | border-bottom-left-radius: @radius;
17 | border-top-left-radius: @radius;
18 | }
19 |
--------------------------------------------------------------------------------
/tasks/config/cssmin.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Compress CSS files.
3 | *
4 | * ---------------------------------------------------------------
5 | *
6 | * Minifies css files and places them into .tmp/public/min directory.
7 | *
8 | * For usage docs see:
9 | * https://github.com/gruntjs/grunt-contrib-cssmin
10 | */
11 | module.exports = function (grunt) {
12 |
13 | grunt.config.set('cssmin', {
14 | dist: {
15 | src: ['.tmp/public/concat/production.css'],
16 | dest: '.tmp/public/min/production.min.css'
17 | }
18 | });
19 |
20 | grunt.loadNpmTasks('grunt-contrib-cssmin');
21 | };
22 |
--------------------------------------------------------------------------------
/tasks/config/eslint.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Clean files and folders.
3 | *
4 | * ---------------------------------------------------------------
5 | *
6 | * This grunt task is configured to clean out the contents in the .tmp/public of your
7 | * sails project.
8 | *
9 | * For usage docs see:
10 | * https://github.com/gruntjs/grunt-contrib-clean
11 | */
12 | module.exports = function (grunt) {
13 |
14 | grunt.config.set('eslint', {
15 | target: ['Gruntfile.js', 'app.js', 'api/**/*.js', 'tasks/**/*.js', 'assets/**/*.js']
16 | });
17 |
18 | grunt.loadNpmTasks('grunt-eslint');
19 |
20 | };
21 |
--------------------------------------------------------------------------------
/assets/views/500.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Server Error
5 |
6 |
7 |
8 |
9 |
500: Internal Server Error
10 | <% if (typeof error !== 'undefined') { %>
11 |
12 | <%- error %>
13 |
14 | <% } else { %>
15 |
16 |
17 | <% } %>
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/tasks/config/test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Clean files and folders.
3 | *
4 | * ---------------------------------------------------------------
5 | *
6 | * This grunt task is configured to clean out the contents in the .tmp/public of your
7 | * sails project.
8 | *
9 | * For usage docs see:
10 | * https://github.com/gruntjs/grunt-contrib-clean
11 | */
12 | module.exports = function (grunt) {
13 |
14 | grunt.config.set('mochaTest', {
15 | unit: {
16 | options: {
17 | reporter: 'spec'
18 | },
19 | src: ['test/**/*.js']
20 | }
21 | });
22 |
23 | grunt.loadNpmTasks('grunt-mocha-test');
24 |
25 | };
26 |
--------------------------------------------------------------------------------
/api/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "node": true
4 | },
5 | "globals": {
6 | "Reference": true,
7 | "User": true,
8 | "Users": true,
9 | "Ban": true,
10 | "Reddit": true,
11 | "Event": true,
12 | "sails": true,
13 | "Flair": true,
14 | "Flairs": true,
15 | "Usernotes": true,
16 | "PointLog": true,
17 | "References": true,
18 | "Sessions": true,
19 | "Game": true,
20 | "Application": true,
21 | "ModNote": true,
22 | "Modmail": true,
23 | "Modmails": true,
24 | "Team": true,
25 | "ContestStats": true,
26 | "Discord": true,
27 | "_": true
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/assets/search/ref/controller.js:
--------------------------------------------------------------------------------
1 | /* global Search */
2 | module.exports = function (req, res) {
3 | var params = req.allParams();
4 | if (!params.keyword) {
5 | return res.view("../search/main", {searchType: 'ref', searchTerm: ''});
6 | }
7 | var searchData = {
8 | description: params.keyword
9 | };
10 |
11 | if (params.user) {
12 | searchData.user = params.user;
13 | }
14 |
15 | if (params.categories) {
16 | searchData.categories = params.categories.split(",");
17 | }
18 |
19 | searchData.skip = params.skip || 0;
20 |
21 | Search.refs(searchData, function (results) {
22 | return res.ok(results);
23 | });
24 | };
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/panels.less:
--------------------------------------------------------------------------------
1 | // Panels
2 |
3 | .panel-variant(@border; @heading-text-color; @heading-bg-color; @heading-border) {
4 | border-color: @border;
5 |
6 | & > .panel-heading {
7 | color: @heading-text-color;
8 | background-color: @heading-bg-color;
9 | border-color: @heading-border;
10 |
11 | + .panel-collapse > .panel-body {
12 | border-top-color: @border;
13 | }
14 | .badge {
15 | color: @heading-bg-color;
16 | background-color: @heading-text-color;
17 | }
18 | }
19 | & > .panel-footer {
20 | + .panel-collapse > .panel-body {
21 | border-bottom-color: @border;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/assets/views/home/banlist.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
Ban list
7 |
8 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/wells.less:
--------------------------------------------------------------------------------
1 | //
2 | // Wells
3 | // --------------------------------------------------
4 |
5 |
6 | // Base class
7 | .well {
8 | min-height: 20px;
9 | padding: 19px;
10 | margin-bottom: 20px;
11 | background-color: @well-bg;
12 | border: 1px solid @well-border;
13 | border-radius: @border-radius-base;
14 | .box-shadow(inset 0 1px 1px rgba(0,0,0,.05));
15 | blockquote {
16 | border-color: #ddd;
17 | border-color: rgba(0,0,0,.15);
18 | }
19 | }
20 |
21 | // Sizes
22 | .well-lg {
23 | padding: 24px;
24 | border-radius: @border-radius-large;
25 | }
26 | .well-sm {
27 | padding: 9px;
28 | border-radius: @border-radius-small;
29 | }
30 |
--------------------------------------------------------------------------------
/api/models/Flair.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Flair.js
3 | *
4 | * @description :: TODO: You might write a short summary of how this model works and what it represents here.
5 | * @docs :: http://sailsjs.org/#!documentation/models
6 | */
7 |
8 | module.exports = {
9 |
10 | attributes: {
11 | name: "string",
12 | sub: "string",
13 |
14 | trades: {
15 | type: "integer",
16 | defaultsTo: 0
17 | },
18 | involvement: {
19 | type: "integer",
20 | defaultsTo: 0
21 | },
22 | eggs: {
23 | type: "integer",
24 | defaultsTo: 0
25 | },
26 | giveaways: {
27 | type: "integer",
28 | defaultsTo: 0
29 | }
30 | }
31 | };
32 |
33 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/list-group.less:
--------------------------------------------------------------------------------
1 | // List Groups
2 |
3 | .list-group-item-variant(@state; @background; @color) {
4 | .list-group-item-@{state} {
5 | color: @color;
6 | background-color: @background;
7 |
8 | a& {
9 | color: @color;
10 |
11 | .list-group-item-heading {
12 | color: inherit;
13 | }
14 |
15 | &:hover,
16 | &:focus {
17 | color: @color;
18 | background-color: darken(@background, 5%);
19 | }
20 | &.active,
21 | &.active:hover,
22 | &.active:focus {
23 | color: #fff;
24 | background-color: @color;
25 | border-color: @color;
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/hide-text.less:
--------------------------------------------------------------------------------
1 | // CSS image replacement
2 | //
3 | // Heads up! v3 launched with with only `.hide-text()`, but per our pattern for
4 | // mixins being reused as classes with the same name, this doesn't hold up. As
5 | // of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`.
6 | //
7 | // Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757
8 |
9 | // Deprecated as of v3.0.1 (will be removed in v4)
10 | .hide-text() {
11 | font: ~"0/0" a;
12 | color: transparent;
13 | text-shadow: none;
14 | background-color: transparent;
15 | border: 0;
16 | }
17 |
18 | // New mixin to use as of v3.0.1
19 | .text-hide() {
20 | .hide-text();
21 | }
22 |
--------------------------------------------------------------------------------
/config/schedule.js:
--------------------------------------------------------------------------------
1 | module.exports.schedule = {
2 | sailsInContext: true,
3 | tasks: {
4 | updateModmail: {
5 | cron : "0 8 * * *",
6 | task : function (context, sails) {
7 | sails.log.info('[Daily task]: Updating modmail archives...');
8 | Promise.all([Modmails.updateArchive('pokemontrades'), Modmails.updateArchive('SVExchange')]).then(function (results) {
9 | sails.log.info('[Daily task]: Finished updating modmail archives.');
10 | }, function (error) {
11 | sails.log.error('There was an issue updating the modmail archives.');
12 | sails.log.error(error);
13 | });
14 | },
15 | context : {}
16 | }
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/tasks/config/concat.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Concatenate files.
3 | *
4 | * ---------------------------------------------------------------
5 | *
6 | * Concatenates files javascript and css from a defined array. Creates concatenated files in
7 | * .tmp/public/contact directory
8 | * [concat](https://github.com/gruntjs/grunt-contrib-concat)
9 | *
10 | * For usage docs see:
11 | * https://github.com/gruntjs/grunt-contrib-concat
12 | */
13 | module.exports = function (grunt) {
14 |
15 | grunt.config.set('concat', {
16 | css: {
17 | src: require('../pipeline').cssFilesToInject,
18 | dest: '.tmp/public/concat/production.css'
19 | }
20 | });
21 |
22 | grunt.loadNpmTasks('grunt-contrib-concat');
23 | };
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # Compiled binary addons (http://nodejs.org/api/addons.html)
20 | build/Release
21 |
22 | # Dependency directory
23 | # Deployed apps should consider commenting this line out:
24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
25 | node_modules
26 |
27 | .tmp/
28 |
29 | .idea/
30 |
31 | config/local.js
32 |
33 | .DS_Store
34 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/clearfix.less:
--------------------------------------------------------------------------------
1 | // Clearfix
2 | //
3 | // For modern browsers
4 | // 1. The space content is one way to avoid an Opera bug when the
5 | // contenteditable attribute is included anywhere else in the document.
6 | // Otherwise it causes space to appear at the top and bottom of elements
7 | // that are clearfixed.
8 | // 2. The use of `table` rather than `block` is only necessary if using
9 | // `:before` to contain the top-margins of child elements.
10 | //
11 | // Source: http://nicolasgallagher.com/micro-clearfix-hack/
12 |
13 | .clearfix() {
14 | &:before,
15 | &:after {
16 | content: " "; // 1
17 | display: table; // 2
18 | }
19 | &:after {
20 | clear: both;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/breadcrumbs.less:
--------------------------------------------------------------------------------
1 | //
2 | // Breadcrumbs
3 | // --------------------------------------------------
4 |
5 |
6 | .breadcrumb {
7 | padding: @breadcrumb-padding-vertical @breadcrumb-padding-horizontal;
8 | margin-bottom: @line-height-computed;
9 | list-style: none;
10 | background-color: @breadcrumb-bg;
11 | border-radius: @border-radius-base;
12 |
13 | > li {
14 | display: inline-block;
15 |
16 | + li:before {
17 | content: "@{breadcrumb-separator}\00a0"; // Unicode space added since inline-block means non-collapsing white-space
18 | padding: 0 5px;
19 | color: @breadcrumb-color;
20 | }
21 | }
22 |
23 | > .active {
24 | color: @breadcrumb-active-color;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/responsive-embed.less:
--------------------------------------------------------------------------------
1 | // Embeds responsive
2 | //
3 | // Credit: Nicolas Gallagher and SUIT CSS.
4 |
5 | .embed-responsive {
6 | position: relative;
7 | display: block;
8 | height: 0;
9 | padding: 0;
10 | overflow: hidden;
11 |
12 | .embed-responsive-item,
13 | iframe,
14 | embed,
15 | object {
16 | position: absolute;
17 | top: 0;
18 | left: 0;
19 | bottom: 0;
20 | height: 100%;
21 | width: 100%;
22 | border: 0;
23 | }
24 |
25 | // Modifier class for 16:9 aspect ratio
26 | &.embed-responsive-16by9 {
27 | padding-bottom: 56.25%;
28 | }
29 |
30 | // Modifier class for 4:3 aspect ratio
31 | &.embed-responsive-4by3 {
32 | padding-bottom: 75%;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/api/controllers/SearchController.js:
--------------------------------------------------------------------------------
1 | var searchTypes = require("../../assets/search/types.js");
2 | var exportObject = {};
3 |
4 | for (let i = 0; i < searchTypes.length; i++) {
5 | // Let's programmatically add the views, because we can.
6 | let type = searchTypes[i];
7 | exportObject[type.short + "View"] = function (req, res) {
8 | return res.view("../search/main", {
9 | searchType: type.short,
10 | searchTerm: decodeURIComponent(req.params.searchterm)
11 | });
12 | };
13 | }
14 |
15 | for (let i = 0; i < searchTypes.length; i++) {
16 | // And here we will programmatically add the search functions
17 | let type = searchTypes[i];
18 | exportObject[type.short] = type.controller;
19 | }
20 |
21 |
22 | module.exports = exportObject;
--------------------------------------------------------------------------------
/assets/styles/bootstrap/component-animations.less:
--------------------------------------------------------------------------------
1 | //
2 | // Component animations
3 | // --------------------------------------------------
4 |
5 | // Heads up!
6 | //
7 | // We don't use the `.opacity()` mixin here since it causes a bug with text
8 | // fields in IE7-8. Source: https://github.com/twbs/bootstrap/pull/3552.
9 |
10 | .fade {
11 | opacity: 0;
12 | .transition(opacity .15s linear);
13 | &.in {
14 | opacity: 1;
15 | }
16 | }
17 |
18 | .collapse {
19 | display: none;
20 |
21 | &.in { display: block; }
22 | tr&.in { display: table-row; }
23 | tbody&.in { display: table-row-group; }
24 | }
25 |
26 | .collapsing {
27 | position: relative;
28 | height: 0;
29 | overflow: hidden;
30 | .transition(height .35s ease);
31 | }
32 |
--------------------------------------------------------------------------------
/api/models/Team.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Team.js
3 | *
4 | * @description :: TODO: You might write a short summary of how this model works and what it represents here.
5 | * @docs :: http://sailsjs.org/#!documentation/models
6 | */
7 |
8 | module.exports = {
9 | autoPK: false,
10 | attributes: {
11 | team: {
12 | type: "string",
13 | columnName: 'id',
14 | enum: ['kanto', 'alola'],
15 | primaryKey: true
16 | },
17 | members: {
18 | type: "array"
19 | },
20 | membershipPoints: {
21 | type: "integer"
22 | },
23 | battlePoints: {
24 | type: "integer"
25 | },
26 | contestPoints: {
27 | type: "integer"
28 | },
29 | triviaPoints: {
30 | type: "integer"
31 | }
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/api/policies/passport.js:
--------------------------------------------------------------------------------
1 | var passport = require('passport');
2 |
3 | module.exports = function (req, res, next) {
4 | // Initialize Passport
5 | passport.initialize()(req, res, function () {
6 | // Use the built-in sessions
7 | passport.session()(req, res, async function () {
8 | try {
9 | res.locals.user = req.user;
10 | if (req.user) {
11 | res.locals.user.games = await Game.find({user: req.user.name});
12 | }
13 | res.locals.query = req.query;
14 | res.locals.flairs = await Flairs.getFlairs();
15 | if (Users.hasModPermission(req.user, 'flair')) {
16 | res.locals.flairApps = await Flairs.getApps();
17 | }
18 | next();
19 | } catch (err) {
20 | return res.serverError(err);
21 | }
22 | });
23 | });
24 | };
25 |
--------------------------------------------------------------------------------
/tasks/config/less.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Compiles LESS files into CSS.
3 | *
4 | * ---------------------------------------------------------------
5 | *
6 | * Only the `assets/styles/importer.less` is compiled.
7 | * This allows you to control the ordering yourself, i.e. import your
8 | * dependencies, mixins, variables, resets, etc. before other stylesheets)
9 | *
10 | * For usage docs see:
11 | * https://github.com/gruntjs/grunt-contrib-less
12 | */
13 | module.exports = function (grunt) {
14 |
15 | grunt.config.set('less', {
16 | dev: {
17 | files: [{
18 | expand: true,
19 | cwd: 'assets/styles/',
20 | src: ['importer.less'],
21 | dest: '.tmp/public/styles/',
22 | ext: '.css'
23 | }]
24 | }
25 | });
26 |
27 | grunt.loadNpmTasks('grunt-contrib-less');
28 | };
29 |
--------------------------------------------------------------------------------
/api/policies/sessionAuth.js:
--------------------------------------------------------------------------------
1 | /**
2 | * sessionAuth
3 | *
4 | * @module :: Policy
5 | * @description :: Simple policy to allow any authenticated user
6 | * Assumes that your login action in one of your controllers sets `req.session.authenticated = true;`
7 | * @docs :: http://sailsjs.org/#!documentation/policies
8 | *
9 | */
10 | module.exports = function(req, res, next) {
11 | if (req.user) {
12 | if (req.user.banned) {
13 | req.session.destroy();
14 | return res.view(403, {error: "You have been banned from FlairHQ"});
15 | }
16 | return next();
17 | }
18 | if (req.isSocket) {
19 | return res.status(403).json({status: 403, redirectTo: "/login"});
20 | }
21 | return res.redirect('/login' + (req.url !== '/' ? '?redirect=' + encodeURIComponent(req.url) : ''));
22 | };
23 |
--------------------------------------------------------------------------------
/assets/views/404.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Page Not Found
5 |
6 |
7 |
8 |
9 |
10 | <%if (data && data.user) {%>
11 |
User <%=data.user%> hasn't created a reference page on FlairHQ yet!
12 |
Be sure you have the right username.
13 | <% } else {%>
14 |
404 Page Not Found
15 |
Judging Scatterbug judges you
16 | <% }%>
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/assets/markdown/markdown.module.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A module to give us a nice, easy way to use markdown in the app
3 | */
4 |
5 | var angular = require("angular");
6 | var Snudown = require("snudown-js");
7 | var remapURLs = require("./remapURLs");
8 |
9 | module.exports = angular.module("fapp.md", [])
10 | .directive("md", function () {
11 | return {
12 | restrict: "E",
13 | require: "?ngModel",
14 | link: function ($scope, $elem, $attrs, ngModel) {
15 | if (!ngModel) {
16 | var html = remapURLs(Snudown.markdown($elem.text()));
17 | $elem.html(html);
18 | return;
19 | }
20 | ngModel.$render = function () {
21 | var html = remapURLs(Snudown.markdown(ngModel.$viewValue || ""));
22 | $elem.html(html);
23 | };
24 | }
25 | };
26 | });
--------------------------------------------------------------------------------
/assets/views/home/profileInfo.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{refUser.name}}'s Information
4 |
5 |
6 |
7 |
8 |
9 |
Introduction:
10 |
11 |
12 |
13 |
Friend codes:
14 |
17 |
18 |
Games:
19 |
20 |
22 | IGN: {{game.ign}}
23 | TSV: {{game.tsv | numberPadding:4}}
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/close.less:
--------------------------------------------------------------------------------
1 | //
2 | // Close icons
3 | // --------------------------------------------------
4 |
5 |
6 | .close {
7 | float: right;
8 | font-size: (@font-size-base * 1.5);
9 | font-weight: @close-font-weight;
10 | line-height: 1;
11 | color: @close-color;
12 | text-shadow: @close-text-shadow;
13 | .opacity(.2);
14 |
15 | &:hover,
16 | &:focus {
17 | color: @close-color;
18 | text-decoration: none;
19 | cursor: pointer;
20 | .opacity(.5);
21 | }
22 |
23 | // Additional properties for button version
24 | // iOS requires the button element instead of an anchor tag.
25 | // If you want the anchor version, it requires `href="#"`.
26 | button& {
27 | padding: 0;
28 | cursor: pointer;
29 | background: transparent;
30 | border: 0;
31 | -webkit-appearance: none;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/table-row.less:
--------------------------------------------------------------------------------
1 | // Tables
2 |
3 | .table-row-variant(@state; @background) {
4 | // Exact selectors below required to override `.table-striped` and prevent
5 | // inheritance to nested tables.
6 | .table > thead > tr,
7 | .table > tbody > tr,
8 | .table > tfoot > tr {
9 | > td.@{state},
10 | > th.@{state},
11 | &.@{state} > td,
12 | &.@{state} > th {
13 | background-color: @background;
14 | }
15 | }
16 |
17 | // Hover states for `.table-hover`
18 | // Note: this is not available for cells or rows within `thead` or `tfoot`.
19 | .table-hover > tbody > tr {
20 | > td.@{state}:hover,
21 | > th.@{state}:hover,
22 | &.@{state}:hover > td,
23 | &:hover > .@{state},
24 | &.@{state}:hover > th {
25 | background-color: darken(@background, 5%);
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/assets/search/README.md:
--------------------------------------------------------------------------------
1 | # Adding a new search function (client side)
2 |
3 | Assuming you have done everything client, side, because that is simple, there are a few small things that are not
4 | obvious with the client side code.
5 |
6 | Firstly, you need to add a new directory (obvious) and have a form.ejs and result.ejs files. These are for the
7 | advanced form (for the advanced page) and the results, for both the advanced page and the dropdown from the header.
8 |
9 | Then you need to add an option to the ./header.ejs file pointing to the right result.ejs file. The reason this can't be
10 | automated, is that we can't loop over the directories in ejs, unless we have an array somewhere of what ones we have, and
11 | that feels messier than this. Maybe. I don't know, we can probably create one sometime or figure out a nicer way to do it.
12 |
13 | I'm sure there is a nice way somewhere...
--------------------------------------------------------------------------------
/assets/styles/bootstrap/thumbnails.less:
--------------------------------------------------------------------------------
1 | //
2 | // Thumbnails
3 | // --------------------------------------------------
4 |
5 |
6 | // Mixin and adjust the regular image class
7 | .thumbnail {
8 | display: block;
9 | padding: @thumbnail-padding;
10 | margin-bottom: @line-height-computed;
11 | line-height: @line-height-base;
12 | background-color: @thumbnail-bg;
13 | border: 1px solid @thumbnail-border;
14 | border-radius: @thumbnail-border-radius;
15 | .transition(all .2s ease-in-out);
16 |
17 | > img,
18 | a > img {
19 | &:extend(.img-responsive);
20 | margin-left: auto;
21 | margin-right: auto;
22 | }
23 |
24 | // Add a hover state for linked versions only
25 | a&:hover,
26 | a&:focus,
27 | a&.active {
28 | border-color: @link-color;
29 | }
30 |
31 | // Image captions
32 | .caption {
33 | padding: @thumbnail-caption-padding;
34 | color: @thumbnail-caption-color;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/test/unit/data/flairTexts.json:
--------------------------------------------------------------------------------
1 | {
2 | "tradesFlairStd": "1111-1111-1111 || YMK (X)",
3 | "tradesFlairMultipleFCs": "1111-1111-1111, 2222-2222-2222 || YMK (X)",
4 | "svexFlairStd": "1111-1111-1111 || YMK (X) || 1234",
5 | "svexFlairDifferentFC": "2222-2222-2222 || YMK (X) || 1234",
6 | "svexFlairBadTSV": "1111-2222-3333 || NAA (Y) || COOKIES",
7 | "incorrectFlair": "not a correct flair",
8 | "SMFlair": "1111-1111-1111 || YMK (S) || Nothing",
9 | "SMFlair2": "1111-1111-1111 || YMK (S, M), YaManicKill (X, Y) || Nothing",
10 | "lotsOfGames": {
11 | "ptrades": "1111-1111-1111 || AAA (Y, ΩR), Joe, Bob (X)",
12 | "svex": "1111-9999-1111 || AAA (Y, ΩR), Joe, Bob (X) || 0000",
13 | "fcs": ["1111-1111-1111", "1111-9999-1111"],
14 | "games": [
15 | {"ign": "AAA", "game": "Y"},
16 | {"ign": "AAA", "game": "ΩR"},
17 | {"ign": "Joe", "game": ""},
18 | {"ign": "Bob", "game": "X"}
19 | ],
20 | "tsvs": ["0000"]
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/config/env/development.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Development environment settings
3 | *
4 | * This file can include shared settings for a development team,
5 | * such as API keys or remote database passwords. If you're using
6 | * a version control solution for your Sails app, this file will
7 | * be committed to your repository unless you add it to your .gitignore
8 | * file. If your repository will be publicly viewable, don't add
9 | * any private information to this file!
10 | *
11 | */
12 |
13 | module.exports = {
14 |
15 | log: {
16 | level: "verbose"
17 | }
18 |
19 | /***************************************************************************
20 | * Set the default database connection for models in the development *
21 | * environment (see config/connections.js and config/models.js ) *
22 | ***************************************************************************/
23 |
24 | // models: {
25 | // connection: 'someMongodbServer'
26 | // }
27 |
28 | };
29 |
--------------------------------------------------------------------------------
/assets/search/ref/form.ejs:
--------------------------------------------------------------------------------
1 |
2 | Search keyword
3 |
6 | User
7 |
9 |
10 |
--------------------------------------------------------------------------------
/tasks/config/copy.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copy files and folders.
3 | *
4 | * ---------------------------------------------------------------
5 | *
6 | * # dev task config
7 | * Copies all directories and files, exept coffescript and less fiels, from the sails
8 | * assets folder into the .tmp/public directory.
9 | *
10 | * # build task config
11 | * Copies all directories nd files from the .tmp/public directory into a www directory.
12 | *
13 | * For usage docs see:
14 | * https://github.com/gruntjs/grunt-contrib-copy
15 | */
16 | module.exports = function (grunt) {
17 |
18 | grunt.config.set('copy', {
19 | dev: {
20 | files: [{
21 | expand: true,
22 | cwd: './assets',
23 | src: ['**/*.!(coffee|less)'],
24 | dest: '.tmp/public'
25 | }]
26 | },
27 | build: {
28 | files: [{
29 | expand: true,
30 | cwd: '.tmp/public',
31 | src: ['**/*'],
32 | dest: 'www'
33 | }]
34 | }
35 | });
36 |
37 | grunt.loadNpmTasks('grunt-contrib-copy');
38 | };
39 |
--------------------------------------------------------------------------------
/test/unit/data/markdownStrings.json:
--------------------------------------------------------------------------------
1 | {
2 | "userUrl": " /u/test ",
3 | "userUrlAfter": "/u/test (FlairHQ )",
4 | "userUrlWithExtra": "/u/test something else ",
5 | "userUrlWithExtraAfter": "/u/test (FlairHQ )something else ",
6 | "userUrlWrong": "/u/not_test ",
7 | "userUrlWrongAfter": "/u/not_test ",
8 | "subUrl": "/r/test ",
9 | "subUrlAfter": "/r/test ",
10 | "subUrlWithExtra": "/r/test something else ",
11 | "subUrlWithExtraAfter": "/r/test something else ",
12 | "subUrlWrong": "/r/not-test ",
13 | "subUrlWrongAfter": "/r/not-test "
14 | }
--------------------------------------------------------------------------------
/assets/styles/bootstrap/utilities.less:
--------------------------------------------------------------------------------
1 | //
2 | // Utility classes
3 | // --------------------------------------------------
4 |
5 |
6 | // Floats
7 | // -------------------------
8 |
9 | .clearfix {
10 | .clearfix();
11 | }
12 | .center-block {
13 | .center-block();
14 | }
15 | .pull-right {
16 | float: right !important;
17 | }
18 | .pull-left {
19 | float: left !important;
20 | }
21 |
22 |
23 | // Toggling content
24 | // -------------------------
25 |
26 | // Note: Deprecated .hide in favor of .hidden or .sr-only (as appropriate) in v3.0.1
27 | .hide {
28 | display: none !important;
29 | }
30 | .show {
31 | display: block !important;
32 | }
33 | .invisible {
34 | visibility: hidden;
35 | }
36 | .text-hide {
37 | .text-hide();
38 | }
39 |
40 |
41 | // Hide from screenreaders and browsers
42 | //
43 | // Credit: HTML5 Boilerplate
44 |
45 | .hidden {
46 | display: none !important;
47 | visibility: hidden !important;
48 | }
49 |
50 |
51 | // For Affix plugin
52 | // -------------------------
53 |
54 | .affix {
55 | position: fixed;
56 | .translate3d(0, 0, 0);
57 | }
58 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/media.less:
--------------------------------------------------------------------------------
1 | // Media objects
2 | // Source: http://stubbornella.org/content/?p=497
3 | // --------------------------------------------------
4 |
5 |
6 | // Common styles
7 | // -------------------------
8 |
9 | // Clear the floats
10 | .media,
11 | .media-body {
12 | overflow: hidden;
13 | zoom: 1;
14 | }
15 |
16 | // Proper spacing between instances of .media
17 | .media,
18 | .media .media {
19 | margin-top: 15px;
20 | }
21 | .media:first-child {
22 | margin-top: 0;
23 | }
24 |
25 | // For images and videos, set to block
26 | .media-object {
27 | display: block;
28 | }
29 |
30 | // Reset margins on headings for tighter default spacing
31 | .media-heading {
32 | margin: 0 0 5px;
33 | }
34 |
35 |
36 | // Media image alignment
37 | // -------------------------
38 |
39 | .media {
40 | > .pull-left {
41 | margin-right: 10px;
42 | }
43 | > .pull-right {
44 | margin-left: 10px;
45 | }
46 | }
47 |
48 |
49 | // Media list variation
50 | // -------------------------
51 |
52 | // Undo default ul/ol styles
53 | .media-list {
54 | padding-left: 0;
55 | list-style: none;
56 | }
57 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/pager.less:
--------------------------------------------------------------------------------
1 | //
2 | // Pager pagination
3 | // --------------------------------------------------
4 |
5 |
6 | .pager {
7 | padding-left: 0;
8 | margin: @line-height-computed 0;
9 | list-style: none;
10 | text-align: center;
11 | &:extend(.clearfix all);
12 | li {
13 | display: inline;
14 | > a,
15 | > span {
16 | display: inline-block;
17 | padding: 5px 14px;
18 | background-color: @pager-bg;
19 | border: 1px solid @pager-border;
20 | border-radius: @pager-border-radius;
21 | }
22 |
23 | > a:hover,
24 | > a:focus {
25 | text-decoration: none;
26 | background-color: @pager-hover-bg;
27 | }
28 | }
29 |
30 | .next {
31 | > a,
32 | > span {
33 | float: right;
34 | }
35 | }
36 |
37 | .previous {
38 | > a,
39 | > span {
40 | float: left;
41 | }
42 | }
43 |
44 | .disabled {
45 | > a,
46 | > a:hover,
47 | > a:focus,
48 | > span {
49 | color: @pager-disabled-color;
50 | background-color: @pager-bg;
51 | cursor: not-allowed;
52 | }
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins.less:
--------------------------------------------------------------------------------
1 | // Mixins
2 | // --------------------------------------------------
3 |
4 | // Utilities
5 | @import "mixins/hide-text";
6 | @import "mixins/opacity";
7 | @import "mixins/image";
8 | @import "mixins/labels";
9 | @import "mixins/reset-filter";
10 | @import "mixins/resize";
11 | @import "mixins/responsive-visibility";
12 | @import "mixins/size";
13 | @import "mixins/tab-focus";
14 | @import "mixins/text-emphasis";
15 | @import "mixins/text-overflow";
16 | @import "mixins/vendor-prefixes";
17 |
18 | // Components
19 | @import "mixins/alerts";
20 | @import "mixins/buttons";
21 | @import "mixins/panels";
22 | @import "mixins/pagination";
23 | @import "mixins/list-group";
24 | @import "mixins/nav-divider";
25 | @import "mixins/forms";
26 | @import "mixins/progress-bar";
27 | @import "mixins/table-row";
28 |
29 | // Skins
30 | @import "mixins/background-variant";
31 | @import "mixins/border-radius";
32 | @import "mixins/gradients";
33 |
34 | // Layout
35 | @import "mixins/clearfix";
36 | @import "mixins/center-block";
37 | @import "mixins/nav-vertical-align";
38 | @import "mixins/grid-framework";
39 | @import "mixins/grid";
40 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/bootstrap.less:
--------------------------------------------------------------------------------
1 | // Core variables and mixins
2 | @import "variables";
3 | @import "mixins";
4 |
5 | // Reset and dependencies
6 | @import "normalize";
7 | @import "print";
8 | @import "glyphicons";
9 |
10 | // Core CSS
11 | @import "scaffolding";
12 | @import "type";
13 | @import "code";
14 | @import "grid";
15 | @import "tables";
16 | @import "forms";
17 | @import "buttons";
18 |
19 | // Components
20 | @import "component-animations";
21 | @import "dropdowns";
22 | @import "button-groups";
23 | @import "input-groups";
24 | @import "navs";
25 | @import "navbar";
26 | @import "breadcrumbs";
27 | @import "pagination";
28 | @import "pager";
29 | @import "labels";
30 | @import "badges";
31 | @import "jumbotron";
32 | @import "thumbnails";
33 | @import "alerts";
34 | @import "progress-bars";
35 | @import "media";
36 | @import "list-group";
37 | @import "panels";
38 | @import "responsive-embed";
39 | @import "wells";
40 | @import "close";
41 |
42 | // Components w/ JavaScript
43 | @import "modals";
44 | @import "tooltip";
45 | @import "popovers";
46 | @import "carousel";
47 |
48 | // Utility classes
49 | @import "utilities";
50 | @import "responsive-utilities";
51 |
--------------------------------------------------------------------------------
/api/models/Reference.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Reference.js
3 | *
4 | * @description :: TODO: You might write a short summary of how this model works and what it represents here.
5 | * @docs :: http://sailsjs.org/#!documentation/models
6 | */
7 |
8 | module.exports = {
9 |
10 | attributes: {
11 | url: {
12 | type: "string"
13 | },
14 | user: "string",
15 | user2: "string",
16 | gave: "string",
17 | got: "string",
18 | description: "string",
19 | number: {
20 | type: "integer",
21 | min: 0,
22 | required: false
23 | },
24 | type: {
25 | type: "string",
26 | enum: [
27 | "event",
28 | "shiny",
29 | "casual",
30 | "bank",
31 | "egg",
32 | "giveaway",
33 | "involvement",
34 | "eggcheck",
35 | "misc"
36 | ]
37 | },
38 | // This is true if the other user has added the same url, and the trade has been approved.
39 | verified: "boolean",
40 | // This defines if the mods have approved it as a trade that can count
41 | approved: "boolean",
42 | edited: "boolean",
43 | notes: "string",
44 | privatenotes: "string"
45 | }
46 | };
47 |
--------------------------------------------------------------------------------
/config/connections.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Connections
3 | * (sails.config.connections)
4 | *
5 | * `Connections` are like "saved settings" for your adapters. What's the difference between
6 | * a connection and an adapter, you might ask? An adapter (e.g. `sails-mysql`) is generic--
7 | * it needs some additional information to work (e.g. your database host, password, user, etc.)
8 | * A `connection` is that additional information.
9 | *
10 | * Each model must have a `connection` property (a string) which is references the name of one
11 | * of these connections. If it doesn't, the default `connection` configured in `config/models.js`
12 | * will be applied. Of course, a connection can (and usually is) shared by multiple models.
13 | * .
14 | * Note: If you're using version control, you should put your passwords/api keys
15 | * in `config/local.js`, environment variables, or use another strategy.
16 | * (this is to prevent you inadvertently sensitive credentials up to your repository.)
17 | *
18 | * For more information on configuration, check out:
19 | * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.connections.html
20 | */
21 |
22 | module.exports.connections = {
23 |
24 | };
--------------------------------------------------------------------------------
/assets/styles/bootstrap/jumbotron.less:
--------------------------------------------------------------------------------
1 | //
2 | // Jumbotron
3 | // --------------------------------------------------
4 |
5 |
6 | .jumbotron {
7 | padding: @jumbotron-padding;
8 | margin-bottom: @jumbotron-padding;
9 | color: @jumbotron-color;
10 | background-color: @jumbotron-bg;
11 |
12 | h1,
13 | .h1 {
14 | color: @jumbotron-heading-color;
15 | }
16 | p {
17 | margin-bottom: (@jumbotron-padding / 2);
18 | font-size: @jumbotron-font-size;
19 | font-weight: 200;
20 | }
21 |
22 | > hr {
23 | border-top-color: darken(@jumbotron-bg, 10%);
24 | }
25 |
26 | .container & {
27 | border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container
28 | }
29 |
30 | .container {
31 | max-width: 100%;
32 | }
33 |
34 | @media screen and (min-width: @screen-sm-min) {
35 | padding-top: (@jumbotron-padding * 1.6);
36 | padding-bottom: (@jumbotron-padding * 1.6);
37 |
38 | .container & {
39 | padding-left: (@jumbotron-padding * 2);
40 | padding-right: (@jumbotron-padding * 2);
41 | }
42 |
43 | h1,
44 | .h1 {
45 | font-size: (@font-size-base * 4.5);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/assets/tooltip/tooltip.module.js:
--------------------------------------------------------------------------------
1 | var ng = require("angular");
2 | var $ = require('jquery');
3 |
4 | ng.module("tooltipModule", []).directive("ngTooltip", function () {
5 | return {
6 | restrict: 'E',
7 | replace: true,
8 | scope: {
9 | title: '@title',
10 | label: '@label'
11 | },
12 | templateUrl: function (tElement, tAttrs) {
13 | if (tAttrs.unlabeled) {
14 | return '/tooltip/tooltip.view.html';
15 | } else {
16 | return '/tooltip/label.view.html';
17 | }
18 | },
19 | transclude: true,
20 | link: function (scope, element) {
21 | var thisElement = $(element[0]).find('[data-toggle=tooltip]');
22 | thisElement.tooltip({
23 | html: true,
24 | trigger: 'manual',
25 | title: scope.title
26 | }).on("mouseenter", function () {
27 | thisElement.tooltip("show");
28 | $(".tooltip").on("mouseleave", function () {
29 | thisElement.tooltip('hide');
30 | });
31 | }).on("mouseleave", function () {
32 | setTimeout(function () {
33 | if (!$(".tooltip:hover").length) {
34 | thisElement.tooltip("hide");
35 | }
36 | }, 100);
37 | });
38 | }
39 | };
40 | });
--------------------------------------------------------------------------------
/config/local.example.js:
--------------------------------------------------------------------------------
1 | // This is an example file. Be sure to rename it to local.js and add valid credentials.
2 | module.exports = {
3 | port: 1337,
4 | environment: "development",
5 | hookTimeout: "50000",
6 | reddit: {
7 | clientID: "CLIENT ID GOES HERE",
8 | clientIDSecret: "SECRET ID GOES HERE",
9 | redirectURL: "http://localhost:1337/auth/reddit/callback",
10 | adminRefreshToken: "ADMIN REFRESH TOKEN GOES HERE",
11 | userAgent: 'FlairHQ development version by /u/DEVELOPERS_USERNAME || hq.porygon.co/info || v' + require('../package.json').version
12 | },
13 | connections: {
14 | "default": "mongo",
15 | mongo: {
16 | adapter: 'sails-mongo',
17 | host: 'localhost',
18 | port: 27017,
19 | user: '',
20 | password: '',
21 | database: 'fapp'
22 | }
23 | },
24 | session: {
25 | adapter: 'mongo',
26 | host: 'localhost',
27 | port: 27017,
28 | db: 'fapp',
29 | collection: 'sessions'
30 | },
31 | discord: {
32 | client_id: 'ID GOES HERE',
33 | client_secret: 'SECRET HERE',
34 | redirect_host: 'http://localhost:1337',
35 | server_id: '111111',
36 | authenticatedRole_id: ['2222222'],
37 | bot_token: 'aaaaaaaaaaa'
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/api/models/Modmail.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 |
3 | types: {
4 | stringOrNull: function (val) {
5 | return typeof val === 'string' || val === null;
6 | }
7 | },
8 |
9 | autoPK: false,
10 |
11 | attributes: {
12 | name: { //The fullname ('t4_' + base36id) of the message
13 | columnName: 'id',
14 | type: 'string',
15 | unique: true,
16 | primaryKey: true
17 | },
18 | subject: 'string', //Subject of the message
19 | body: 'string', //Body of the message
20 | author: 'string', //Username of the message author
21 | subreddit: { //The subreddit that the modmail was sent to
22 | enum: ['pokemontrades', 'SVExchange']
23 | },
24 | first_message_name: { //The fullname of the first message in this chain, or null if this is the first message
25 | stringOrNull: true
26 | },
27 | created_utc: { //The UTC timestamp of when the message was created
28 | type: 'integer'
29 | },
30 | parent_id: { //The fullname of the parent message, or null if this is the first message
31 | stringOrNull: true
32 | },
33 | distinguished: { //This will be 'moderator' if the author was a mod, 'admin' if the author was a reddit admin, or null otherwise
34 | enum: ['moderator', 'admin', null]
35 | }
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/image.less:
--------------------------------------------------------------------------------
1 | // Image Mixins
2 | // - Responsive image
3 | // - Retina image
4 |
5 |
6 | // Responsive image
7 | //
8 | // Keep images from scaling beyond the width of their parents.
9 | .img-responsive(@display: block) {
10 | display: @display;
11 | width: 100% \9; // Force IE10 and below to size SVG images correctly
12 | max-width: 100%; // Part 1: Set a maximum relative to the parent
13 | height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching
14 | }
15 |
16 |
17 | // Retina image
18 | //
19 | // Short retina mixin for setting background-image and -size. Note that the
20 | // spelling of `min--moz-device-pixel-ratio` is intentional.
21 | .img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {
22 | background-image: url("@{file-1x}");
23 |
24 | @media
25 | only screen and (-webkit-min-device-pixel-ratio: 2),
26 | only screen and ( min--moz-device-pixel-ratio: 2),
27 | only screen and ( -o-min-device-pixel-ratio: 2/1),
28 | only screen and ( min-device-pixel-ratio: 2),
29 | only screen and ( min-resolution: 192dpi),
30 | only screen and ( min-resolution: 2dppx) {
31 | background-image: url("@{file-2x}");
32 | background-size: @width-1x @height-1x;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/config/locales/_README.md:
--------------------------------------------------------------------------------
1 | # Internationalization / Localization Settings
2 |
3 | > Also see the official docs on internationalization/localization:
4 | > http://links.sailsjs.org/docs/config/locales
5 |
6 | ## Locales
7 | All locale files live under `config/locales`. Here is where you can add translations
8 | as JSON key-value pairs. The name of the file should match the language that you are supporting, which allows for automatic language detection based on request headers.
9 |
10 | Here is an example locale stringfile for the Spanish language (`config/locales/es.json`):
11 | ```json
12 | {
13 | "Hello!": "Hola!",
14 | "Hello %s, how are you today?": "¿Hola %s, como estas?",
15 | }
16 | ```
17 | ## Usage
18 | Locales can be accessed in controllers/policies through `res.i18n()`, or in views through the `__(key)` or `i18n(key)` functions.
19 | Remember that the keys are case sensitive and require exact key matches, e.g.
20 |
21 | ```ejs
22 | <%= __('Welcome to PencilPals!') %>
23 | <%= i18n('Hello %s, how are you today?', 'Pencil Maven') %>
24 | <%= i18n('That\'s right-- you can use either i18n() or __()') %>
25 | ```
26 |
27 | ## Configuration
28 | Localization/internationalization config can be found in `config/i18n.js`, from where you can set your supported locales.
29 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/buttons.less:
--------------------------------------------------------------------------------
1 | // Button variants
2 | //
3 | // Easily pump out default styles, as well as :hover, :focus, :active,
4 | // and disabled options for all buttons
5 |
6 | .button-variant(@color; @background; @border) {
7 | color: @color;
8 | background-color: @background;
9 | border-color: @border;
10 |
11 | &:hover,
12 | &:focus,
13 | &:active,
14 | &.active,
15 | .open > .dropdown-toggle& {
16 | color: @color;
17 | background-color: darken(@background, 10%);
18 | border-color: darken(@border, 12%);
19 | }
20 | &:active,
21 | &.active,
22 | .open > .dropdown-toggle& {
23 | background-image: none;
24 | }
25 | &.disabled,
26 | &[disabled],
27 | fieldset[disabled] & {
28 | &,
29 | &:hover,
30 | &:focus,
31 | &:active,
32 | &.active {
33 | background-color: @background;
34 | border-color: @border;
35 | }
36 | }
37 |
38 | .badge {
39 | color: @background;
40 | background-color: @color;
41 | }
42 | }
43 |
44 | // Button sizes
45 | .button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {
46 | padding: @padding-vertical @padding-horizontal;
47 | font-size: @font-size;
48 | line-height: @line-height;
49 | border-radius: @border-radius;
50 | }
51 |
--------------------------------------------------------------------------------
/api/services/Modmails.js:
--------------------------------------------------------------------------------
1 | var relevantKeys = ['name', 'subject', 'body', 'author', 'subreddit', 'first_message_name', 'created_utc', 'parent_id', 'distinguished'];
2 | var makeModmailObjects = function (modmails) {
3 | var all_modmails = [];
4 | for (let i = 0; i < modmails.length; i++) {
5 | let compressed = {};
6 | for (let j = 0; j < relevantKeys.length; j++) {
7 | compressed[relevantKeys[j]] = modmails[i].data[relevantKeys[j]];
8 | }
9 | all_modmails.push(compressed);
10 | if (modmails[i].data.replies) {
11 | all_modmails = all_modmails.concat(makeModmailObjects(modmails[i].data.replies.data.children));
12 | }
13 | }
14 | return all_modmails;
15 | };
16 | exports.updateArchive = async function (subreddit) {
17 | let most_recent = await Modmail.find({subreddit: subreddit, limit: 1, sort: 'created_utc DESC'});
18 | if (!most_recent.length) {
19 | sails.log.warn('Modmail archives for /r/' + subreddit + ' could not be found for some reason. Recreating from scratch...');
20 | return Modmail.findOrCreate(makeModmailObjects(await Reddit.getModmail(sails.config.reddit.adminRefreshToken, subreddit)));
21 | }
22 | return Modmail.findOrCreate(makeModmailObjects(await Reddit.getModmail(sails.config.reddit.adminRefreshToken, subreddit, undefined, most_recent[0].name)));
23 | };
24 |
--------------------------------------------------------------------------------
/tasks/config/watch.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Run predefined tasks whenever watched file patterns are added, changed or deleted.
3 | *
4 | * ---------------------------------------------------------------
5 | *
6 | * Watch for changes on
7 | * - files in the `assets` folder
8 | * - the `tasks/pipeline.js` file
9 | * and re-run the appropriate tasks.
10 | *
11 | * For usage docs see:
12 | * https://github.com/gruntjs/grunt-contrib-watch
13 | *
14 | */
15 | module.exports = function (grunt) {
16 |
17 | grunt.config.set('watch', {
18 | api: {
19 | files: ['api/**/*']
20 | },
21 | less: {
22 | files: [
23 | 'assets/styles/**/*'
24 | ],
25 | tasks: [
26 | 'less:dev'
27 | ],
28 | options: {
29 | livereload: true,
30 | livereloadOnError: false
31 | }
32 | },
33 | js: {
34 | files: [
35 | 'assets/**/*.js'
36 | ],
37 | tasks: [
38 | 'copy:dev',
39 | 'eslint'
40 | ],
41 | options: {
42 | livereload: true,
43 | livereloadOnError: false
44 | }
45 | }
46 | });
47 |
48 | grunt.config.set('focus', {
49 | dev: {
50 | include: ['less', 'js']
51 | }
52 | });
53 |
54 | grunt.loadNpmTasks('grunt-focus');
55 | grunt.loadNpmTasks('grunt-contrib-watch');
56 | };
57 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/badges.less:
--------------------------------------------------------------------------------
1 | //
2 | // Badges
3 | // --------------------------------------------------
4 |
5 |
6 | // Base class
7 | .badge {
8 | display: inline-block;
9 | min-width: 10px;
10 | padding: 3px 7px;
11 | font-size: @font-size-small;
12 | font-weight: @badge-font-weight;
13 | color: @badge-color;
14 | line-height: @badge-line-height;
15 | vertical-align: baseline;
16 | white-space: nowrap;
17 | text-align: center;
18 | background-color: @badge-bg;
19 | border-radius: @badge-border-radius;
20 |
21 | // Empty badges collapse automatically (not available in IE8)
22 | &:empty {
23 | display: none;
24 | }
25 |
26 | // Quick fix for badges in buttons
27 | .btn & {
28 | position: relative;
29 | top: -1px;
30 | }
31 | .btn-xs & {
32 | top: 0;
33 | padding: 1px 5px;
34 | }
35 |
36 | // Hover state, but only for links
37 | a& {
38 | &:hover,
39 | &:focus {
40 | color: @badge-link-hover-color;
41 | text-decoration: none;
42 | cursor: pointer;
43 | }
44 | }
45 |
46 | // Account for badges in navs
47 | a.list-group-item.active > &,
48 | .nav-pills > .active > a > & {
49 | color: @badge-active-color;
50 | background-color: @badge-active-bg;
51 | }
52 | .nav-pills > li > a > & {
53 | margin-left: 3px;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: cpp
2 | compiler: gcc
3 | sudo: false
4 |
5 | os:
6 | - linux
7 | - osx
8 |
9 | env:
10 | global:
11 | - SKIP_SASS_BINARY_DOWNLOAD_FOR_CI=true
12 | matrix:
13 | - export NODE_VERSION="6"
14 |
15 | matrix:
16 | fast_finish: true
17 |
18 | addons:
19 | apt:
20 | sources:
21 | - ubuntu-toolchain-r-test
22 | packages:
23 | - gcc-4.7
24 | - g++-4.7
25 |
26 |
27 | before_install:
28 | - git submodule update --init --recursive
29 | - git clone https://github.com/creationix/nvm.git ./.nvm
30 | - source ./.nvm/nvm.sh
31 | - nvm install $NODE_VERSION
32 | - nvm use $NODE_VERSION
33 | - npm config set python `which python`
34 | - if [ $TRAVIS_OS_NAME == "linux" ]; then
35 | export CC="gcc-4.7";
36 | export CXX="g++-4.7";
37 | export LINK="gcc-4.7";
38 | export LINKXX="g++-4.7";
39 | sudo apt install build-essential checkinstall libssl-dev;
40 | fi
41 | - gcc --version
42 | - g++ --version
43 | - rm package-lock.json
44 | - npm cache clean --f
45 | - rm -rf node_modules
46 | - npm install -g npm@latest-6
47 |
48 | before_script:
49 | - npm install -g grunt-cli
50 | - npm install -g eslint
51 |
52 | script:
53 | - npm install
54 | - npm test
55 |
56 | cache:
57 | directories:
58 | - $HOME/.node-gyp
59 | - $HOME/.npm
60 |
--------------------------------------------------------------------------------
/api/models/User.js:
--------------------------------------------------------------------------------
1 | /**
2 | * User.js
3 | *
4 | * @description :: TODO: You might write a short summary of how this model works and what it represents here.
5 | * @docs :: http://sailsjs.org/#!documentation/models
6 | */
7 |
8 | module.exports = {
9 |
10 | types: {
11 | friendCodeFormat: function (codes) {
12 | for (var code in codes) {
13 | var patt = /(?:SW-)?([0-9]{4})(-?)(?:([0-9]{4})\2)([0-9]{4})/;
14 | if (!patt.test(codes[code])) {
15 | return false;
16 | }
17 | }
18 | return true;
19 | }
20 | },
21 |
22 | autoPK: false,
23 |
24 | attributes: {
25 | provider: 'STRING',
26 | name: {
27 | type: "string",
28 | columnName: 'id',
29 | unique: true,
30 | primaryKey: true
31 | },
32 | email: "string",
33 | firstname: "string",
34 | lastname: "string",
35 | intro: {
36 | type: "text",
37 | maxLength: 10000
38 | },
39 | friendCodes: {
40 | type: "array",
41 | friendCodeFormat: true
42 | },
43 | loggedFriendCodes: {
44 | type: "array",
45 | friendCodeFormat: true
46 | },
47 | isMod: "boolean",
48 | modPermissions: {
49 | type: "array",
50 | defaultsTo: null
51 | },
52 | banned: "boolean",
53 | redToken: "string",
54 | flair: "json"
55 | }
56 | };
57 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/labels.less:
--------------------------------------------------------------------------------
1 | //
2 | // Labels
3 | // --------------------------------------------------
4 |
5 | .label {
6 | display: inline;
7 | padding: .2em .6em .3em;
8 | font-size: 75%;
9 | font-weight: bold;
10 | line-height: 1;
11 | color: @label-color;
12 | text-align: center;
13 | white-space: nowrap;
14 | vertical-align: baseline;
15 | border-radius: .25em;
16 |
17 | // Add hover effects, but only for links
18 | a& {
19 | &:hover,
20 | &:focus {
21 | color: @label-link-hover-color;
22 | text-decoration: none;
23 | cursor: pointer;
24 | }
25 | }
26 |
27 | // Empty labels collapse automatically (not available in IE8)
28 | &:empty {
29 | display: none;
30 | }
31 |
32 | // Quick fix for labels in buttons
33 | .btn & {
34 | position: relative;
35 | top: -1px;
36 | }
37 | }
38 |
39 | // Colors
40 | // Contextual variations (linked labels get darker on :hover)
41 |
42 | .label-default {
43 | .label-variant(@label-default-bg);
44 | }
45 |
46 | .label-primary {
47 | .label-variant(@label-primary-bg);
48 | }
49 |
50 | .label-success {
51 | .label-variant(@label-success-bg);
52 | }
53 |
54 | .label-info {
55 | .label-variant(@label-info-bg);
56 | }
57 |
58 | .label-warning {
59 | .label-variant(@label-warning-bg);
60 | }
61 |
62 | .label-danger {
63 | .label-variant(@label-danger-bg);
64 | }
65 |
--------------------------------------------------------------------------------
/config/log.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Built-in Log Configuration
3 | * (sails.config.log)
4 | *
5 | * Configure the log level for your app, as well as the transport
6 | * (Underneath the covers, Sails uses Winston for logging, which
7 | * allows for some pretty neat custom transports/adapters for log messages)
8 | *
9 | * For more information on the Sails logger, check out:
10 | * http://sailsjs.org/#/documentation/concepts/Logging
11 | */
12 |
13 | module.exports.log = {
14 |
15 | /***************************************************************************
16 | * *
17 | * Valid `level` configs: i.e. the minimum log level to capture with *
18 | * sails.log.*() *
19 | * *
20 | * The order of precedence for log levels from lowest to highest is: *
21 | * silly, verbose, info, debug, warn, error *
22 | * *
23 | * You may also set the level to "silent" to suppress all logs. *
24 | * *
25 | ***************************************************************************/
26 |
27 | // level: 'info'
28 |
29 | };
30 |
--------------------------------------------------------------------------------
/assets/common/regexCommon.js:
--------------------------------------------------------------------------------
1 | var ptradesFlair = "(:[a-zA-Z0-9_-]*:)*((?:SW-)?([0-9]{4}-){2}[0-9]{4})(, ((?:SW-)?([0-9]{4}-){2}[0-9]{4}))* \\|\\| ([^ ,|(]*( \\((X|Y|ΩR|αS|S|M|US|UM|LGP|LGE|SW|SH|BD|SP|PLA)(, (X|Y|ΩR|αS|S|M|US|UM|LGP|LGE|SW|SH|BD|SP|PLA))*\\))?)(, ([^ ,|(]*( \\((X|Y|ΩR|αS|S|M|US|UM|LGP|LGE|SW|SH|BD|SP|PLA)(, (X|Y|ΩR|αS|S|M|US|UM|LGP|LGE|SW|SH|BD|SP|PLA))*\\))?))*";
2 | var regex = {
3 | emoji: "(:[a-zA-Z0-9_-]*:)",
4 | tsv: "[0-3]\\d{3}|40(?:[0-8]\\d|9[0-5])",
5 | tsvBars: "(\\|\\| [0-9]{4})|(, [0-9]{4})",
6 | fc: "((?:SW-)?([0-9]{4}-){2}[0-9]{4})",
7 | console: "Switch|3DS",
8 | game: "((\\()|(,))(X|Y|ΩR|αS|S|M|US|UM|LGP|LGE|SW|SH|BD|SP|PLA)((,)|(\\)))",
9 | ign: "((\\d \\|\\|)|(\\),)) [^(|,]*( (\\()|(\\|)|(,)|$)",
10 |
11 | ptradesFlair: ptradesFlair,
12 | svexFlair: ptradesFlair + " \\|\\| ([0-9]{4}|XXXX)(, (([0-9]{4})|XXXX))*"
13 | };
14 |
15 | var single = function (reg) {
16 | return new RegExp(reg);
17 | };
18 | var global = function (reg) {
19 | return new RegExp(reg, "g");
20 | };
21 |
22 | module.exports = {
23 | emoji: global(regex.emoji),
24 | tsv: global(regex.tsvBars),
25 | fc: global(regex.fc),
26 | game: global(regex.game),
27 | ign: global(regex.ign),
28 |
29 | fcSingle: single(regex.fc),
30 | consoleSingle: single(regex.console),
31 | tsvSingle: single(regex.tsv),
32 |
33 | ptradesFlair: single(regex.ptradesFlair),
34 | svexFlair: single(regex.svexFlair)
35 | };
36 |
--------------------------------------------------------------------------------
/assets/tools/tools.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
Tools
7 |
8 |
9 |
Subreddit Greasemonkey Script
10 |
11 |
17 |
18 |
19 |
20 |
21 |
22 |
FlairHQ Dark Mode
23 |
24 |
28 |
29 |
Note, this will probably be pretty buggy, as it was mostly added just as a joke.
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/config/bootstrap.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Bootstrap
3 | * (sails.config.bootstrap)
4 | *
5 | * An asynchronous bootstrap function that runs before your Sails app gets lifted.
6 | * This gives you an opportunity to set up your data model, run jobs, or perform some special logic.
7 | *
8 | * For more information on bootstrapping your app, check out:
9 | * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.bootstrap.html
10 | */
11 |
12 | module.exports.bootstrap = function(cb) {
13 |
14 | var passport = require('passport'),
15 | initialize = passport.initialize(),
16 | session = passport.session(),
17 | http = require('http'),
18 | methods = ['login', 'logIn', 'logout', 'logOut', 'isAuthenticated', 'isUnauthenticated'];
19 |
20 | sails.removeAllListeners('router:request');
21 | sails.on('router:request', function(req, res) {
22 | initialize(req, res, function () {
23 | session(req, res, function (err) {
24 | if (err) {
25 | return sails.config[500](500, req, res);
26 | }
27 | for (var i = 0; i < methods.length; i++) {
28 | req[methods[i]] = http.IncomingMessage.prototype[methods[i]].bind(req);
29 | }
30 | sails.router.route(req, res);
31 | });
32 | });
33 | });
34 |
35 | // It's very important to trigger this callback method when you are finished
36 | // with the bootstrap! (otherwise your server will never lift, since it's waiting on the bootstrap)
37 | cb();
38 | };
39 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: CI Action
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | branches:
9 | - master
10 | schedule:
11 | - cron: '0 0 * * 0' # weekly
12 |
13 | jobs:
14 | build:
15 | runs-on: '${{ matrix.os }}'
16 | strategy:
17 | matrix:
18 | os: [ubuntu-20.04]
19 | node: [12]
20 | steps:
21 | - name: Cache NPM
22 | uses: actions/cache@v3
23 | with:
24 | path: ~/.npm
25 | key: ${{ runner.os }}-build-${{hashFiles('**/package-lock.json')}}
26 | restore-keys: |
27 | ${{ runner.os }}-build-
28 | ${{ runner.os }}-
29 | - uses: actions/checkout@v4
30 | - uses: actions/setup-node@v3
31 | with:
32 | node-version: ${{matrix.node}}
33 | - run: npm config set python `which python`
34 | - name: Before Install
35 | if: runner.os == 'Linux'
36 | run: |-
37 | export CC="gcc-9.3.0"
38 | export CXX="g++-9.3.0"
39 | export LINK="gcc-9.3.0"
40 | export LINKXX="g++-9.3.0"
41 | sudo apt install build-essential checkinstall libssl-dev
42 | - run: gcc --version
43 | - run: g++ --version
44 | - run: rm package-lock.json
45 | - run: npm cache clean --f
46 | - run: rm -rf node_modules
47 | - run: npm install -g grunt-cli
48 | - run: npm install -g eslint
49 | - run: npm install
50 | - run: npm test
--------------------------------------------------------------------------------
/api/responses/ok.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 200 (OK) Response
3 | *
4 | * Usage:
5 | * return res.ok();
6 | * return res.ok(data);
7 | * return res.ok(data, 'auth/login');
8 | *
9 | * @param {Object} data
10 | * @param {String|Object} options
11 | * - pass string to render specified view
12 | */
13 |
14 | module.exports = function sendOK (data, options) {
15 |
16 | // Get access to `req`, `res`, & `sails`
17 | var req = this.req;
18 | var res = this.res;
19 | var sails = req._sails;
20 |
21 | sails.log.silly('res.ok() :: Sending 200 ("OK") response');
22 |
23 | // Set status code
24 | res.status(200);
25 |
26 | // If appropriate, serve data as JSON(P)
27 | if (req.wantsJSON) {
28 | return res.jsonx(data);
29 | }
30 |
31 | // If second argument is a string, we take that to mean it refers to a view.
32 | // If it was omitted, use an empty object (`{}`)
33 | options = (typeof options === 'string') ? { view: options } : options || {};
34 |
35 | // If a view was provided in options, serve it.
36 | // Otherwise try to guess an appropriate view, or if that doesn't
37 | // work, just send JSON.
38 | if (options.view) {
39 | return res.view(options.view, { data: data });
40 | }
41 |
42 | // If no second argument provided, try to serve the implied view,
43 | // but fall back to sending JSON(P) if no view can be inferred.
44 | else return res.guessView({ data: data }, function couldNotGuessView () {
45 | return res.jsonx(data);
46 | });
47 |
48 | };
49 |
--------------------------------------------------------------------------------
/assets/search/main.ejs:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
Advanced Search - {{search.getSearch().name}}
9 |
10 |
11 |
12 |
15 |
16 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/config/models.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Default model configuration
3 | * (sails.config.models)
4 | *
5 | * Unless you override them, the following properties will be included
6 | * in each of your models.
7 | *
8 | * For more info on Sails models, see:
9 | * http://sailsjs.org/#/documentation/concepts/ORM
10 | */
11 |
12 | module.exports.models = {
13 |
14 | /***************************************************************************
15 | * *
16 | * Your app's default connection. i.e. the name of one of your app's *
17 | * connections (see `config/connections.js`) *
18 | * *
19 | ***************************************************************************/
20 | connection: 'mongo',
21 | adapter: 'mongo',
22 |
23 | /***************************************************************************
24 | * *
25 | * How and whether Sails will attempt to automatically rebuild the *
26 | * tables/collections/etc. in your schema. *
27 | * *
28 | * See http://sailsjs.org/#/documentation/concepts/ORM/model-settings.html *
29 | * *
30 | ***************************************************************************/
31 | migrate: 'safe'
32 |
33 | };
34 |
--------------------------------------------------------------------------------
/config/env/production.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Production environment settings
3 | *
4 | * This file can include shared settings for a production environment,
5 | * such as API keys or remote database passwords. If you're using
6 | * a version control solution for your Sails app, this file will
7 | * be committed to your repository unless you add it to your .gitignore
8 | * file. If your repository will be publicly viewable, don't add
9 | * any private information to this file!
10 | *
11 | */
12 |
13 | module.exports = {
14 |
15 | /***************************************************************************
16 | * Set the default database connection for models in the production *
17 | * environment (see config/connections.js and config/models.js ) *
18 | ***************************************************************************/
19 |
20 | // models: {
21 | // connection: 'someMysqlServer'
22 | // },
23 |
24 | /***************************************************************************
25 | * Set the port in the production environment to 80 *
26 | ***************************************************************************/
27 |
28 | // port: 80,
29 |
30 | /***************************************************************************
31 | * Set the log level in production environment to "silent" *
32 | ***************************************************************************/
33 |
34 | log: {
35 | level: 'info',
36 | timestamp: true,
37 | timestampFormat: 'YYYY-MM-DD HH:mm:ss.SSS'
38 | }
39 |
40 | };
41 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/code.less:
--------------------------------------------------------------------------------
1 | //
2 | // Code (inline and block)
3 | // --------------------------------------------------
4 |
5 |
6 | // Inline and block code styles
7 | code,
8 | kbd,
9 | pre,
10 | samp {
11 | font-family: @font-family-monospace;
12 | }
13 |
14 | // Inline code
15 | code {
16 | padding: 2px 4px;
17 | font-size: 90%;
18 | color: @code-color;
19 | background-color: @code-bg;
20 | border-radius: @border-radius-base;
21 | }
22 |
23 | // User input typically entered via keyboard
24 | kbd {
25 | padding: 2px 4px;
26 | font-size: 90%;
27 | color: @kbd-color;
28 | background-color: @kbd-bg;
29 | border-radius: @border-radius-small;
30 | box-shadow: inset 0 -1px 0 rgba(0,0,0,.25);
31 |
32 | kbd {
33 | padding: 0;
34 | font-size: 100%;
35 | box-shadow: none;
36 | }
37 | }
38 |
39 | // Blocks of code
40 | pre {
41 | display: block;
42 | padding: ((@line-height-computed - 1) / 2);
43 | margin: 0 0 (@line-height-computed / 2);
44 | font-size: (@font-size-base - 1); // 14px to 13px
45 | line-height: @line-height-base;
46 | word-break: break-all;
47 | word-wrap: break-word;
48 | color: @pre-color;
49 | background-color: @pre-bg;
50 | border: 1px solid @pre-border-color;
51 | border-radius: @border-radius-base;
52 |
53 | // Account for some code outputs that place code tags in pre tags
54 | code {
55 | padding: 0;
56 | font-size: inherit;
57 | color: inherit;
58 | white-space: pre-wrap;
59 | background-color: transparent;
60 | border-radius: 0;
61 | }
62 | }
63 |
64 | // Enable scrollable blocks of code
65 | .pre-scrollable {
66 | max-height: @pre-scrollable-max-height;
67 | overflow-y: scroll;
68 | }
69 |
--------------------------------------------------------------------------------
/assets/adminCtrl.js:
--------------------------------------------------------------------------------
1 | var shared = require('./sharedClientFunctions.js');
2 | module.exports = function ($scope, io) {
3 | shared.addRepeats($scope, io);
4 | $scope.users = [];
5 | $scope.flairAppError = "";
6 | $scope.adminok = {
7 | appFlair: {}
8 | };
9 | $scope.adminspin = {
10 | appFlair: {}
11 | };
12 |
13 | $scope.denyApp = function (id) {
14 | var url = "/flair/app/deny";
15 | $scope.flairAppError = "";
16 |
17 | io.socket.post(url, {id: id}, function (data, res) {
18 | if (res.statusCode === 200) {
19 | $scope.flairApps = data;
20 | } else if (res.statusCode === 404) {
21 | $scope.flairApps = data;
22 | $scope.flairAppError = "That app no longer exists.";
23 | } else {
24 | $scope.flairAppError = "Couldn't deny, for some reason.";
25 | console.log(data);
26 | }
27 | $scope.$apply();
28 | });
29 | };
30 |
31 | $scope.approveApp = function (id) {
32 | $scope.adminok.appFlair[id] = false;
33 | $scope.adminspin.appFlair[id] = true;
34 | $scope.flairAppError = "";
35 | var url = "/flair/app/approve";
36 |
37 | io.socket.post(url, {id: id}, function (data, res) {
38 | if (res.statusCode === 200) {
39 | $scope.adminok.appFlair[id] = true;
40 | $scope.flairApps = data;
41 | } else if (res.statusCode === 404) {
42 | $scope.flairApps = data;
43 | $scope.flairAppError = "That app no longer exists.";
44 | } else {
45 | $scope.flairAppError = "Couldn't approve, for some reason.";
46 | console.log(data);
47 | }
48 | $scope.adminspin.appFlair[id] = false;
49 | $scope.$apply();
50 | });
51 | };
52 | };
53 |
--------------------------------------------------------------------------------
/test/unit/data/flairCssClasses.json:
--------------------------------------------------------------------------------
1 | {
2 | "pokemontrades": {
3 | "pokeball,premierball": "premierball",
4 | "greatball hok,ultraball": "ultraball hok",
5 | "ovalcharm1,shinycharm": "shinycharm1",
6 | "greatball,involvement": "greatball1",
7 | "ovalcharm,involvement": "ovalcharm1",
8 | "premierball hok,involvement": "premierball1 hok",
9 | ",banned": "banned",
10 | "pokeball,banned": "pokeball banned",
11 | "pokeball1,banned": "pokeball1 banned",
12 | "ovalcharm hok,banned": "ovalcharm banned",
13 | "ovalcharm1 hok,banned": "ovalcharm1 banned",
14 | "masterball1 hok something-else,banned": "masterball1 banned",
15 | "dreamball banned,banned": "dreamball banned",
16 | "banned,banned": "banned"
17 | },
18 |
19 | "SVExchange": {
20 | ",lucky": "lucky",
21 | "lucky,egg": "egg",
22 | "eevee,togepi": "togepi",
23 | "togepi smartribbon,torchic": "torchic smartribbon",
24 | ",cuteribbon": "cuteribbon",
25 | "cuteribbon,coolribbon": "coolribbon",
26 | "manaphy smartribbon,toughribbon": "manaphy toughribbon",
27 | "eggcup,beautyribbon": "eggcup beautyribbon",
28 | "beautyribbon,eggcup": "eggcup beautyribbon",
29 | ",banned": "banned",
30 | "lucky,banned": "lucky banned",
31 | "lucky cuteribbon,banned": "lucky cuteribbon banned",
32 | "toughribbon,banned": "toughribbon banned",
33 | "banned,banned": "banned",
34 | "lucky banned,banned": "lucky banned",
35 | "lucky cuteribbon banned,banned":"lucky cuteribbon banned",
36 | "toughribbon banned,banned": "toughribbon banned",
37 | "pichu banned,toughribbon": "pichu toughribbon banned",
38 | "coolribbon banned,togepi": "togepi coolribbon banned"
39 | }
40 | }
--------------------------------------------------------------------------------
/assets/styles/bootstrap/grid.less:
--------------------------------------------------------------------------------
1 | //
2 | // Grid system
3 | // --------------------------------------------------
4 |
5 |
6 | // Container widths
7 | //
8 | // Set the container width, and override it for fixed navbars in media queries.
9 |
10 | .container {
11 | .container-fixed();
12 |
13 | @media (min-width: @screen-sm-min) {
14 | width: @container-sm;
15 | }
16 | @media (min-width: @screen-md-min) {
17 | width: @container-md;
18 | }
19 | @media (min-width: @screen-lg-min) {
20 | width: @container-lg;
21 | }
22 | }
23 |
24 |
25 | // Fluid container
26 | //
27 | // Utilizes the mixin meant for fixed width containers, but without any defined
28 | // width for fluid, full width layouts.
29 |
30 | .container-fluid {
31 | .container-fixed();
32 | }
33 |
34 |
35 | // Row
36 | //
37 | // Rows contain and clear the floats of your columns.
38 |
39 | .row {
40 | .make-row();
41 | }
42 |
43 |
44 | // Columns
45 | //
46 | // Common styles for small and large grid columns
47 |
48 | .make-grid-columns();
49 |
50 |
51 | // Extra small grid
52 | //
53 | // Columns, offsets, pushes, and pulls for extra small devices like
54 | // smartphones.
55 |
56 | .make-grid(xs);
57 |
58 |
59 | // Small grid
60 | //
61 | // Columns, offsets, pushes, and pulls for the small device range, from phones
62 | // to tablets.
63 |
64 | @media (min-width: @screen-sm-min) {
65 | .make-grid(sm);
66 | }
67 |
68 |
69 | // Medium grid
70 | //
71 | // Columns, offsets, pushes, and pulls for the desktop device range.
72 |
73 | @media (min-width: @screen-md-min) {
74 | .make-grid(md);
75 | }
76 |
77 |
78 | // Large grid
79 | //
80 | // Columns, offsets, pushes, and pulls for the large desktop device range.
81 |
82 | @media (min-width: @screen-lg-min) {
83 | .make-grid(lg);
84 | }
85 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/alerts.less:
--------------------------------------------------------------------------------
1 | //
2 | // Alerts
3 | // --------------------------------------------------
4 |
5 |
6 | // Base styles
7 | // -------------------------
8 |
9 | .alert {
10 | padding: @alert-padding;
11 | margin-bottom: @line-height-computed;
12 | border: 1px solid transparent;
13 | border-radius: @alert-border-radius;
14 |
15 | // Headings for larger alerts
16 | h4 {
17 | margin-top: 0;
18 | // Specified for the h4 to prevent conflicts of changing @headings-color
19 | color: inherit;
20 | }
21 | // Provide class for links that match alerts
22 | .alert-link {
23 | font-weight: @alert-link-font-weight;
24 | }
25 |
26 | // Improve alignment and spacing of inner content
27 | > p,
28 | > ul {
29 | margin-bottom: 0;
30 | }
31 | > p + p {
32 | margin-top: 5px;
33 | }
34 | }
35 |
36 | // Dismissible alerts
37 | //
38 | // Expand the right padding and account for the close button's positioning.
39 |
40 | .alert-dismissable, // The misspelled .alert-dismissable was deprecated in 3.2.0.
41 | .alert-dismissible {
42 | padding-right: (@alert-padding + 20);
43 |
44 | // Adjust close link position
45 | .close {
46 | position: relative;
47 | top: -2px;
48 | right: -21px;
49 | color: inherit;
50 | }
51 | }
52 |
53 | // Alternate styles
54 | //
55 | // Generate contextual modifier classes for colorizing the alert.
56 |
57 | .alert-success {
58 | .alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text);
59 | }
60 | .alert-info {
61 | .alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text);
62 | }
63 | .alert-warning {
64 | .alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text);
65 | }
66 | .alert-danger {
67 | .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text);
68 | }
69 |
--------------------------------------------------------------------------------
/test/unit/markdown/remapURLs.test.js:
--------------------------------------------------------------------------------
1 | var assert = require("chai").assert;
2 | var remapURLs = require("../../../assets/markdown/remapURLs");
3 |
4 | var markdownData = require("../data/markdownStrings.json");
5 |
6 | describe("Markdown User URLs", function () {
7 | it("Replaces " + markdownData.userUrl + " with " + markdownData.userUrlAfter, function () {
8 | var test = remapURLs(markdownData.userUrl);
9 | assert.equal(test, markdownData.userUrlAfter, "Not mapping user urls correctly.");
10 | });
11 |
12 | it("Replaces " + markdownData.userUrlWithExtra + " with " + markdownData.userUrlWithExtraAfter, function () {
13 | var test = remapURLs(markdownData.userUrlWithExtra);
14 | assert.equal(test, markdownData.userUrlWithExtraAfter, "Not mapping user urls correctly.");
15 | });
16 |
17 | it("Replaces " + markdownData.userUrlWrong + " with " + markdownData.userUrlWrongAfter, function () {
18 | var test = remapURLs(markdownData.userUrlWrong);
19 | assert.equal(test, markdownData.userUrlWrongAfter, "Not mapping user urls correctly.");
20 | });
21 | });
22 |
23 | describe("Markdown Subreddit URLs", function () {
24 | it("Replaces " + markdownData.subUrl + " with " + markdownData.subUrlAfter, function () {
25 | var test = remapURLs(markdownData.subUrl);
26 | assert.equal(test, markdownData.subUrlAfter, "Not mapping sub urls correctly.");
27 | });
28 |
29 | it("Replaces " + markdownData.subUrlWithExtra + " with " + markdownData.subUrlWithExtraAfter, function () {
30 | var test = remapURLs(markdownData.subUrlWithExtra);
31 | assert.equal(test, markdownData.subUrlWithExtraAfter, "Not mapping sub urls correctly.");
32 | });
33 |
34 | it("Replaces " + markdownData.subUrlWrong + " with " + markdownData.subUrlWrongAfter, function () {
35 | var test = remapURLs(markdownData.subUrlWrong);
36 | assert.equal(test, markdownData.subUrlWrongAfter, "Not mapping sub urls correctly.");
37 | });
38 | });
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | /**
2 | * app.js
3 | *
4 | * Use `app.js` to run your app without `sails lift`.
5 | * To start the server, run: `node app.js`.
6 | *
7 | * This is handy in situations where the sails CLI is not relevant or useful.
8 | *
9 | * For example:
10 | * => `node app.js`
11 | * => `forever start app.js`
12 | * => `node debug app.js`
13 | * => `modulus deploy`
14 | * => `heroku scale`
15 | *
16 | *
17 | * The same command-line arguments are supported, e.g.:
18 | * `node app.js --silent --port=80 --prod`
19 | */
20 |
21 | // Ensure a "sails" can be located:
22 | (function() {
23 | var sails;
24 | try {
25 | sails = require('sails');
26 | } catch (e) {
27 | console.error('To run an app using `node app.js`, you usually need to have a version of `sails` installed in the same directory as your app.');
28 | console.error('To do that, run `npm install sails`');
29 | console.error('');
30 | console.error('Alternatively, if you have sails installed globally (i.e. you did `npm install -g sails`), you can use `sails lift`.');
31 | console.error('When you run `sails lift`, your app will still use a local `./node_modules/sails` dependency if it exists,');
32 | console.error('but if it doesn\'t, the app will run with the global sails instead!');
33 | return;
34 | }
35 |
36 | // Try to get `rc` dependency
37 | var rc;
38 | try {
39 | rc = require('rc');
40 | } catch (e0) {
41 | try {
42 | rc = require('sails/node_modules/rc');
43 | } catch (e1) {
44 | console.error('Could not find dependency: `rc`.');
45 | console.error('Your `.sailsrc` file(s) will be ignored.');
46 | console.error('To resolve this, run:');
47 | console.error('npm install rc --save');
48 | rc = function () { return {}; };
49 | }
50 | }
51 |
52 | require("babel-core/register")({/* babel options */});
53 |
54 | // Start server
55 | sails.lift(rc('sails'));
56 | })();
57 |
--------------------------------------------------------------------------------
/config/session.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Session Configuration
3 | * (sails.config.session)
4 | *
5 | * Sails session integration leans heavily on the great work already done by
6 | * Express, but also unifies Socket.io with the Connect session store. It uses
7 | * Connect's cookie parser to normalize configuration differences between Express
8 | * and Socket.io and hooks into Sails' middleware interpreter to allow you to access
9 | * and auto-save to `req.session` with Socket.io the same way you would with Express.
10 | *
11 | * For more information on configuring the session, check out:
12 | * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.session.html
13 | */
14 |
15 | module.exports.session = {
16 |
17 | /***************************************************************************
18 | * *
19 | * Session secret is automatically generated when your new app is created *
20 | * Replace at your own risk in production-- you will invalidate the cookies *
21 | * of your users, forcing them to log in again. *
22 | * *
23 | ***************************************************************************/
24 | secret: '5750234438bfe009',
25 |
26 | /***************************************************************************
27 | * *
28 | * Set the session cookie expire time The maxAge is set by milliseconds, *
29 | * the example below is for 24 hours *
30 | * *
31 | ***************************************************************************/
32 |
33 | cookie: {
34 | maxAge: 30 * 24 * 60 * 60 * 1000
35 | },
36 |
37 | autoReconnect: true
38 |
39 | };
40 |
--------------------------------------------------------------------------------
/api/responses/badRequest.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 400 (Bad Request) Handler
3 | *
4 | * Usage:
5 | * return res.badRequest();
6 | * return res.badRequest(data);
7 | * return res.badRequest(data, 'some/specific/badRequest/view');
8 | *
9 | * e.g.:
10 | * ```
11 | * return res.badRequest(
12 | * 'Please choose a valid `password` (6-12 characters)',
13 | * 'trial/signup'
14 | * );
15 | * ```
16 | */
17 |
18 | module.exports = function badRequest(data, options) {
19 |
20 | // Get access to `req`, `res`, & `sails`
21 | var req = this.req;
22 | var res = this.res;
23 | var sails = req._sails;
24 |
25 | // Set status code
26 | res.status(400);
27 |
28 | // Log error to console
29 | if (data !== undefined) {
30 | sails.log.verbose('Sending 400 ("Bad Request") response: \n',data);
31 | }
32 | else sails.log.verbose('Sending 400 ("Bad Request") response');
33 |
34 | // Only include errors in response if application environment
35 | // is not set to 'production'. In production, we shouldn't
36 | // send back any identifying information about errors.
37 | if (sails.config.environment === 'production') {
38 | data = undefined;
39 | }
40 |
41 | // If the user-agent wants JSON, always respond with JSON
42 | if (req.wantsJSON) {
43 | return res.jsonx(data);
44 | }
45 |
46 | // If second argument is a string, we take that to mean it refers to a view.
47 | // If it was omitted, use an empty object (`{}`)
48 | options = (typeof options === 'string') ? { view: options } : options || {};
49 |
50 | // If a view was provided in options, serve it.
51 | // Otherwise try to guess an appropriate view, or if that doesn't
52 | // work, just send JSON.
53 | if (options.view) {
54 | return res.view(options.view, { data: data });
55 | }
56 |
57 | // If no second argument provided, try to serve the implied view,
58 | // but fall back to sending JSON(P) if no view can be inferred.
59 | else return res.guessView({ data: data }, function couldNotGuessView () {
60 | return res.jsonx(data);
61 | });
62 |
63 | };
64 |
65 |
--------------------------------------------------------------------------------
/assets/app.js:
--------------------------------------------------------------------------------
1 | var ng = require('angular');
2 | var $ = require('jquery');
3 | var _csrf = $('#app').attr('_csrf');
4 |
5 | var refCtrl = require('./refCtrl');
6 | var indexCtrl = require('./indexCtrl');
7 | var adminCtrl = require('./adminCtrl');
8 | var banCtrl = require('./banCtrl');
9 | var userCtrl = require('./userCtrl');
10 | require('./search/search.module');
11 | require('./markdown/markdown.module');
12 | require('angular-spinner');
13 | require('angular-bootstrap-npm');
14 | require('angular-mask');
15 | require('bootstrap');
16 | require('./ngReallyClick');
17 | require('./tooltip/tooltip.module');
18 | require('./numberPadding');
19 | //require('spin');
20 |
21 | var fapp = ng.module("fapp", [
22 | 'angularSpinner',
23 | 'ngReallyClickModule',
24 | 'numberPaddingModule',
25 | 'tooltipModule',
26 | 'ngMask',
27 | 'fapp.search',
28 | 'fapp.md'
29 | ]);
30 |
31 | fapp.config(['$locationProvider', function($locationProvider) {
32 | $locationProvider.hashPrefix('');
33 | }]);
34 |
35 | fapp.service('io', function () {
36 | var socket = require('socket.io-client');
37 | var io = require('sails.io.js')(socket);
38 | io.socket.post = function (url, data, callback) {
39 | data._csrf = _csrf;
40 | io.socket.request({method: 'post', url: url, params: data}, callback);
41 | };
42 | return io;
43 | });
44 |
45 | // Define controllers, and their angular dependencies
46 | fapp.controller("referenceCtrl", ['$scope', 'io', refCtrl]);
47 | fapp.controller("indexCtrl", ['$scope', 'io', indexCtrl]);
48 | fapp.controller("userCtrl", ['$scope', '$location', 'io', userCtrl]);
49 | fapp.controller("adminCtrl", ['$scope', 'io', adminCtrl]);
50 | fapp.controller("banCtrl", ['$scope', 'io', banCtrl]);
51 |
52 | // Bug fix for iOS safari
53 | $(function () {
54 | $("[data-toggle='collapse']").click(function () {
55 | // For some reason, iOS safari doesn't let collapse work on a div if it
56 | // doesn't have a click handler. The click handler doesn't need to do anything.
57 | });
58 | });
59 |
60 | ng.bootstrap(document, ['fapp'], {strictDi: true});
61 |
--------------------------------------------------------------------------------
/assets/search/header.ejs:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/print.less:
--------------------------------------------------------------------------------
1 | //
2 | // Basic print styles
3 | // --------------------------------------------------
4 | // Source: https://github.com/h5bp/html5-boilerplate/blob/master/css/main.css
5 |
6 | @media print {
7 |
8 | * {
9 | text-shadow: none !important;
10 | color: #000 !important; // Black prints faster: h5bp.com/s
11 | background: transparent !important;
12 | box-shadow: none !important;
13 | }
14 |
15 | a,
16 | a:visited {
17 | text-decoration: underline;
18 | }
19 |
20 | a[href]:after {
21 | content: " (" attr(href) ")";
22 | }
23 |
24 | abbr[title]:after {
25 | content: " (" attr(title) ")";
26 | }
27 |
28 | // Don't show links for images, or javascript/internal links
29 | a[href^="javascript:"]:after,
30 | a[href^="#"]:after {
31 | content: "";
32 | }
33 |
34 | pre,
35 | blockquote {
36 | border: 1px solid #999;
37 | page-break-inside: avoid;
38 | }
39 |
40 | thead {
41 | display: table-header-group; // h5bp.com/t
42 | }
43 |
44 | tr,
45 | img {
46 | page-break-inside: avoid;
47 | }
48 |
49 | img {
50 | max-width: 100% !important;
51 | }
52 |
53 | p,
54 | h2,
55 | h3 {
56 | orphans: 3;
57 | widows: 3;
58 | }
59 |
60 | h2,
61 | h3 {
62 | page-break-after: avoid;
63 | }
64 |
65 | // Chrome (OSX) fix for https://github.com/twbs/bootstrap/issues/11245
66 | // Once fixed, we can just straight up remove this.
67 | select {
68 | background: #fff !important;
69 | }
70 |
71 | // Bootstrap components
72 | .navbar {
73 | display: none;
74 | }
75 | .table {
76 | td,
77 | th {
78 | background-color: #fff !important;
79 | }
80 | }
81 | .btn,
82 | .dropup > .btn {
83 | > .caret {
84 | border-top-color: #000 !important;
85 | }
86 | }
87 | .label {
88 | border: 1px solid #000;
89 | }
90 |
91 | .table {
92 | border-collapse: collapse !important;
93 | }
94 | .table-bordered {
95 | th,
96 | td {
97 | border: 1px solid #ddd !important;
98 | }
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/tasks/pipeline.js:
--------------------------------------------------------------------------------
1 | /**
2 | * grunt/pipeline.js
3 | *
4 | * The order in which your css, javascript, and template files should be
5 | * compiled and linked from your views and static HTML files.
6 | *
7 | * (Note that you can take advantage of Grunt-style wildcard/glob/splat expressions
8 | * for matching multiple files.)
9 | */
10 |
11 |
12 |
13 | // CSS files to inject in order
14 | //
15 | // (if you're using LESS with the built-in default config, you'll want
16 | // to change `assets/styles/importer.less` instead.)
17 | var cssFilesToInject = [
18 | 'styles/**/*.css'
19 | ];
20 |
21 |
22 | // Client-side javascript files to inject in order
23 | // (uses Grunt-style wildcard/glob/splat expressions)
24 | var jsFilesToInject = [
25 | 'js/app.js'
26 | ];
27 | var prodJSFilesToInject = [
28 | 'min/production.min.js'
29 | ];
30 |
31 |
32 | // Client-side HTML templates are injected using the sources below
33 | // The ordering of these templates shouldn't matter.
34 | // (uses Grunt-style wildcard/glob/splat expressions)
35 | //
36 | // By default, Sails uses JST templates and precompiles them into
37 | // functions for you. If you want to use jade, handlebars, dust, etc.,
38 | // with the linker, no problem-- you'll just want to make sure the precompiled
39 | // templates get spit out to the same file. Be sure and check out `tasks/README.md`
40 | // for information on customizing and installing new tasks.
41 | var templateFilesToInject = [
42 | 'templates/**/*.html'
43 | ];
44 |
45 |
46 | // Prefix relative paths to source files so they point to the proper locations
47 | // (i.e. where the other Grunt tasks spit them out, or in some cases, where
48 | // they reside in the first place)
49 | module.exports.cssFilesToInject = cssFilesToInject.map(function (path) {
50 | return '.tmp/public/' + path;
51 | });
52 | module.exports.prodJSFilesToInject = prodJSFilesToInject.map(function (path) {
53 | return '.tmp/public/' + path;
54 | });
55 | module.exports.jsFilesToInject = jsFilesToInject.map(function (path) {
56 | return '.tmp/public/' + path;
57 | });
58 | module.exports.templateFilesToInject = templateFilesToInject.map(function (path) {
59 | return 'assets/' + path;
60 | });
61 |
--------------------------------------------------------------------------------
/config/policies.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Policy Mappings
3 | * (sails.config.policies)
4 | *
5 | * Policies are simple functions which run **before** your controllers.
6 | * You can apply one or more policies to a given controller, or protect
7 | * its actions individually.
8 | *
9 | * Any policy file (e.g. `api/policies/authenticated.js`) can be accessed
10 | * below by its filename, minus the extension, (e.g. "authenticated")
11 | *
12 | * For more information on how policies work, see:
13 | * http://sailsjs.org/#/documentation/concepts/Policies
14 | *
15 | * For more information on configuring policies, check out:
16 | * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.policies.html
17 | */
18 |
19 | const anyone = ['passport'];
20 | const user = ['passport', 'sessionAuth'];
21 | const flairMod = ['passport', 'sessionAuth', 'isFlairMod'];
22 | const postMod = ['passport', 'sessionAuth', 'isPostMod'];
23 | const admin = ['passport', 'sessionAuth', 'isAdmin'];
24 |
25 | module.exports.policies = {
26 |
27 | '*': admin,
28 |
29 | AuthController: {
30 | '*': anyone
31 | },
32 |
33 | FlairController: {
34 | '*': admin,
35 | applist: flairMod,
36 | apply: user,
37 | setText: user,
38 | denyApp: flairMod,
39 | approveApp: flairMod
40 | },
41 |
42 | HomeController: {
43 | '*': admin,
44 | index: user,
45 | reference: anyone,
46 | search: user,
47 | info: anyone,
48 | tools: anyone,
49 | applist: flairMod,
50 | discord: user
51 | },
52 |
53 | ReferenceController: {
54 | '*': admin,
55 | get: user,
56 | add: user,
57 | edit: user,
58 | deleteRef: user,
59 | comment: user,
60 | delComment: user,
61 | approve: flairMod,
62 | approveAll: flairMod,
63 | saveFlairs: flairMod,
64 | getFlairs: user
65 | },
66 |
67 | SearchController: {
68 | '*': admin,
69 | ref: user,
70 | refView: user,
71 | user: user,
72 | userView: user
73 | },
74 |
75 | UserController: {
76 | '*': admin,
77 | edit: user,
78 | mine: user,
79 | get: anyone
80 | },
81 |
82 | ModNoteController: {
83 | '*': admin,
84 | find: postMod
85 | }
86 | };
87 |
--------------------------------------------------------------------------------
/api/services/Users.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const _ = require('lodash');
4 |
5 | var removeSecretInformation = function (user) {
6 | user.redToken = undefined;
7 | user.loggedFriendCodes = undefined;
8 | if (user.apps) {
9 | user.apps.forEach(function (app) {
10 | app.claimedBy = undefined;
11 | });
12 | }
13 | return user;
14 | };
15 |
16 | exports.get = async function (requester, username) {
17 | var user = await User.findOne(username);
18 | if (!user) {
19 | throw {statusCode: 404};
20 | }
21 | var promises = [];
22 |
23 | promises.push(Game.find({user: user.name}).sort({createdAt: 'desc'}).then(function (result) {
24 | user.games = result;
25 | }));
26 |
27 | promises.push(Comment.find({user: user.name}).sort({createdAt: 'desc'}).then(function (result) {
28 | user.comments = result;
29 | }));
30 |
31 | if (Users.hasModPermission(requester, 'posts') && Users.hasModPermission(requester, 'wiki')) {
32 | promises.push(ModNote.find({refUser: user.name}).sort({createdAt: 'desc'}).then(function (result) {
33 | user.modNotes = result;
34 | }));
35 | }
36 |
37 | if (requester && requester.name === user.name) {
38 | promises.push(Flairs.getApps(user.name).then(function (result) {
39 | user.apps = result;
40 | }));
41 | }
42 |
43 | promises.push(Reference.find({user: user.name}).sort({type: 'asc', createdAt: 'desc'}).then(function (result) {
44 | result.forEach(function (ref) {
45 | if (!requester || requester.name !== user.name) {
46 | ref.privatenotes = undefined;
47 | }
48 | if (!Users.hasModPermission(requester, 'flair')) {
49 | ref.approved = undefined;
50 | ref.verified = undefined;
51 | }
52 | });
53 | user.references = result;
54 | }));
55 | await* promises;
56 | return removeSecretInformation(user);
57 | };
58 |
59 | // Returns a promise for all banned users
60 | exports.getBannedUsers = function () {
61 | return User.find({banned: true}).then(function (results) {
62 | return results.map(removeSecretInformation);
63 | });
64 | };
65 |
66 | exports.hasModPermission = (user, modPermission) => {
67 | return user && user.isMod && user.modPermissions && (_.includes(user.modPermissions, 'all') || _.includes(user.modPermissions, modPermission));
68 | };
69 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/pagination.less:
--------------------------------------------------------------------------------
1 | //
2 | // Pagination (multiple pages)
3 | // --------------------------------------------------
4 | .pagination {
5 | display: inline-block;
6 | padding-left: 0;
7 | margin: @line-height-computed 0;
8 | border-radius: @border-radius-base;
9 |
10 | > li {
11 | display: inline; // Remove list-style and block-level defaults
12 | > a,
13 | > span {
14 | position: relative;
15 | float: left; // Collapse white-space
16 | padding: @padding-base-vertical @padding-base-horizontal;
17 | line-height: @line-height-base;
18 | text-decoration: none;
19 | color: @pagination-color;
20 | background-color: @pagination-bg;
21 | border: 1px solid @pagination-border;
22 | margin-left: -1px;
23 | }
24 | &:first-child {
25 | > a,
26 | > span {
27 | margin-left: 0;
28 | .border-left-radius(@border-radius-base);
29 | }
30 | }
31 | &:last-child {
32 | > a,
33 | > span {
34 | .border-right-radius(@border-radius-base);
35 | }
36 | }
37 | }
38 |
39 | > li > a,
40 | > li > span {
41 | &:hover,
42 | &:focus {
43 | color: @pagination-hover-color;
44 | background-color: @pagination-hover-bg;
45 | border-color: @pagination-hover-border;
46 | }
47 | }
48 |
49 | > .active > a,
50 | > .active > span {
51 | &,
52 | &:hover,
53 | &:focus {
54 | z-index: 2;
55 | color: @pagination-active-color;
56 | background-color: @pagination-active-bg;
57 | border-color: @pagination-active-border;
58 | cursor: default;
59 | }
60 | }
61 |
62 | > .disabled {
63 | > span,
64 | > span:hover,
65 | > span:focus,
66 | > a,
67 | > a:hover,
68 | > a:focus {
69 | color: @pagination-disabled-color;
70 | background-color: @pagination-disabled-bg;
71 | border-color: @pagination-disabled-border;
72 | cursor: not-allowed;
73 | }
74 | }
75 | }
76 |
77 | // Sizing
78 | // --------------------------------------------------
79 |
80 | // Large
81 | .pagination-lg {
82 | .pagination-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @border-radius-large);
83 | }
84 |
85 | // Small
86 | .pagination-sm {
87 | .pagination-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @border-radius-small);
88 | }
89 |
--------------------------------------------------------------------------------
/assets/styles/fonts.less:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Open Sans';
3 | font-style: normal;
4 | font-weight: 300;
5 | src: local('Open Sans Light'), local('OpenSans-Light'), url(/fonts/OpenSans-Light.woff) format('woff');
6 | }
7 | @font-face {
8 | font-family: 'Open Sans';
9 | font-style: normal;
10 | font-weight: 400;
11 | src: local('Open Sans'), local('OpenSans'), url(/fonts/OpenSans.woff) format('woff');
12 | }
13 | @font-face {
14 | font-family: 'Open Sans';
15 | font-style: normal;
16 | font-weight: 700;
17 | src: local('Open Sans Bold'), local('OpenSans-Bold'), url(/fonts/OpenSans-Bold.woff) format('woff');
18 | }
19 | @font-face {
20 | font-family: 'Open Sans';
21 | font-style: italic;
22 | font-weight: 300;
23 | src: local('Open Sans Light Italic'), local('OpenSansLight-Italic'), url(/fonts/OpenSansLight-Italic.woff) format('woff');
24 | }
25 | @font-face {
26 | font-family: 'Open Sans';
27 | font-style: italic;
28 | font-weight: 700;
29 | src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'), url(/fonts/OpenSans-BoldItalic.woff) format('woff');
30 | }
31 |
32 |
33 | /*
34 | * Web Fonts from fontspring.com
35 | *
36 | * All OpenType features and all extended glyphs have been removed.
37 | * Fully installable fonts can be purchased at http://www.fontspring.com
38 | *
39 | * The fonts included in this stylesheet are subject to the End User License you purchased
40 | * from Fontspring. The fonts are protected under domestic and international trademark and
41 | * copyright law. You are prohibited from modifying, reverse engineering, duplicating, or
42 | * distributing this font software.
43 | *
44 | * (c) 2010-2014 Fontspring
45 | *
46 | *
47 | *
48 | *
49 | * The fonts included are copyrighted by the vendor listed below.
50 | *
51 | * Vendor: Fontfabric
52 | * License URL: http://www.fontspring.com/fflicense/fontfabric
53 | *
54 | *
55 | */
56 |
57 | @font-face {
58 | font-family: 'nexa_lightregular';
59 | src: url(/fonts/Nexa_Free_Light-webfont.eot);
60 | src: url(/fonts/Nexa_Free_Light-webfont.eot?#iefix) format('embedded-opentype'),
61 | url(/fonts/Nexa_Free_Light-webfont.woff) format('woff'),
62 | url(/fonts/Nexa_Free_Light-webfont.ttf) format('truetype'),
63 | url(/fonts/Nexa_Free_Light-webfont.svg#nexa_lightregular) format('svg');
64 | font-weight: normal;
65 | font-style: normal;
66 |
67 | }
68 |
69 |
--------------------------------------------------------------------------------
/assets/ngReallyClick.js:
--------------------------------------------------------------------------------
1 | var ng = require("angular"),
2 | _ = require('lodash');
3 |
4 | ng.module('ngReallyClickModule', ['ui.bootstrap'])
5 | .controller('ngReallyClickCtrl', ['$scope', '$modalInstance', function ($scope, $modalInstance) {
6 | $scope.ok = function () {
7 | $modalInstance.close();
8 | };
9 |
10 | $scope.cancel = function () {
11 | $modalInstance.dismiss('cancel');
12 | };
13 | }])
14 | .directive('ngReallyClick', ['$uibModal',
15 | function ($uibModal) {
16 |
17 | return {
18 | restrict: 'A',
19 | scope: {
20 | ngReallyClick: "&"
21 | },
22 | link: function (scope, element, attrs) {
23 | element.bind('click', function () {
24 | var user = attrs.ngReallyUser;
25 | var flair = attrs.ngReallyFlair;
26 | var switchInfo = attrs.ngReallySwitch;
27 | var modalHtml = "";
28 | var deleteHtml = '' +
29 | 'Are you sure you wish to delete this reference?' +
30 | '
';
31 | var denyHtml = '' +
32 | 'Are you sure you wish to deny this application?' +
33 | '
';
34 | var defaultHtml = 'Are you sure you want ' +
35 | 'to give ' + _.escape(user) + ' the ' +
36 | _.escape(flair) + ' flair?
';
37 |
38 | switch (switchInfo) {
39 | case "deleteRef":
40 | modalHtml = deleteHtml;
41 | break;
42 | case "denyApp":
43 | modalHtml = denyHtml;
44 | break;
45 | default:
46 | modalHtml = defaultHtml;
47 | break;
48 | }
49 |
50 | modalHtml += '';
54 |
55 | var modalInstance = $uibModal.open({
56 | template: modalHtml,
57 | controller: 'ngReallyClickCtrl'
58 | });
59 |
60 | modalInstance.result.then(function () {
61 | scope.ngReallyClick();
62 | }, function () {
63 | //Modal dismissed
64 | });
65 |
66 | });
67 |
68 | }
69 | };
70 | }
71 | ]);
--------------------------------------------------------------------------------
/api/controllers/HomeController.js:
--------------------------------------------------------------------------------
1 | /**
2 | * HomeController.js
3 | *
4 | * @description ::
5 | * @docs :: http://sailsjs.org/#!documentation/controllers
6 | */
7 |
8 | var _ = require("lodash");
9 |
10 | module.exports = {
11 |
12 | index: async function (req, res) {
13 | res.view({refUser: await Users.get(req.user, req.user.name)});
14 | Reddit.getBothFlairs(sails.config.reddit.adminRefreshToken, req.user.name).then(function (flairs) {
15 | if (flairs[0] || flairs[1]) {
16 | req.user.flair = {ptrades: flairs[0], svex: flairs[1]};
17 | var ptrades_fcs, svex_fcs;
18 | if (flairs[0] && flairs[0].flair_text) {
19 | ptrades_fcs = flairs[0].flair_text.match(/(\d{4}-){2}\d{4}/g);
20 | }
21 | if (flairs[1] && flairs[1].flair_text) {
22 | svex_fcs = flairs[1].flair_text.match(/(\d{4}-){2}\d{4}/g);
23 | }
24 | req.user.loggedFriendCodes = _.union(ptrades_fcs, svex_fcs, req.user.loggedFriendCodes);
25 | req.user.save(function (err) {
26 | if (err) {
27 | sails.log.error(err);
28 | }
29 | });
30 | }
31 | }, sails.log.error);
32 | },
33 |
34 | reference: async function(req, res) {
35 | try {
36 | return res.view({refUser: await Users.get(req.user, req.params.user)});
37 | } catch (err) {
38 | if (err.statusCode === 404) {
39 | return res.view('404', {data: {user: req.params.user, error: "User not found"}});
40 | }
41 | return res.serverError(err);
42 | }
43 | },
44 |
45 | banlist: async function (req, res) {
46 | try {
47 | return res.view({bannedUsers: await Users.getBannedUsers()});
48 | } catch (err) {
49 | return res.serverError(err);
50 | }
51 | },
52 |
53 | banuser: function (req, res) {
54 | res.view();
55 | },
56 |
57 | applist: function (req, res) {
58 | res.view();
59 | },
60 |
61 | info: function (req, res) {
62 | res.view();
63 | },
64 |
65 | tools: function (req, res) {
66 | res.view("../tools/tools.ejs");
67 | },
68 |
69 | version: function(req, res) {
70 | res.ok(sails.config.version);
71 | },
72 |
73 | discord: function (req, res) {
74 | let redirect_uri = encodeURIComponent(sails.config.discord.redirect_host + '/discord/callback');
75 | let authorize_uri = 'https://discordapp.com/api/oauth2/authorize?client_id='+ sails.config.discord.client_id + '&redirect_uri='+ redirect_uri + '&response_type=code&scope=identify%20guilds.join';
76 | res.redirect(authorize_uri);
77 | }
78 |
79 | };
80 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | /* global __dirname */
2 | /**
3 | * Gruntfile
4 | *
5 | * This Node script is executed when you run `grunt` or `sails lift`.
6 | * It's purpose is to load the Grunt tasks in your project's `tasks`
7 | * folder, and allow you to add and remove tasks as you see fit.
8 | * For more information on how this works, check out the `README.md`
9 | * file that was generated in your `tasks` folder.
10 | *
11 | * WARNING:
12 | * Unless you know what you're doing, you shouldn't change this file.
13 | * Check out the `tasks` directory instead.
14 | */
15 |
16 | module.exports = function (grunt) {
17 |
18 |
19 | // Load the include-all library in order to require all of our grunt
20 | // configurations and task registrations dynamically.
21 | var includeAll;
22 | try {
23 | includeAll = require('include-all');
24 | } catch (e0) {
25 | try {
26 | includeAll = require('sails/node_modules/include-all');
27 | }
28 | catch (e1) {
29 | console.error('Could not find `include-all` module.');
30 | console.error('Skipping grunt tasks...');
31 | console.error('To fix this, please run:');
32 | console.error('npm install include-all --save`');
33 | console.error();
34 |
35 | grunt.registerTask('default', []);
36 | return;
37 | }
38 | }
39 |
40 |
41 | /**
42 | * Loads Grunt configuration modules from the specified
43 | * relative path. These modules should export a function
44 | * that, when run, should either load/configure or register
45 | * a Grunt task.
46 | */
47 | function loadTasks(relPath) {
48 | return includeAll({
49 | dirname: require('path').resolve(__dirname, relPath),
50 | filter: /(.+)\.js$/
51 | }) || {};
52 | }
53 |
54 | /**
55 | * Invokes the function from a Grunt configuration module with
56 | * a single argument - the `grunt` object.
57 | */
58 | function invokeConfigFn(tasks) {
59 | for (var taskName in tasks) {
60 | if (tasks.hasOwnProperty(taskName)) {
61 | tasks[taskName](grunt);
62 | }
63 | }
64 | }
65 |
66 |
67 | // Load task functions
68 | var taskConfigurations = loadTasks('./tasks/config'),
69 | registerDefinitions = loadTasks('./tasks/register');
70 |
71 | // (ensure that a default task exists)
72 | if (!registerDefinitions.default) {
73 | registerDefinitions.default = function (grunt) {
74 | grunt.registerTask('default', []);
75 | };
76 | }
77 |
78 | // Run task functions to configure Grunt.
79 | invokeConfigFn(taskConfigurations);
80 | invokeConfigFn(registerDefinitions);
81 |
82 | };
83 |
--------------------------------------------------------------------------------
/assets/views/home/row.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{$index + 1}}.
4 |
5 |
6 |
7 |
8 |
9 | {{reference.gave}} for {{reference.got}}
10 |
11 |
12 |
13 |
14 |
15 | {{reference.description || reference.descrip}}
16 |
17 |
18 |
19 |
20 | {{reference.description || reference.descrip}}
21 | {{reference.number ? "(" + reference.number + " checked)" : ""}}
22 |
23 |
24 |
25 |
26 |
27 | {{reference.description || reference.descrip}} (Sub: {{reference.url.split("/")[4]}}{{reference.number ? ", " + reference.number + " given" : ""}})
28 |
29 |
30 |
31 |
32 |
33 | {{getRedditUser(reference.user2)}}
34 |
35 |
36 |
37 |
38 |
39 |
40 | *
41 |
42 |
43 | ✓
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
53 |
54 |
55 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/api/responses/forbidden.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 403 (Forbidden) Handler
3 | *
4 | * Usage:
5 | * return res.forbidden();
6 | * return res.forbidden(err);
7 | * return res.forbidden(err, 'some/specific/forbidden/view');
8 | *
9 | * e.g.:
10 | * ```
11 | * return res.forbidden('Access denied.');
12 | * ```
13 | */
14 |
15 | module.exports = function forbidden (data, options) {
16 |
17 | // Get access to `req`, `res`, & `sails`
18 | var req = this.req;
19 | var res = this.res;
20 | var sails = req._sails;
21 |
22 | // Set status code
23 | res.status(403);
24 |
25 | // Log error to console
26 | if (data !== undefined) {
27 | sails.log.verbose('Sending 403 ("Forbidden") response: \n',data);
28 | }
29 | else sails.log.verbose('Sending 403 ("Forbidden") response');
30 |
31 | // Only include errors in response if application environment
32 | // is not set to 'production'. In production, we shouldn't
33 | // send back any identifying information about errors.
34 | if (sails.config.environment === 'production') {
35 | data = undefined;
36 | }
37 |
38 | // If the user-agent wants JSON, always respond with JSON
39 | if (req.wantsJSON) {
40 | return res.jsonx(data);
41 | }
42 |
43 | // If second argument is a string, we take that to mean it refers to a view.
44 | // If it was omitted, use an empty object (`{}`)
45 | options = (typeof options === 'string') ? { view: options } : options || {};
46 |
47 | // If a view was provided in options, serve it.
48 | // Otherwise try to guess an appropriate view, or if that doesn't
49 | // work, just send JSON.
50 | if (options.view) {
51 | return res.view(options.view, { data: data });
52 | }
53 |
54 | // If no second argument provided, try to serve the default view,
55 | // but fall back to sending JSON(P) if any errors occur.
56 | else return res.view('403', { data: data }, function (err, html) {
57 |
58 | // If a view error occured, fall back to JSON(P).
59 | if (err) {
60 | //
61 | // Additionally:
62 | // • If the view was missing, ignore the error but provide a verbose log.
63 | if (err.code === 'E_VIEW_FAILED') {
64 | sails.log.verbose('res.forbidden() :: Could not locate view for error page (sending JSON instead). Details: ',err);
65 | }
66 | // Otherwise, if this was a more serious error, log to the console with the details.
67 | else {
68 | sails.log.warn('res.forbidden() :: When attempting to render error page view, an error occured (sending JSON instead). Details: ', err);
69 | }
70 | return res.jsonx(data);
71 | }
72 |
73 | return res.send(html);
74 | });
75 |
76 | };
77 |
78 |
--------------------------------------------------------------------------------
/test/unit/data/referenceFactory.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash');
2 | module.exports = {
3 | // Generate random references
4 | // Intended use is for unit testing, not flair grinding
5 | getRefs: function (numberOfRefs, params) {
6 | var refs = [];
7 | for (var i = 0; i < numberOfRefs; i++) {
8 | var subreddit, url, type, approved;
9 | if (params.url) {
10 | url = params.url;
11 | subreddit = params.url.indexOf('/r/pokemontrades') !== -1 ? 'pokemontrades' : 'SVExchange';
12 | } else {
13 | if (params.subreddit) {
14 | subreddit = params.subreddit;
15 | } else if (params.type === 'egg' || params.type === 'eggcheck') {
16 | subreddit = 'SVExchange';
17 | } else if (params.type && params.type !== 'giveaway') {
18 | subreddit = 'pokemontrades';
19 | } else {
20 | subreddit = _.sample(['pokemontrades', 'SVExchange']);
21 | }
22 | url = 'https://reddit.com/r/' + subreddit + '/comments/a/a/' + Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 20);
23 | }
24 | if (params.type) {
25 | type = params.type;
26 | }
27 | else if (subreddit === 'pokemontrades') {
28 | type = _.sample(['event', 'shiny', 'casual', 'bank', 'involvement', 'giveaway']);
29 | } else {
30 | type = _.sample(['egg', 'eggcheck', 'giveaway']);
31 | }
32 | approved = _.sample([true, false]);
33 | refs.push({
34 | url: url,
35 | user: params.user || Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 20),
36 | user2: params.user2 || Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 20),
37 | description: params.description || Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 20),
38 | gave: params.gave || Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 20),
39 | got: params.got || Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 20),
40 | type: type,
41 | notes: params.notes || Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 20),
42 | privatenotes: params.privatenotes || Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 20),
43 | edited: params.edited || _.sample([true, false]),
44 | number: _.random(0, Number.MAX_SAFE_INTEGER),
45 | createdAt: params.createdAt || new Date(_.random(0, 4294967295000)).toISOString(),
46 | updatedAt: params.updatedAt || new Date(_.random(0, 4294967295000)).toISOString(),
47 | approved: approved,
48 | verified: approved && _.sample([true, false])
49 | });
50 | }
51 | return refs;
52 | }
53 | };
--------------------------------------------------------------------------------
/assets/views/layout.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | FlairHQ
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
21 |
22 | <%- partial ('home/header.ejs') %>
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | It appears you don't have JavaScript enabled.
32 | If you don't have JavaScript, some parts of FlairHQ will not work for you.
33 |
34 | We are currently working hard to improve this so that you won't need JavaScript to use the site.
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | <%- body %>
46 |
47 |
48 |
49 |
54 |
55 |
56 |
57 |
58 | <% if(sails.config.environment == 'development' ){ %> <% } %>
59 | <%- partial('privacyPolicy.ejs') %>
60 |
61 |
62 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "FlairHQ",
3 | "version": "2.8.0",
4 | "description": "A project to allow easy adding of flair applications for subreddits (focusing initially on /r/pokemontrades) and easy moderation for moderators.",
5 | "scripts": {
6 | "start": "node ./node_modules/sails/bin/sails.js lift",
7 | "test": "grunt test"
8 | },
9 | "main": "app.js",
10 | "repository": {
11 | "type": "git",
12 | "url": "git://github.com/yamanickill/flairhq.git"
13 | },
14 | "author": "YaManicKill",
15 | "license": "Apache-2.0",
16 | "keywords": [],
17 | "dependencies": {
18 | "angular": "^1.5.3",
19 | "angular-bootstrap-npm": "^0.14.2",
20 | "angular-spinner": "^0.7.0",
21 | "angular-ui-mask": "~1.4.3",
22 | "async": "~1.4.2",
23 | "babel-core": "^6.3.17",
24 | "babel-eslint": "^4.1.4",
25 | "babelify": "6.3.0",
26 | "bootstrap": "^3.3.5",
27 | "chai": "^3.4.1",
28 | "connect-mongo": "^0.8.2",
29 | "crypto": "^0.0.3",
30 | "ejs": "~2.5.5",
31 | "grunt": "~0.4.5",
32 | "grunt-browserify": "^5.0.0",
33 | "grunt-contrib-clean": "~0.6.0",
34 | "grunt-contrib-concat": "~0.5.1",
35 | "grunt-contrib-copy": "~0.8.1",
36 | "grunt-contrib-cssmin": "~0.14.0",
37 | "grunt-contrib-less": "~1.0.1",
38 | "grunt-contrib-uglify": "~0.9.2",
39 | "grunt-contrib-watch": "~0.6.1",
40 | "grunt-eslint": "^17.3.1",
41 | "grunt-focus": "^0.1.1",
42 | "grunt-mocha-test": "^0.12.7",
43 | "grunt-sync": "~0.4.1",
44 | "include-all": "~0.1.3",
45 | "jquery-browserify": "^1.8.1",
46 | "lodash": "^3.10.1",
47 | "mocha": "^2.3.4",
48 | "moment": "~2.19.3",
49 | "ng-mask": "^3.0.12",
50 | "node-cache": "^3.0.0",
51 | "node-sha1": "^1.0.1",
52 | "pako": "^0.2.8",
53 | "passport": "^0.3.0",
54 | "passport-reddit": "^0.2.4",
55 | "q": "1.4.1",
56 | "rc": "~1.1.2",
57 | "regex": "^0.1.1",
58 | "request": "~2.64.0",
59 | "request-promise": "^1.0.2",
60 | "sails": "~0.11.2",
61 | "sails-disk": "~0.10.8",
62 | "sails-hook-babel": "^5.0.1",
63 | "sails-hook-schedule": "^0.2.2",
64 | "sails-hook-winston": "^1.1.0",
65 | "sails-mongo": "^0.11.4",
66 | "sails.io.js": "^0.11.7",
67 | "sha256": "^0.2.0",
68 | "snudown-js": "^1.4.0",
69 | "socket.io-browserify": "^0.9.6",
70 | "socket.io-client": "^1.3.7"
71 | },
72 | "browser": {
73 | "jquery": "jquery-browserify",
74 | "node-jquery-deparam": "./node_modules/node-jquery-deparam/node-jquery-deparam.js",
75 | "angular-mask": "./node_modules/ng-mask/dist/ngMask.js",
76 | "angular-md": "./node_modules/angular-md/dist/angular-md.js",
77 | "regex": "./assets/common/regexCommon.js"
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/api/responses/serverError.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 500 (Server Error) Response
3 | *
4 | * Usage:
5 | * return res.serverError();
6 | * return res.serverError(err);
7 | * return res.serverError(err, 'some/specific/error/view');
8 | *
9 | * NOTE:
10 | * If something throws in a policy or controller, or an internal
11 | * error is encountered, Sails will call `res.serverError()`
12 | * automatically.
13 | */
14 |
15 | module.exports = function serverError (data, options) {
16 |
17 | // Get access to `req`, `res`, & `sails`
18 | var req = this.req;
19 | var res = this.res;
20 | var sails = req._sails;
21 |
22 | // Set status code
23 | res.status(500);
24 |
25 | // Log error to console
26 | if (data !== undefined) {
27 | sails.log.error('Sending 500 ("Server Error") response: \n',data);
28 | }
29 | else sails.log.error('Sending empty 500 ("Server Error") response');
30 |
31 | // Only include errors in response if application environment
32 | // is not set to 'production'. In production, we shouldn't
33 | // send back any identifying information about errors.
34 | if (sails.config.environment === 'production') {
35 | data = undefined;
36 | }
37 |
38 | // If the user-agent wants JSON, always respond with JSON
39 | if (req.wantsJSON) {
40 | return res.jsonx(data);
41 | }
42 |
43 | // If second argument is a string, we take that to mean it refers to a view.
44 | // If it was omitted, use an empty object (`{}`)
45 | options = (typeof options === 'string') ? { view: options } : options || {};
46 |
47 | // If a view was provided in options, serve it.
48 | // Otherwise try to guess an appropriate view, or if that doesn't
49 | // work, just send JSON.
50 | if (options.view) {
51 | return res.view(options.view, { data: data });
52 | }
53 |
54 | // If no second argument provided, try to serve the default view,
55 | // but fall back to sending JSON(P) if any errors occur.
56 | else return res.view('500', { data: data }, function (err, html) {
57 |
58 | // If a view error occured, fall back to JSON(P).
59 | if (err) {
60 | //
61 | // Additionally:
62 | // • If the view was missing, ignore the error but provide a verbose log.
63 | if (err.code === 'E_VIEW_FAILED') {
64 | sails.log.verbose('res.serverError() :: Could not locate view for error page (sending JSON instead). Details: ',err);
65 | }
66 | // Otherwise, if this was a more serious error, log to the console with the details.
67 | else {
68 | sails.log.warn('res.serverError() :: When attempting to render error page view, an error occured (sending JSON instead). Details: ', err);
69 | }
70 | return res.jsonx(data);
71 | }
72 |
73 | return res.send(html);
74 | });
75 |
76 | };
77 |
78 |
--------------------------------------------------------------------------------
/assets/views/home/applist.ejs:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
11 |
12 |
42 |
43 |
44 |
{{flairAppError}}
45 |
46 |
47 |
48 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/progress-bars.less:
--------------------------------------------------------------------------------
1 | //
2 | // Progress bars
3 | // --------------------------------------------------
4 |
5 |
6 | // Bar animations
7 | // -------------------------
8 |
9 | // WebKit
10 | @-webkit-keyframes progress-bar-stripes {
11 | from { background-position: 40px 0; }
12 | to { background-position: 0 0; }
13 | }
14 |
15 | // Spec and IE10+
16 | @keyframes progress-bar-stripes {
17 | from { background-position: 40px 0; }
18 | to { background-position: 0 0; }
19 | }
20 |
21 |
22 |
23 | // Bar itself
24 | // -------------------------
25 |
26 | // Outer container
27 | .progress {
28 | overflow: hidden;
29 | height: @line-height-computed;
30 | margin-bottom: @line-height-computed;
31 | background-color: @progress-bg;
32 | border-radius: @border-radius-base;
33 | .box-shadow(inset 0 1px 2px rgba(0,0,0,.1));
34 | }
35 |
36 | // Bar of progress
37 | .progress-bar {
38 | float: left;
39 | width: 0%;
40 | height: 100%;
41 | font-size: @font-size-small;
42 | line-height: @line-height-computed;
43 | color: @progress-bar-color;
44 | text-align: center;
45 | background-color: @progress-bar-bg;
46 | .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15));
47 | .transition(width .6s ease);
48 | }
49 |
50 | // Striped bars
51 | //
52 | // `.progress-striped .progress-bar` is deprecated as of v3.2.0 in favor of the
53 | // `.progress-bar-striped` class, which you just add to an existing
54 | // `.progress-bar`.
55 | .progress-striped .progress-bar,
56 | .progress-bar-striped {
57 | #gradient > .striped();
58 | background-size: 40px 40px;
59 | }
60 |
61 | // Call animation for the active one
62 | //
63 | // `.progress.active .progress-bar` is deprecated as of v3.2.0 in favor of the
64 | // `.progress-bar.active` approach.
65 | .progress.active .progress-bar,
66 | .progress-bar.active {
67 | .animation(progress-bar-stripes 2s linear infinite);
68 | }
69 |
70 | // Account for lower percentages
71 | .progress-bar {
72 | &[aria-valuenow="1"],
73 | &[aria-valuenow="2"] {
74 | min-width: 30px;
75 | }
76 |
77 | &[aria-valuenow="0"] {
78 | color: @gray-light;
79 | min-width: 30px;
80 | background-color: transparent;
81 | background-image: none;
82 | box-shadow: none;
83 | }
84 | }
85 |
86 |
87 |
88 | // Variations
89 | // -------------------------
90 |
91 | .progress-bar-success {
92 | .progress-bar-variant(@progress-bar-success-bg);
93 | }
94 |
95 | .progress-bar-info {
96 | .progress-bar-variant(@progress-bar-info-bg);
97 | }
98 |
99 | .progress-bar-warning {
100 | .progress-bar-variant(@progress-bar-warning-bg);
101 | }
102 |
103 | .progress-bar-danger {
104 | .progress-bar-variant(@progress-bar-danger-bg);
105 | }
106 |
--------------------------------------------------------------------------------
/config/i18n.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Internationalization / Localization Settings
3 | * (sails.config.i18n)
4 | *
5 | * If your app will touch people from all over the world, i18n (or internationalization)
6 | * may be an important part of your international strategy.
7 | *
8 | *
9 | * For more informationom i18n in Sails, check out:
10 | * http://sailsjs.org/#/documentation/concepts/Internationalization
11 | *
12 | * For a complete list of i18n options, see:
13 | * https://github.com/mashpie/i18n-node#list-of-configuration-options
14 | *
15 | *
16 | */
17 |
18 | module.exports.i18n = {
19 |
20 | /***************************************************************************
21 | * *
22 | * Which locales are supported? *
23 | * *
24 | ***************************************************************************/
25 |
26 | // locales: ['en', 'es', 'fr', 'de']
27 |
28 | /****************************************************************************
29 | * *
30 | * What is the default locale for the site? Note that this setting will be *
31 | * overridden for any request that sends an "Accept-Language" header (i.e. *
32 | * most browsers), but it's still useful if you need to localize the *
33 | * response for requests made by non-browser clients (e.g. cURL). *
34 | * *
35 | ****************************************************************************/
36 |
37 | // defaultLocale: 'en',
38 |
39 | /****************************************************************************
40 | * *
41 | * Automatically add new keys to locale (translation) files when they are *
42 | * encountered during a request? *
43 | * *
44 | ****************************************************************************/
45 |
46 | // updateFiles: false,
47 |
48 | /****************************************************************************
49 | * *
50 | * Path (relative to app root) of directory to store locale (translation) *
51 | * files in. *
52 | * *
53 | ****************************************************************************/
54 |
55 | // localesDirectory: '/config/locales'
56 |
57 | };
58 |
--------------------------------------------------------------------------------
/api/responses/notFound.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 404 (Not Found) Handler
3 | *
4 | * Usage:
5 | * return res.notFound();
6 | * return res.notFound(err);
7 | * return res.notFound(err, 'some/specific/notfound/view');
8 | *
9 | * e.g.:
10 | * ```
11 | * return res.notFound();
12 | * ```
13 | *
14 | * NOTE:
15 | * If a request doesn't match any explicit routes (i.e. `config/routes.js`)
16 | * or route blueprints (i.e. "shadow routes", Sails will call `res.notFound()`
17 | * automatically.
18 | */
19 |
20 | module.exports = function notFound (data, options) {
21 |
22 | // Get access to `req`, `res`, & `sails`
23 | var req = this.req;
24 | var res = this.res;
25 | var sails = req._sails;
26 |
27 | res.locals.user = req.user;
28 |
29 | // Set status code
30 | res.status(404);
31 |
32 | // Log error to console
33 | if (data !== undefined) {
34 | sails.log.verbose('Sending 404 ("Not Found") response: \n',data);
35 | }
36 | else sails.log.verbose('Sending 404 ("Not Found") response');
37 |
38 | // Only include errors in response if application environment
39 | // is not set to 'production'. In production, we shouldn't
40 | // send back any identifying information about errors.
41 | if (sails.config.environment === 'production') {
42 | data = undefined;
43 | }
44 |
45 | // If the user-agent wants JSON, always respond with JSON
46 | if (req.wantsJSON) {
47 | return res.jsonx(data);
48 | }
49 |
50 | // If second argument is a string, we take that to mean it refers to a view.
51 | // If it was omitted, use an empty object (`{}`)
52 | options = (typeof options === 'string') ? { view: options } : options || {};
53 |
54 | // If a view was provided in options, serve it.
55 | // Otherwise try to guess an appropriate view, or if that doesn't
56 | // work, just send JSON.
57 | if (options.view) {
58 | return res.view(options.view, { data: data });
59 | }
60 |
61 | // If no second argument provided, try to serve the default view,
62 | // but fall back to sending JSON(P) if any errors occur.
63 | else return res.view('404', { data: data }, function (err, html) {
64 |
65 | // If a view error occured, fall back to JSON(P).
66 | if (err) {
67 | //
68 | // Additionally:
69 | // • If the view was missing, ignore the error but provide a verbose log.
70 | if (err.code === 'E_VIEW_FAILED') {
71 | sails.log.verbose('res.notFound() :: Could not locate view for error page (sending JSON instead). Details: ',err);
72 | }
73 | // Otherwise, if this was a more serious error, log to the console with the details.
74 | else {
75 | sails.log.warn('res.notFound() :: When attempting to render error page view, an error occured (sending JSON instead). Details: ', err);
76 | }
77 | return res.jsonx(data);
78 | }
79 |
80 | return res.send(html);
81 | });
82 |
83 | };
84 |
85 |
--------------------------------------------------------------------------------
/api/services/Usernotes.js:
--------------------------------------------------------------------------------
1 | var pako = require('pako'),
2 | sha256 = require('sha256'),
3 | moment = require('moment');
4 | var decompress = function(blob) {
5 | var inflate = new pako.Inflate({to: 'string'});
6 | inflate.push(new Buffer(blob, 'base64').toString('binary'));
7 | return JSON.parse(inflate.result);
8 | };
9 | var compress = function(notesObject) {
10 | var deflate = new pako.Deflate({to: 'string'});
11 | deflate.push(JSON.stringify(notesObject), true);
12 | return (new Buffer(deflate.result.toString(), 'binary')).toString('base64');
13 | };
14 | exports.addUsernote = async function (redToken, modname, subreddit, user, noteText, type, link_index) {
15 | let compressed_notes = await Reddit.getWikiPage(redToken, subreddit, 'usernotes');
16 | var parsed = JSON.parse(compressed_notes);
17 | var mods = parsed.constants.users;
18 | var warnings = parsed.constants.warnings;
19 | var notes = decompress(parsed.blob);
20 | if (!notes[user]) {
21 | notes[user] = {ns: []};
22 | }
23 | if (mods.indexOf(modname) == -1) {
24 | mods.push(modname);
25 | }
26 | if (warnings.indexOf(type) == -1) {
27 | warnings.push(type);
28 | }
29 | var newNote = {
30 | n: noteText,
31 | t: moment().unix(),
32 | m: mods.indexOf(modname),
33 | l: link_index,
34 | w: warnings.indexOf(type)
35 | };
36 | notes[user].ns.unshift(newNote);
37 | parsed.blob = compress(notes);
38 | await Reddit.editWikiPage(redToken, subreddit, 'usernotes', JSON.stringify(parsed), 'FlairHQ: Created note on /u/' + user);
39 | var hash = sha256(user + newNote.n + newNote.t + newNote.m + newNote.l + newNote.w);
40 | /* By default, notes on a particular user are not indexed. This makes it difficult if one wants to delete a specific note that it created,
41 | * because new notes might have been added or removed since the note in question was created.
42 | * To resolve this issue, the addUsernote function returns a hash of the note when it's added. Then a specific note can be deleted by
43 | * searching for a note that matches a particular hash. */
44 | return hash;
45 | };
46 | exports.removeUsernote = async function (redToken, username, subreddit, note_hash) {
47 | let compressed_notes = await Reddit.getWikiPage(redToken, subreddit, 'usernotes');
48 | var pageObject = JSON.parse(compressed_notes);
49 | var notes = decompress(pageObject.blob);
50 | for (var i = 0; i < notes[username].ns.length; i++) {
51 | var note = notes[username].ns[i];
52 | if (note_hash === sha256(username + note.n + note.t + note.m + note.l + note.w)) {
53 | notes[username].ns.splice(i,1);
54 | i--;
55 | }
56 | }
57 | pageObject.blob = compress(notes);
58 | var reason = 'FlairHQ: Deleted note ' + note_hash.substring(0,7) + ' on ' + username;
59 | await Reddit.editWikiPage(redToken, subreddit, 'usernotes', JSON.stringify(pageObject), reason);
60 | return;
61 | };
62 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/forms.less:
--------------------------------------------------------------------------------
1 | // Form validation states
2 | //
3 | // Used in forms.less to generate the form validation CSS for warnings, errors,
4 | // and successes.
5 |
6 | .form-control-validation(@text-color: #555; @border-color: #ccc; @background-color: #f5f5f5) {
7 | // Color the label and help text
8 | .help-block,
9 | .control-label,
10 | .radio,
11 | .checkbox,
12 | .radio-inline,
13 | .checkbox-inline {
14 | color: @text-color;
15 | }
16 | // Set the border and box shadow on specific inputs to match
17 | .form-control {
18 | border-color: @border-color;
19 | .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work
20 | &:focus {
21 | border-color: darken(@border-color, 10%);
22 | @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@border-color, 20%);
23 | .box-shadow(@shadow);
24 | }
25 | }
26 | // Set validation states also for addons
27 | .input-group-addon {
28 | color: @text-color;
29 | border-color: @border-color;
30 | background-color: @background-color;
31 | }
32 | // Optional feedback icon
33 | .form-control-feedback {
34 | color: @text-color;
35 | }
36 | }
37 |
38 |
39 | // Form control focus state
40 | //
41 | // Generate a customized focus state and for any input with the specified color,
42 | // which defaults to the `@input-border-focus` variable.
43 | //
44 | // We highly encourage you to not customize the default value, but instead use
45 | // this to tweak colors on an as-needed basis. This aesthetic change is based on
46 | // WebKit's default styles, but applicable to a wider range of browsers. Its
47 | // usability and accessibility should be taken into account with any change.
48 | //
49 | // Example usage: change the default blue border and shadow to white for better
50 | // contrast against a dark gray background.
51 | .form-control-focus(@color: @input-border-focus) {
52 | @color-rgba: rgba(red(@color), green(@color), blue(@color), .6);
53 | &:focus {
54 | border-color: @color;
55 | outline: 0;
56 | .box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}");
57 | }
58 | }
59 |
60 | // Form control sizing
61 | //
62 | // Relative text size, padding, and border-radii changes for form controls. For
63 | // horizontal sizing, wrap controls in the predefined grid classes. ``
64 | // element gets special love because it's special, and that's a fact!
65 | .input-size(@input-height; @padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {
66 | height: @input-height;
67 | padding: @padding-vertical @padding-horizontal;
68 | font-size: @font-size;
69 | line-height: @line-height;
70 | border-radius: @border-radius;
71 |
72 | select& {
73 | height: @input-height;
74 | line-height: @input-height;
75 | }
76 |
77 | textarea&,
78 | select[multiple]& {
79 | height: auto;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/test/unit/data/users.json:
--------------------------------------------------------------------------------
1 | {
2 | "regular_moderator": {
3 | "name": "not_an_aardvark",
4 | "friendCodes": [
5 | "0000-0000-0135",
6 | "0000-0000-0165"
7 | ],
8 | "updatedAt": "2015-11-22T20:32:22.767Z",
9 | "redToken": "Secret-Base64-String",
10 | "isMod": true,
11 | "intro": "Not_an_aardvark's interesting intro with lots of special characters (;\"'<<[",
12 | "loggedFriendCodes": [
13 | "0000-0000-0135",
14 | "0000-0000-0165"
15 | ],
16 | "provider": "reddit",
17 | "createdAt": "2015-10-31T03:51:43.117Z",
18 | "flair": {
19 | "ptrades": {
20 | "flair_css_class": "naa",
21 | "flair_template_id": null,
22 | "flair_text": "0000-0000-0135, 0000-0000-0165 || Naa (ΩR, X)",
23 | "flair_position": "right"
24 | },
25 | "svex": {
26 | "flair_css_class": "wobbuffet",
27 | "flair_template_id": null,
28 | "flair_text": "0000-0000-0135, 0000-0000-0165 || Naa (ΩR, X) || 2325, 4015",
29 | "flair_position": "right"
30 | }
31 | }
32 | },
33 |
34 | "greatball_user": {
35 | "name": "actually_an_aardvark",
36 | "friendCodes": [
37 | "1111-1111-1111"
38 | ],
39 | "updatedAt": "2015-11-22T20:39:49.965Z",
40 | "redToken": "Secret-Base64-String",
41 | "intro": "Actually_an_aardvark's intro!",
42 | "loggedFriendCodes": [
43 | "0000-0000-0135",
44 | "0000-0000-0000"
45 | ],
46 | "provider": "reddit",
47 | "createdAt": "2015-10-31T03:52:34.214Z",
48 | "flair": {
49 | "ptrades": {
50 | "flair_css_class": "greatball",
51 | "flair_template_id": null,
52 | "flair_text": "0000-0000-0135 || naa test (X)",
53 | "flair_position": "right"
54 | },
55 | "svex": {
56 | "flair_css_class": "lucky cuteribbon",
57 | "flair_template_id": null,
58 | "flair_text": "0000-0000-0135 || naa test (X) || XXXX",
59 | "flair_position": "right"
60 | }
61 | },
62 | "banned": false
63 | },
64 |
65 | "default_flair_user": {
66 | "name": "mr_default_flair",
67 | "friendCodes": [
68 | "1111-1111-1111"
69 | ],
70 | "updatedAt": "2015-11-22T20:39:49.965Z",
71 | "redToken": "Secret-Base64-String",
72 | "intro": "My intro",
73 | "loggedFriendCodes": [
74 | "1111-1111-1111"
75 | ],
76 | "provider": "reddit",
77 | "createdAt": "2015-10-31T03:52:34.214Z",
78 | "flair": {
79 | "ptrades": {
80 | "flair_css_class": "default",
81 | "flair_template_id": null,
82 | "flair_text": "1111-1111-1111 || mr-default (ΩR)",
83 | "flair_position": "right"
84 | },
85 | "svex": {
86 | "flair_css_class": "",
87 | "flair_template_id": null,
88 | "flair_text": "1111-1111-1111 || mr-default (ΩR) || 1234",
89 | "flair_position": "right"
90 | }
91 | },
92 | "banned": false
93 | }
94 | }
--------------------------------------------------------------------------------
/assets/styles/bootstrap/tooltip.less:
--------------------------------------------------------------------------------
1 | //
2 | // Tooltips
3 | // --------------------------------------------------
4 |
5 |
6 | // Base class
7 | .tooltip {
8 | position: absolute;
9 | z-index: @zindex-tooltip;
10 | display: block;
11 | visibility: visible;
12 | font-size: @font-size-small;
13 | line-height: 1.4;
14 | .opacity(0);
15 |
16 | &.in { .opacity(@tooltip-opacity); }
17 | &.top { margin-top: -3px; padding: @tooltip-arrow-width 0; }
18 | &.right { margin-left: 3px; padding: 0 @tooltip-arrow-width; }
19 | &.bottom { margin-top: 3px; padding: @tooltip-arrow-width 0; }
20 | &.left { margin-left: -3px; padding: 0 @tooltip-arrow-width; }
21 | }
22 |
23 | // Wrapper for the tooltip content
24 | .tooltip-inner {
25 | max-width: @tooltip-max-width;
26 | padding: 3px 8px;
27 | color: @tooltip-color;
28 | text-align: center;
29 | text-decoration: none;
30 | background-color: @tooltip-bg;
31 | border-radius: @border-radius-base;
32 | }
33 |
34 | // Arrows
35 | .tooltip-arrow {
36 | position: absolute;
37 | width: 0;
38 | height: 0;
39 | border-color: transparent;
40 | border-style: solid;
41 | }
42 | .tooltip {
43 | &.top .tooltip-arrow {
44 | bottom: 0;
45 | left: 50%;
46 | margin-left: -@tooltip-arrow-width;
47 | border-width: @tooltip-arrow-width @tooltip-arrow-width 0;
48 | border-top-color: @tooltip-arrow-color;
49 | }
50 | &.top-left .tooltip-arrow {
51 | bottom: 0;
52 | left: @tooltip-arrow-width;
53 | border-width: @tooltip-arrow-width @tooltip-arrow-width 0;
54 | border-top-color: @tooltip-arrow-color;
55 | }
56 | &.top-right .tooltip-arrow {
57 | bottom: 0;
58 | right: @tooltip-arrow-width;
59 | border-width: @tooltip-arrow-width @tooltip-arrow-width 0;
60 | border-top-color: @tooltip-arrow-color;
61 | }
62 | &.right .tooltip-arrow {
63 | top: 50%;
64 | left: 0;
65 | margin-top: -@tooltip-arrow-width;
66 | border-width: @tooltip-arrow-width @tooltip-arrow-width @tooltip-arrow-width 0;
67 | border-right-color: @tooltip-arrow-color;
68 | }
69 | &.left .tooltip-arrow {
70 | top: 50%;
71 | right: 0;
72 | margin-top: -@tooltip-arrow-width;
73 | border-width: @tooltip-arrow-width 0 @tooltip-arrow-width @tooltip-arrow-width;
74 | border-left-color: @tooltip-arrow-color;
75 | }
76 | &.bottom .tooltip-arrow {
77 | top: 0;
78 | left: 50%;
79 | margin-left: -@tooltip-arrow-width;
80 | border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;
81 | border-bottom-color: @tooltip-arrow-color;
82 | }
83 | &.bottom-left .tooltip-arrow {
84 | top: 0;
85 | left: @tooltip-arrow-width;
86 | border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;
87 | border-bottom-color: @tooltip-arrow-color;
88 | }
89 | &.bottom-right .tooltip-arrow {
90 | top: 0;
91 | right: @tooltip-arrow-width;
92 | border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;
93 | border-bottom-color: @tooltip-arrow-color;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/config/express.js:
--------------------------------------------------------------------------------
1 | var passport = require('passport'),
2 | RedditStrategy = require('passport-reddit').Strategy;
3 |
4 | var verifyHandler = function (adminToken, token, tokenSecret, profile, done) {
5 | User.findOne({id: profile.name}, function(err, user) {
6 | Reddit.getBothFlairs(adminToken, profile.name).then(function (flairs) {
7 | if (user) {
8 | if (user.banned) {
9 | return done("banned", user);
10 | }
11 | user.redToken = tokenSecret;
12 | user.flair = {ptrades: flairs[0], svex: flairs[1]};
13 | user.save(function (err) {
14 | if (err) {
15 | return done(err, user);
16 | }
17 | return done(null, user);
18 | });
19 | } else {
20 | var data = {
21 | redToken: tokenSecret,
22 | name: profile.name,
23 | flair: {ptrades: flairs[0], svex: flairs[1]}
24 | };
25 |
26 | User.create(data, function(err, user) {
27 | return done(err, user);
28 | });
29 | }
30 | }, function (error) {
31 | return done(error);
32 | });
33 | });
34 | };
35 |
36 | passport.serializeUser(function(user, done) {
37 | done(null, user.name);
38 | });
39 |
40 | passport.deserializeUser(function(name, done) {
41 | User.findOne({id: name}, function(err, user) {
42 | done(err, user);
43 | });
44 | });
45 |
46 | /**
47 | * Configure advanced options for the Express server inside of Sails.
48 | *
49 | * For more information on configuration, check out:
50 | * http://sailsjs.org/#documentation
51 | */
52 | module.exports.http = {
53 |
54 | customMiddleware: function(app) {
55 | var callWithToken = function (token, tokenSecret, profile, done) {
56 | verifyHandler(sails.config.reddit.adminRefreshToken, token, tokenSecret, profile, done);
57 | };
58 | passport.use(new RedditStrategy({
59 | clientID: sails.config.reddit.clientID,
60 | clientSecret: sails.config.reddit.clientIDSecret,
61 | callbackURL: sails.config.reddit.redirectURL,
62 | scope: 'flair,modflair,modcontributors,wikiread,wikiedit,read,modposts'
63 | }, callWithToken));
64 |
65 | app.use(passport.initialize());
66 | app.use(passport.session());
67 | }
68 |
69 | };
70 |
71 |
72 | /**
73 | * HTTP Flat-File Cache
74 | *
75 | * These settings are for Express' static middleware- the part that serves
76 | * flat-files like images, css, client-side templates, favicons, etc.
77 | *
78 | * In Sails, this affects the files in your app's `assets` directory.
79 | * By default, Sails uses your project's Gruntfile to compile/copy those
80 | * assets to `.tmp/public`, where they're accessible to Express.
81 | *
82 | * The HTTP static cache is only active in a 'production' environment,
83 | * since that's the only time Express will cache flat-files.
84 | *
85 | * For more information on configuration, check out:
86 | * http://sailsjs.org/#documentation
87 | */
88 | module.exports.cache = {
89 |
90 | // The number of seconds to cache files being served from disk
91 | // (only works in production mode)
92 | maxAge: 31557600000
93 | };
94 |
--------------------------------------------------------------------------------
/config/csrf.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Cross-Site Request Forgery Protection Settings
3 | * (sails.config.csrf)
4 | *
5 | * CSRF tokens are like a tracking chip. While a session tells the server that a user
6 | * "is who they say they are", a csrf token tells the server "you are where you say you are".
7 | *
8 | * When enabled, all non-GET requests to the Sails server must be accompanied by
9 | * a special token, identified as the '_csrf' parameter.
10 | *
11 | * This option protects your Sails app against cross-site request forgery (or CSRF) attacks.
12 | * A would-be attacker needs not only a user's session cookie, but also this timestamped,
13 | * secret CSRF token, which is refreshed/granted when the user visits a URL on your app's domain.
14 | *
15 | * This allows us to have certainty that our users' requests haven't been hijacked,
16 | * and that the requests they're making are intentional and legitimate.
17 | *
18 | * This token has a short-lived expiration timeline, and must be acquired by either:
19 | *
20 | * (a) For traditional view-driven web apps:
21 | * Fetching it from one of your views, where it may be accessed as
22 | * a local variable, e.g.:
23 | *
26 | *
27 | * or (b) For AJAX/Socket-heavy and/or single-page apps:
28 | * Sending a GET request to the `/csrfToken` route, where it will be returned
29 | * as JSON, e.g.:
30 | * { _csrf: 'ajg4JD(JGdajhLJALHDa' }
31 | *
32 | *
33 | * Enabling this option requires managing the token in your front-end app.
34 | * For traditional web apps, it's as easy as passing the data from a view into a form action.
35 | * In AJAX/Socket-heavy apps, just send a GET request to the /csrfToken route to get a valid token.
36 | *
37 | * For more information on CSRF, check out:
38 | * http://en.wikipedia.org/wiki/Cross-site_request_forgery
39 | *
40 | * For more information on this configuration file, including info on CSRF + CORS, see:
41 | * http://beta.sailsjs.org/#/documentation/reference/sails.config/sails.config.csrf.html
42 | *
43 | */
44 |
45 | /****************************************************************************
46 | * *
47 | * Enabled CSRF protection for your site? *
48 | * *
49 | ****************************************************************************/
50 |
51 | module.exports.csrf = true;
52 |
53 | /****************************************************************************
54 | * *
55 | * You may also specify more fine-grained settings for CSRF, including the *
56 | * domains which are allowed to request the CSRF token via AJAX. These *
57 | * settings override the general CORS settings in your config/cors.js file. *
58 | * *
59 | ****************************************************************************/
60 |
61 | // module.exports.csrf = {
62 | // grantTokenViaAjax: true,
63 | // origin: ''
64 | // }
65 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/mixins/grid-framework.less:
--------------------------------------------------------------------------------
1 | // Framework grid generation
2 | //
3 | // Used only by Bootstrap to generate the correct number of grid classes given
4 | // any value of `@grid-columns`.
5 |
6 | .make-grid-columns() {
7 | // Common styles for all sizes of grid columns, widths 1-12
8 | .col(@index) when (@index = 1) { // initial
9 | @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}";
10 | .col((@index + 1), @item);
11 | }
12 | .col(@index, @list) when (@index =< @grid-columns) { // general; "=<" isn't a typo
13 | @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}";
14 | .col((@index + 1), ~"@{list}, @{item}");
15 | }
16 | .col(@index, @list) when (@index > @grid-columns) { // terminal
17 | @{list} {
18 | position: relative;
19 | // Prevent columns from collapsing when empty
20 | min-height: 1px;
21 | // Inner gutter via padding
22 | padding-left: (@grid-gutter-width / 2);
23 | padding-right: (@grid-gutter-width / 2);
24 | }
25 | }
26 | .col(1); // kickstart it
27 | }
28 |
29 | .float-grid-columns(@class) {
30 | .col(@index) when (@index = 1) { // initial
31 | @item: ~".col-@{class}-@{index}";
32 | .col((@index + 1), @item);
33 | }
34 | .col(@index, @list) when (@index =< @grid-columns) { // general
35 | @item: ~".col-@{class}-@{index}";
36 | .col((@index + 1), ~"@{list}, @{item}");
37 | }
38 | .col(@index, @list) when (@index > @grid-columns) { // terminal
39 | @{list} {
40 | float: left;
41 | }
42 | }
43 | .col(1); // kickstart it
44 | }
45 |
46 | .calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) {
47 | .col-@{class}-@{index} {
48 | width: percentage((@index / @grid-columns));
49 | }
50 | }
51 | .calc-grid-column(@index, @class, @type) when (@type = push) and (@index > 0) {
52 | .col-@{class}-push-@{index} {
53 | left: percentage((@index / @grid-columns));
54 | }
55 | }
56 | .calc-grid-column(@index, @class, @type) when (@type = push) and (@index = 0) {
57 | .col-@{class}-push-0 {
58 | left: auto;
59 | }
60 | }
61 | .calc-grid-column(@index, @class, @type) when (@type = pull) and (@index > 0) {
62 | .col-@{class}-pull-@{index} {
63 | right: percentage((@index / @grid-columns));
64 | }
65 | }
66 | .calc-grid-column(@index, @class, @type) when (@type = pull) and (@index = 0) {
67 | .col-@{class}-pull-0 {
68 | right: auto;
69 | }
70 | }
71 | .calc-grid-column(@index, @class, @type) when (@type = offset) {
72 | .col-@{class}-offset-@{index} {
73 | margin-left: percentage((@index / @grid-columns));
74 | }
75 | }
76 |
77 | // Basic looping in LESS
78 | .loop-grid-columns(@index, @class, @type) when (@index >= 0) {
79 | .calc-grid-column(@index, @class, @type);
80 | // next iteration
81 | .loop-grid-columns((@index - 1), @class, @type);
82 | }
83 |
84 | // Create grid for specific class
85 | .make-grid(@class) {
86 | .float-grid-columns(@class);
87 | .loop-grid-columns(@grid-columns, @class, width);
88 | .loop-grid-columns(@grid-columns, @class, pull);
89 | .loop-grid-columns(@grid-columns, @class, push);
90 | .loop-grid-columns(@grid-columns, @class, offset);
91 | }
92 |
--------------------------------------------------------------------------------
/assets/views/home/viewreference.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 | Trade Info
15 |
16 |
17 |
18 | Traded with: /u/{{selectedRef.user2}}
19 |
20 |
25 |
Gave: {{selectedRef.gave}}
26 | Got: {{selectedRef.got}}
27 |
28 |
33 | Description: {{selectedRef.description}}
34 |
35 |
37 | Number given: {{selectedRef.number || 0}}
38 |
39 |
40 |
Link to trade
41 |
42 |
43 |
44 |
45 |
46 |
47 | Public Notes
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | Private Notes
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/tasks/README.md:
--------------------------------------------------------------------------------
1 | # About the `tasks` folder
2 |
3 | The `tasks` directory is a suite of Grunt tasks and their configurations, bundled for your convenience. The Grunt integration is mainly useful for bundling front-end assets, (like stylesheets, scripts, & markup templates) but it can also be used to run all kinds of development tasks, from browserify compilation to database migrations.
4 |
5 | If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, read on!
6 |
7 |
8 | ### How does this work?
9 |
10 | The asset pipeline bundled in Sails is a set of Grunt tasks configured with conventional defaults designed to make your project more consistent and productive.
11 |
12 | The entire front-end asset workflow in Sails is completely customizable-- while it provides some suggestions out of the box, Sails makes no pretense that it can anticipate all of the needs you'll encounter building the browser-based/front-end portion of your application. Who's to say you're even building an app for a browser?
13 |
14 |
15 |
16 | ### What tasks does Sails run automatically?
17 |
18 | Sails runs some of these tasks (the ones in the `tasks/register` folder) automatically when you run certain commands.
19 |
20 | ###### `sails lift`
21 |
22 | Runs the `default` task (`tasks/register/default.js`).
23 |
24 | ###### `sails lift --prod`
25 |
26 | Runs the `prod` task (`tasks/register/prod.js`).
27 |
28 | ###### `sails www`
29 |
30 | Runs the `build` task (`tasks/register/build.js`).
31 |
32 | ###### `sails www --prod` (production)
33 |
34 | Runs the `buildProd` task (`tasks/register/buildProd.js`).
35 |
36 |
37 | ### Can I customize this for SASS, Angular, client-side Jade templates, etc?
38 |
39 | You can modify, omit, or replace any of these Grunt tasks to fit your requirements. You can also add your own Grunt tasks- just add a `someTask.js` file in the `grunt/config` directory to configure the new task, then register it with the appropriate parent task(s) (see files in `grunt/register/*.js`).
40 |
41 |
42 | ### Do I have to use Grunt?
43 |
44 | Nope! To disable Grunt integration in Sails, just delete your Gruntfile or disable the Grunt hook.
45 |
46 |
47 | ### What if I'm not building a web frontend?
48 |
49 | That's ok! A core tenant of Sails is client-agnosticism-- it's especially designed for building APIs used by all sorts of clients; native Android/iOS/Cordova, serverside SDKs, etc.
50 |
51 | You can completely disable Grunt by following the instructions above.
52 |
53 | If you still want to use Grunt for other purposes, but don't want any of the default web front-end stuff, just delete your project's `assets` folder and remove the front-end oriented tasks from the `grunt/register` and `grunt/config` folders. You can also run `sails new myCoolApi --no-frontend` to omit the `assets` folder and front-end-oriented Grunt tasks for future projects. You can also replace your `sails-generate-frontend` module with alternative community generators, or create your own. This allows `sails new` to create the boilerplate for native iOS apps, Android apps, Cordova apps, SteroidsJS apps, etc.
54 |
55 |
--------------------------------------------------------------------------------
/assets/views/privacyPolicy.ejs:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
11 |
12 |
Reddit
13 |
FlairHQ uses Reddit to authenticate users. This is for 3 reasons:
14 |
15 | All our users are on Reddit (due to the usage of the site)
16 | One less password for users to remember
17 | We do not store any credentials for the
18 | user that can't be easily revoked
19 |
20 |
We use Reddit's authentication system, which means that we never have access to your password ,
21 | and you can revoke our access to your account at any time from the reddit settings.
22 |
Reddit Permissions
23 |
We only request permissions necessary for the site to function.
24 |
31 |
32 |
Google Analytics
33 |
FlairHQ uses Google Analytics to collect data about how users
34 | use the site. This allows us to improve the site by seeing
35 | what is confusing/unused and tailor the design best.
36 |
37 |
User-specific information
38 |
Like most web services, FlairHQ occasionally logs information
39 | sent to us by your browser, such as user-agent strings and IP
40 | addresses.
41 |
42 |
Other
43 |
44 | All other information we gather is the information you enter into the site
45 | (this includes, but is not limited to: 3DS friend codes, links to trades,
46 | in game names and any comments made). All of this information is publicly
47 | viewable across the site by anyone.
48 |
49 |
50 |
51 | FlairHQ is open-source. For specifics on what information is collected by FlairHQ,
52 | you can view the source code in its git repository .
53 |
54 |
55 |
56 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/assets/styles/bootstrap/scaffolding.less:
--------------------------------------------------------------------------------
1 | //
2 | // Scaffolding
3 | // --------------------------------------------------
4 |
5 |
6 | // Reset the box-sizing
7 | //
8 | // Heads up! This reset may cause conflicts with some third-party widgets.
9 | // For recommendations on resolving such conflicts, see
10 | // http://getbootstrap.com/getting-started/#third-box-sizing
11 | * {
12 | .box-sizing(border-box);
13 | }
14 | *:before,
15 | *:after {
16 | .box-sizing(border-box);
17 | }
18 |
19 |
20 | // Body reset
21 |
22 | html {
23 | font-size: 10px;
24 | -webkit-tap-highlight-color: rgba(0,0,0,0);
25 | }
26 |
27 | body {
28 | font-family: @font-family-base;
29 | font-size: @font-size-base;
30 | line-height: @line-height-base;
31 | color: @text-color;
32 | background-color: @body-bg;
33 | }
34 |
35 | // Reset fonts for relevant elements
36 | input,
37 | button,
38 | select,
39 | textarea {
40 | font-family: inherit;
41 | font-size: inherit;
42 | line-height: inherit;
43 | }
44 |
45 |
46 | // Links
47 |
48 | a {
49 | color: @link-color;
50 | text-decoration: none;
51 |
52 | &:hover,
53 | &:focus {
54 | color: @link-hover-color;
55 | text-decoration: underline;
56 | }
57 |
58 | &:focus {
59 | .tab-focus();
60 | }
61 | }
62 |
63 |
64 | // Figures
65 | //
66 | // We reset this here because previously Normalize had no `figure` margins. This
67 | // ensures we don't break anyone's use of the element.
68 |
69 | figure {
70 | margin: 0;
71 | }
72 |
73 |
74 | // Images
75 |
76 | img {
77 | vertical-align: middle;
78 | }
79 |
80 | // Responsive images (ensure images don't scale beyond their parents)
81 | .img-responsive {
82 | .img-responsive();
83 | }
84 |
85 | // Rounded corners
86 | .img-rounded {
87 | border-radius: @border-radius-large;
88 | }
89 |
90 | // Image thumbnails
91 | //
92 | // Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`.
93 | .img-thumbnail {
94 | padding: @thumbnail-padding;
95 | line-height: @line-height-base;
96 | background-color: @thumbnail-bg;
97 | border: 1px solid @thumbnail-border;
98 | border-radius: @thumbnail-border-radius;
99 | .transition(all .2s ease-in-out);
100 |
101 | // Keep them at most 100% wide
102 | .img-responsive(inline-block);
103 | }
104 |
105 | // Perfect circle
106 | .img-circle {
107 | border-radius: 50%; // set radius in percents
108 | }
109 |
110 |
111 | // Horizontal rules
112 |
113 | hr {
114 | margin-top: @line-height-computed;
115 | margin-bottom: @line-height-computed;
116 | border: 0;
117 | border-top: 1px solid @hr-border;
118 | }
119 |
120 |
121 | // Only display content to screen readers
122 | //
123 | // See: http://a11yproject.com/posts/how-to-hide-content/
124 |
125 | .sr-only {
126 | position: absolute;
127 | width: 1px;
128 | height: 1px;
129 | margin: -1px;
130 | padding: 0;
131 | overflow: hidden;
132 | clip: rect(0,0,0,0);
133 | border: 0;
134 | }
135 |
136 | // Use in conjunction with .sr-only to only display content when it's focused.
137 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1
138 | // Credit: HTML5 Boilerplate
139 |
140 | .sr-only-focusable {
141 | &:active,
142 | &:focus {
143 | position: static;
144 | width: auto;
145 | height: auto;
146 | margin: 0;
147 | overflow: visible;
148 | clip: auto;
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/assets/views/home/header.ejs:
--------------------------------------------------------------------------------
1 | <%- partial('editProfile.ejs') %>
2 | <%- partial('flairMod.ejs') %>
3 | <%- partial('flairApply.ejs') %>
4 | <%- partial('flairText.ejs') %>
5 | <%- partial('applist.ejs') %>
6 |
7 |
8 |
9 |
20 |
21 |
22 | <%- partial('../../search/header.ejs') %>
23 |
24 | <% if (typeof user !== 'undefined') {%>
25 |
64 | <% } else {%>
65 |
74 | <% }%>
75 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/config/globals.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Global Variable Configuration
3 | * (sails.config.globals)
4 | *
5 | * Configure which global variables which will be exposed
6 | * automatically by Sails.
7 | *
8 | * For more information on configuration, check out:
9 | * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.globals.html
10 | */
11 | module.exports.globals = {
12 |
13 | /****************************************************************************
14 | * *
15 | * Expose the lodash installed in Sails core as a global variable. If this *
16 | * is disabled, like any other node module you can always run npm install *
17 | * lodash --save, then var _ = require('lodash') at the top of any file. *
18 | * *
19 | ****************************************************************************/
20 |
21 | // _: true,
22 |
23 | /****************************************************************************
24 | * *
25 | * Expose the async installed in Sails core as a global variable. If this is *
26 | * disabled, like any other node module you can always run npm install async *
27 | * --save, then var async = require('async') at the top of any file. *
28 | * *
29 | ****************************************************************************/
30 |
31 | // async: true,
32 |
33 | /****************************************************************************
34 | * *
35 | * Expose the sails instance representing your app. If this is disabled, you *
36 | * can still get access via req._sails. *
37 | * *
38 | ****************************************************************************/
39 |
40 | // sails: true,
41 |
42 | /****************************************************************************
43 | * *
44 | * Expose each of your app's services as global variables (using their *
45 | * "globalId"). E.g. a service defined in api/models/NaturalLanguage.js *
46 | * would have a globalId of NaturalLanguage by default. If this is disabled, *
47 | * you can still access your services via sails.services.* *
48 | * *
49 | ****************************************************************************/
50 |
51 | // services: true,
52 |
53 | /****************************************************************************
54 | * *
55 | * Expose each of your app's models as global variables (using their *
56 | * "globalId"). E.g. a model defined in api/models/User.js would have a *
57 | * globalId of User by default. If this is disabled, you can still access *
58 | * your models via sails.models.*. *
59 | * *
60 | ****************************************************************************/
61 |
62 | // models: true
63 | };
64 |
--------------------------------------------------------------------------------
/assets/views/home/banuser.ejs:
--------------------------------------------------------------------------------
1 |
67 |
--------------------------------------------------------------------------------
/assets/styles/spinners.less:
--------------------------------------------------------------------------------
1 |
2 | //////////////////////////////////
3 | // ### Small spinner button
4 | /////////////////////////////////
5 |
6 | .spinner {
7 | display: inline-block;
8 | opacity: 0;
9 | max-width: 0;
10 |
11 | -webkit-transition: opacity 0.25s, max-width 0.45s;
12 | -moz-transition: opacity 0.25s, max-width 0.45s;
13 | -o-transition: opacity 0.25s, max-width 0.45s;
14 | transition: opacity 0.25s, max-width 0.45s;
15 | }
16 |
17 | .has-spinner.active {
18 | cursor:progress;
19 | }
20 |
21 | .has-spinner.active .spinner {
22 | opacity: 1;
23 | max-width: 50px;
24 | }
25 |
26 | //////////////////////////////////
27 | // ### Pokeball spinner
28 | /////////////////////////////////
29 |
30 | .bigspinner {
31 | margin: 50px auto;
32 | width: 200px;
33 | height: 200px;
34 | position: relative;
35 | animation: catchBall ease 1.5s infinite !important;
36 | -webkit-animation: catchBall ease 2.7s infinite !important;
37 | background: url("/images/GreyBall.png");
38 | background-size: 100%;
39 | }
40 |
41 | @keyframes catchBall{
42 | 0% {transform: rotate(0deg); transform-origin: center bottom 0;}
43 | 4% {transform: rotate(25deg); transform-origin: center bottom 0;}
44 | 8% {transform: rotate(0deg); transform-origin: center bottom 0;}
45 | 12% {transform: rotate(-25deg); transform-origin: center bottom 0;}
46 | 16% {transform: rotate(0deg); transform-origin: center bottom 0;}
47 | 40% {transform: rotate(0deg); transform-origin: center bottom 0;}
48 | 44% {transform: rotate(25deg); transform-origin: center bottom 0;}
49 | 48% {transform: rotate(0deg); transform-origin: center bottom 0;}
50 | 52% {transform: rotate(-25deg); transform-origin: center bottom 0;}
51 | 56% {transform: rotate(0deg); transform-origin: center bottom 0;}
52 | 70% {transform: rotate(0deg); transform-origin: center bottom 0;}
53 | 74% {transform: rotate(25deg); transform-origin: center bottom 0;}
54 | 78% {transform: rotate(0deg); transform-origin: center bottom 0;}
55 | 82% {transform: rotate(-25deg); transform-origin: center bottom 0;}
56 | 86% {transform: rotate(0deg); transform-origin: center bottom 0;}
57 | 100% {transform: rotate(0deg); transform-origin: center bottom 0;}
58 | }
59 |
60 | @-webkit-keyframes catchBall{
61 | 0% {-webkit-transform: rotate(0deg); -webkit-transform-origin: center bottom 0; }
62 | 4% {-webkit-transform: rotate(25deg); -webkit-transform-origin: center bottom 0;}
63 | 8% {-webkit-transform: rotate(0deg); -webkit-transform-origin: center bottom 0;}
64 | 12% {-webkit-transform: rotate(-25deg); -webkit-transform-origin: center bottom 0;}
65 | 16% {-webkit-transform: rotate(0deg); -webkit-transform-origin: center bottom 0;}
66 | 40% {-webkit-transform: rotate(0deg); -webkit-transform-origin: center bottom 0;}
67 | 44% {-webkit-transform: rotate(25deg); -webkit-transform-origin: center bottom 0;}
68 | 48% {-webkit-transform: rotate(0deg); -webkit-transform-origin: center bottom 0;}
69 | 52% {-webkit-transform: rotate(-25deg); -webkit-transform-origin: center bottom 0;}
70 | 56% {-webkit-transform: rotate(0deg); -webkit-transform-origin: center bottom 0;}
71 | 70% {-webkit-transform: rotate(0deg); -webkit-transform-origin: center bottom 0;}
72 | 74% {-webkit-transform: rotate(25deg); -webkit-transform-origin: center bottom 0;}
73 | 78% {-webkit-transform: rotate(0deg); -webkit-transform-origin: center bottom 0;}
74 | 82% {-webkit-transform: rotate(-25deg); -webkit-transform-origin: center bottom 0;}
75 | 86% {-webkit-transform: rotate(0deg); -webkit-transform-origin: center bottom 0;}
76 | 100% {-webkit-transform: rotate(0deg); -webkit-transform-origin: center bottom 0;}
77 |
78 | }
79 |
--------------------------------------------------------------------------------