├── public ├── sass │ ├── mdb │ │ ├── _custom.scss │ │ └── free │ │ │ ├── _breadcrumb.scss │ │ │ ├── _deprecated.scss │ │ │ ├── _list-group.scss │ │ │ ├── _dropdowns.scss │ │ │ ├── _msc.scss │ │ │ ├── _jumbotron.scss │ │ │ ├── _badge.scss │ │ │ ├── _collapse.scss │ │ │ ├── _progress.scss │ │ │ ├── data │ │ │ ├── _prefixer.scss │ │ │ ├── _functions.scss │ │ │ ├── _mixins.scss │ │ │ ├── _variables.scss │ │ │ └── _colors.scss │ │ │ ├── _tables.scss │ │ │ ├── _cards-basic.scss │ │ │ ├── _carousel-basic.scss │ │ │ ├── _footer.scss │ │ │ ├── _pagination.scss │ │ │ ├── _global.scss │ │ │ ├── _masks.scss │ │ │ ├── _buttons.scss │ │ │ ├── _typography.scss │ │ │ ├── _helpers.scss │ │ │ ├── _waves.scss │ │ │ ├── _navbar.scss │ │ │ ├── _forms-basic.scss │ │ │ └── _modals.scss │ └── mdb.scss ├── img │ ├── campo.png │ ├── logo.png │ ├── mock.png │ ├── escudo.png │ ├── overlays │ │ ├── 01.png │ │ ├── 02.png │ │ ├── 03.png │ │ ├── 04.png │ │ ├── 05.png │ │ ├── 06.png │ │ ├── 07.png │ │ ├── 08.png │ │ └── 09.png │ ├── lightbox │ │ ├── preloader.gif │ │ ├── default-skin.png │ │ └── default-skin.svg │ └── svg │ │ ├── arrow_left.svg │ │ └── arrow_right.svg ├── font │ └── roboto │ │ ├── Roboto-Bold.eot │ │ ├── Roboto-Bold.ttf │ │ ├── Roboto-Bold.woff │ │ ├── Roboto-Light.eot │ │ ├── Roboto-Light.ttf │ │ ├── Roboto-Thin.eot │ │ ├── Roboto-Thin.ttf │ │ ├── Roboto-Thin.woff │ │ ├── Roboto-Bold.woff2 │ │ ├── Roboto-Light.woff │ │ ├── Roboto-Light.woff2 │ │ ├── Roboto-Medium.eot │ │ ├── Roboto-Medium.ttf │ │ ├── Roboto-Medium.woff │ │ ├── Roboto-Regular.eot │ │ ├── Roboto-Regular.ttf │ │ ├── Roboto-Thin.woff2 │ │ ├── Roboto-Medium.woff2 │ │ ├── Roboto-Regular.woff │ │ └── Roboto-Regular.woff2 ├── css │ ├── futiba-interno.css │ ├── futiba.css │ └── style.css └── js │ └── popper.min.js ├── .gitattributes ├── support_files ├── db_scheema │ ├── futibaclub.mwb │ └── futibaclub.sql ├── screenshot │ └── futibaclub.webp └── templates │ ├── grupos.html │ └── home.html ├── npm ├── config.js ├── setup.js └── encryptedRedisClient.js ├── utils ├── crypto.js ├── mailer.js └── safe.js ├── middleware.js ├── .gitignore ├── views ├── _partials │ ├── page │ │ ├── header.ejs │ │ └── footer.ejs │ ├── painel │ │ ├── header.ejs │ │ └── footer.ejs │ ├── navbar.ejs │ └── menu-items.ejs ├── account │ ├── sign-in.ejs │ ├── profile.ejs │ └── new.ejs ├── groups.ejs ├── admin │ └── games.ejs ├── ranking.ejs ├── home.ejs └── group.ejs ├── docker-compose.yml.example ├── .env.example ├── package.json ├── ranking.js ├── README.md ├── index.js ├── admin.js ├── account.js └── groups.js /public/sass/mdb/_custom.scss: -------------------------------------------------------------------------------- 1 | // Your custom style -------------------------------------------------------------------------------- /public/sass/mdb/free/_breadcrumb.scss: -------------------------------------------------------------------------------- 1 | // Breadcrumbs 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * linguist-vendored 2 | *.js linguist-vendored=false 3 | -------------------------------------------------------------------------------- /public/img/campo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/img/campo.png -------------------------------------------------------------------------------- /public/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/img/logo.png -------------------------------------------------------------------------------- /public/img/mock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/img/mock.png -------------------------------------------------------------------------------- /public/img/escudo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/img/escudo.png -------------------------------------------------------------------------------- /public/img/overlays/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/img/overlays/01.png -------------------------------------------------------------------------------- /public/img/overlays/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/img/overlays/02.png -------------------------------------------------------------------------------- /public/img/overlays/03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/img/overlays/03.png -------------------------------------------------------------------------------- /public/img/overlays/04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/img/overlays/04.png -------------------------------------------------------------------------------- /public/img/overlays/05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/img/overlays/05.png -------------------------------------------------------------------------------- /public/img/overlays/06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/img/overlays/06.png -------------------------------------------------------------------------------- /public/img/overlays/07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/img/overlays/07.png -------------------------------------------------------------------------------- /public/img/overlays/08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/img/overlays/08.png -------------------------------------------------------------------------------- /public/img/overlays/09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/img/overlays/09.png -------------------------------------------------------------------------------- /public/font/roboto/Roboto-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/font/roboto/Roboto-Bold.eot -------------------------------------------------------------------------------- /public/font/roboto/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/font/roboto/Roboto-Bold.ttf -------------------------------------------------------------------------------- /public/font/roboto/Roboto-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/font/roboto/Roboto-Bold.woff -------------------------------------------------------------------------------- /public/font/roboto/Roboto-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/font/roboto/Roboto-Light.eot -------------------------------------------------------------------------------- /public/font/roboto/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/font/roboto/Roboto-Light.ttf -------------------------------------------------------------------------------- /public/font/roboto/Roboto-Thin.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/font/roboto/Roboto-Thin.eot -------------------------------------------------------------------------------- /public/font/roboto/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/font/roboto/Roboto-Thin.ttf -------------------------------------------------------------------------------- /public/font/roboto/Roboto-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/font/roboto/Roboto-Thin.woff -------------------------------------------------------------------------------- /public/img/lightbox/preloader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/img/lightbox/preloader.gif -------------------------------------------------------------------------------- /public/font/roboto/Roboto-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/font/roboto/Roboto-Bold.woff2 -------------------------------------------------------------------------------- /public/font/roboto/Roboto-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/font/roboto/Roboto-Light.woff -------------------------------------------------------------------------------- /public/font/roboto/Roboto-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/font/roboto/Roboto-Light.woff2 -------------------------------------------------------------------------------- /public/font/roboto/Roboto-Medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/font/roboto/Roboto-Medium.eot -------------------------------------------------------------------------------- /public/font/roboto/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/font/roboto/Roboto-Medium.ttf -------------------------------------------------------------------------------- /public/font/roboto/Roboto-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/font/roboto/Roboto-Medium.woff -------------------------------------------------------------------------------- /public/font/roboto/Roboto-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/font/roboto/Roboto-Regular.eot -------------------------------------------------------------------------------- /public/font/roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/font/roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /public/font/roboto/Roboto-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/font/roboto/Roboto-Thin.woff2 -------------------------------------------------------------------------------- /public/img/lightbox/default-skin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/img/lightbox/default-skin.png -------------------------------------------------------------------------------- /public/font/roboto/Roboto-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/font/roboto/Roboto-Medium.woff2 -------------------------------------------------------------------------------- /public/font/roboto/Roboto-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/font/roboto/Roboto-Regular.woff -------------------------------------------------------------------------------- /public/font/roboto/Roboto-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/public/font/roboto/Roboto-Regular.woff2 -------------------------------------------------------------------------------- /support_files/db_scheema/futibaclub.mwb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/support_files/db_scheema/futibaclub.mwb -------------------------------------------------------------------------------- /support_files/screenshot/futibaclub.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vs0uz4/futibaclub/HEAD/support_files/screenshot/futibaclub.webp -------------------------------------------------------------------------------- /public/sass/mdb/free/_deprecated.scss: -------------------------------------------------------------------------------- 1 | // Buttons 2 | // .btn-mdb was replaced by .btn-mdb-color 3 | 4 | @include make-button('mdb', map-get($mdb-color, base)); -------------------------------------------------------------------------------- /public/sass/mdb/free/_list-group.scss: -------------------------------------------------------------------------------- 1 | .media .media-left { 2 | padding: 0 10px 10px 0; 3 | img { 4 | @extend .z-depth-1-half; 5 | } 6 | } 7 | 8 | .list-group a:hover { 9 | transition: 0.5s; 10 | } -------------------------------------------------------------------------------- /public/img/svg/arrow_left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/img/svg/arrow_right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /npm/config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('dotenv').config() 4 | 5 | const config = { 6 | host: process.env.REDIS_HOST || 'localhost', 7 | port: process.env.REDIS_PORT || 6379, 8 | password: process.env.REDIS_PASSWORD || 'secret' 9 | } 10 | 11 | module.exports = config 12 | -------------------------------------------------------------------------------- /public/sass/mdb/free/_dropdowns.scss: -------------------------------------------------------------------------------- 1 | // Dropdowns free 2 | .dropdown { 3 | .dropdown-menu { 4 | .dropdown-item { 5 | &:hover, 6 | &:active, 7 | &:focus { 8 | color: #1d1e1f; 9 | background-color: #f7f7f9; 10 | } 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /utils/crypto.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const bcrypt = require("bcrypt") 4 | 5 | const saltRounds = process.env.SALT_ROUNDS || 10 6 | const salt = bcrypt.genSaltSync(saltRounds) 7 | 8 | const crypto = (password) => { 9 | return bcrypt.hashSync(password, salt); 10 | } 11 | 12 | module.exports = crypto 13 | -------------------------------------------------------------------------------- /public/sass/mdb/free/_msc.scss: -------------------------------------------------------------------------------- 1 | // Miscellaneous free 2 | 3 | // White border 4 | .table-inverse { 5 | th, 6 | td, 7 | thead th { 8 | border-color: $table-inverse-color-border; 9 | } 10 | } 11 | 12 | // Edge Headers 13 | .edge-header { 14 | display:block; 15 | height:278px; 16 | background-color:#ccc; 17 | } 18 | 19 | .free-bird { 20 | margin-top:-100px; 21 | } -------------------------------------------------------------------------------- /public/sass/mdb/free/_jumbotron.scss: -------------------------------------------------------------------------------- 1 | // Jumbtotron 2 | .jumbotron { 3 | background-color: $white; 4 | padding: 2rem; 5 | &.m-1, 6 | &.m-2, 7 | &.m-3 { 8 | z-index: 50; 9 | position: relative; 10 | margin-left: 3%; 11 | margin-right: 3%; 12 | } 13 | &.m-1 { 14 | margin-top: -20px; 15 | } 16 | &.m-2 { 17 | margin-top: -30px; 18 | } 19 | &.m-3 { 20 | margin-top: -40px; 21 | } 22 | } -------------------------------------------------------------------------------- /public/sass/mdb/free/_badge.scss: -------------------------------------------------------------------------------- 1 | //Badges 2 | .badge { 3 | color: #fff; 4 | } 5 | 6 | @each $name, $color in $material-colors { 7 | .badge-#{$name} { 8 | background-color: $color; 9 | } 10 | } 11 | 12 | .badge-pill { 13 | padding-right: $badge-pill-padding-x; 14 | padding-left: $badge-pill-padding-x; 15 | @include border-radius($badge-pill-border-radius); 16 | } 17 | 18 | .badge-warning { 19 | color: #FFF !important; 20 | } -------------------------------------------------------------------------------- /middleware.js: -------------------------------------------------------------------------------- 1 | module.exports = (req, res, next) => { 2 | if (req.url.includes('/admin') && (!req.session.user || req.session.user.role === 'user')) { 3 | res.locals.url = req.url 4 | res.redirect('/') 5 | } else if (req.url.includes('/groups') && (!req.session.user)) { 6 | res.locals.url = req.url 7 | res.redirect('/sign-in') 8 | } else if (req.session.user) { 9 | res.locals.url = req.url 10 | res.locals.user = req.session.user 11 | next() 12 | } else { 13 | res.locals.url = req.url 14 | next() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /npm/setup.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const redis = require('redis') 4 | const config = require('./config') 5 | const Safe = require('../utils/safe') 6 | const EncryptedRedisClient = require('./encryptedRedisClient') 7 | let key = process.env.ENCRYPTED_KEY 8 | let iv = process.env.ENCRYPTED_IV 9 | 10 | const client = redis.createClient(config) 11 | 12 | client.on('connect', () => { 13 | console.log('Redis Client Connected!') 14 | }) 15 | 16 | client.on('error', (err) => { 17 | console.log('Something went wrong ' + err) 18 | }) 19 | 20 | const encClient = new EncryptedRedisClient(client, new Safe(this.key, this.iv)) 21 | 22 | module.exports = encClient 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editors and IDEs 9 | .vscode/ 10 | .idea/ 11 | 12 | # Database scheema backup file 13 | support_files/db_scheema/*.bak 14 | 15 | # Dependency directories 16 | node_modules/ 17 | 18 | # Others directories 19 | mysql/ 20 | 21 | # Optional npm cache directory 22 | .npm 23 | 24 | # Optional eslint cache 25 | .eslintcache 26 | 27 | # Optional REPL history 28 | .node_repl_history 29 | 30 | # Yarn Integrity file 31 | .yarn-integrity 32 | 33 | # dotenv environment variables file 34 | .env 35 | 36 | # docker-compose orchestration file 37 | docker-compose.yml 38 | 39 | # anotations 40 | anotations.txt -------------------------------------------------------------------------------- /views/_partials/page/header.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Futiba Club - Fullstack Academy - DevPleno 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /views/_partials/painel/header.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Futiba Club - Fullstack Academy - DevPleno 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /views/_partials/page/footer.ejs: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /views/_partials/painel/footer.ejs: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /views/_partials/navbar.ejs: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /public/sass/mdb/free/_collapse.scss: -------------------------------------------------------------------------------- 1 | // ACCORDION 2 | .accordion { 3 | .card { 4 | border-bottom: 1px solid color('grey', 'lighten-3'); 5 | box-shadow: none; 6 | .card-header { 7 | padding: 1rem 1.5rem; 8 | background: transparent; 9 | border-bottom: 0; 10 | a:not(.collapsed) { 11 | .rotate-icon { 12 | transform: rotate(180deg); 13 | } 14 | } 15 | } 16 | .fa-angle-down { 17 | float: right; 18 | } 19 | .card-block { 20 | padding-top: .25rem; 21 | } 22 | .card-body { 23 | line-height: 1.7; 24 | color: #626262; 25 | font-size: 0.9rem; 26 | font-weight: 300; 27 | } 28 | h5 { 29 | font-size: 1.1rem; 30 | font-weight: 400; 31 | } 32 | } 33 | } 34 | 35 | // Collapsible body 36 | .collapsible-body { 37 | display: none; 38 | } 39 | -------------------------------------------------------------------------------- /docker-compose.yml.example: -------------------------------------------------------------------------------- 1 | version: '2.1' 2 | 3 | volumes: 4 | example-mysql-data: 5 | driver: local 6 | 7 | example-redis-data: 8 | driver: local 9 | 10 | services: 11 | # MySQL (5.7) 12 | mysql: 13 | image: mysql:5.7 14 | container_name: example_mysql_container_name 15 | volumes: 16 | - example-mysql-data:/var/lib/mysql 17 | ports: 18 | - '${DB_PORT}:3306' 19 | environment: 20 | - MYSQL_ROOT_PASSWORD=${DB_PASSWORD} 21 | - MYSQL_DATABASE=${DB_DATABASE} 22 | - MYSQL_USER=${DB_USERNAME} 23 | - MYSQL_PASSWORD=${DB_PASSWORD} 24 | 25 | # Redis (4.0) 26 | cache: 27 | image: redis:4.0 28 | container_name: example_cache_container_name 29 | command: --appendonly yes --requirepass ${REDIS_PASSWORD} 30 | volumes: 31 | - example-redis-data:/data 32 | ports: 33 | - "${REDIS_PORT}:6379" 34 | -------------------------------------------------------------------------------- /public/sass/mdb/free/_progress.scss: -------------------------------------------------------------------------------- 1 | // Progress 2 | .progress { 3 | box-shadow: none; 4 | position: relative; 5 | display: flex; 6 | width: 100%; 7 | height: $progress-bar-height; 8 | overflow: hidden; 9 | margin-bottom: $progress-bar-margin-y; 10 | background-color: $progress-bar-bg-color; 11 | .progress-bar { 12 | border-radius: 0; 13 | height: $progress-bar-height; 14 | background-color: $primary-color-dark; 15 | } 16 | &.progress-default { 17 | height: $progress-height; 18 | .progress-bar { 19 | height: $progress-height; 20 | } 21 | } 22 | &.progress-wider { 23 | height: $progress-bar-wider-height; 24 | .progress-bar { 25 | height: $progress-bar-wider-height; 26 | } 27 | } 28 | &.progress-narrower { 29 | height: $progress-bar-narrower-height; 30 | .progress-bar { 31 | height: $progress-bar-narrower-height; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=example # Name of Application 2 | PORT=3000 # Port of Application Server 3 | 4 | DB_HOST=localhost # Database Host Name or Ip 5 | DB_PORT=3306 # Port of Database Server 6 | DB_DATABASE=example # Database Name 7 | DB_USERNAME=example # Database Username 8 | DB_PASSWORD=example # Database Password 9 | 10 | SESSION_SECRET=example # Session Secret Phrase 11 | 12 | 13 | REDIS_HOST=localhost # Hostname or Ip of Redis IO Server 14 | REDIS_PORT=6379 # Port of Redis IO Server 15 | REDIS_PASSWORD=example # Redis IO Password 16 | 17 | ETHEREAL_USER=example@ethereal.email # Ethereal user name 18 | ETHEREAL_PASSWD=example # Ethereal password 19 | 20 | ENCRYPT_KEY=3zTvzr3p67VC61jmV54rIYu1545x4TlY # Encrypt Key 21 | ENCRYPT_IV=60iP0h6vJoEa # Encrypt Iv 22 | SALT_ROUNDS=10 # Bcrypt Salt Rounds -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "futibaclub", 3 | "version": "1.0.0", 4 | "description": "Bolão de Apostas de Resultados dos Jogos de Futebol", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: nenhum teste especificado\" && exit 1", 8 | "start": "node index.js" 9 | }, 10 | "keywords": [ 11 | "futibol", 12 | "apostas", 13 | "jogos", 14 | "bolão", 15 | "resultados" 16 | ], 17 | "author": "Vitor Rodrigues (https://github.com/vs0uz4)", 18 | "license": "MIT", 19 | "dependencies": { 20 | "bcrypt": "^5.1.1", 21 | "body-parser": "^1.20.1", 22 | "dotenv": "^6.1.0", 23 | "ejs": "^3.1.7", 24 | "express": "^4.18.2", 25 | "express-rate-limit": "^7.1.5", 26 | "express-session": "^1.15.6", 27 | "lusca": "^1.7.0", 28 | "mysql2": "^1.6.4", 29 | "nodemailer": "^6.6.1", 30 | "redis": "^3.1.1" 31 | }, 32 | "devDependencies": { 33 | "standard": "^12.0.1" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /public/css/futiba-interno.css: -------------------------------------------------------------------------------- 1 | .top-nav-collapse { 2 | background-color: #499542 !important; 3 | } 4 | 5 | @media (max-width: 768px) { 6 | .navbar:not(.top-nav-collapse) { 7 | background: #459542 !important; 8 | } 9 | } 10 | 11 | h6 { 12 | line-height: 1.7; 13 | } 14 | 15 | @media (max-width: 450px) { 16 | .margins { 17 | margin-right: 1rem; 18 | margin-left: 1rem; 19 | } 20 | } 21 | 22 | .navbar-inner { 23 | min-height: 0px; 24 | } 25 | 26 | .navbar-brand, 27 | .navbar-nav li a { 28 | line-height: 70px; 29 | height: 70px; 30 | padding-top: 0; 31 | } 32 | 33 | .navbar-brand img { 34 | margin: 6px; 35 | } 36 | 37 | .navbar { 38 | font-weight: 300; 39 | padding-right: 1rem !important; 40 | background-color: #4CAF50 !important; 41 | } 42 | 43 | .dropdown-item.active, .dropdown-item:active { 44 | color: #fff !important; 45 | text-decoration: none; 46 | background-color: #bfbfc46c; 47 | } 48 | 49 | .content { 50 | margin: 140px 0 0 0; 51 | } -------------------------------------------------------------------------------- /public/sass/mdb/free/data/_prefixer.scss: -------------------------------------------------------------------------------- 1 | // Transforms 2 | 3 | @mixin transform($args) { 4 | -webkit-transform: $args; 5 | -moz-transform: $args; 6 | -ms-transform: $args; 7 | -o-transform: $args; 8 | transform: $args; 9 | } 10 | 11 | @mixin transform-origin($args) { 12 | -webkit-transform-origin: $args; 13 | -moz-transform-origin: $args; 14 | -ms-transform-origin: $args; 15 | -o-transform-origin: $args; 16 | transform-origin: $args; 17 | } 18 | 19 | // Keyframes 20 | @mixin keyframes($animation-name) { 21 | @keyframes #{$animation-name} { 22 | @content; 23 | } 24 | } 25 | 26 | //animations 27 | @mixin animation($args) { 28 | -webkit-animation: $args; 29 | -moz-animation: $args; 30 | -ms-animation: $args; 31 | -o-animation: $args; 32 | animation: $args; 33 | } 34 | 35 | @mixin animation-delay($delay) { 36 | -webkit-animation-delay: $delay; 37 | -moz-animation-delay: $delay; 38 | -ms-animation-delay: $delay; 39 | -o-animation-delay: $delay; 40 | animation-delay: $delay; 41 | } -------------------------------------------------------------------------------- /public/sass/mdb/free/_tables.scss: -------------------------------------------------------------------------------- 1 | table { 2 | th { 3 | font-size: 0.9rem; 4 | font-weight: 400; 5 | } 6 | td { 7 | font-size: 0.9rem; 8 | font-weight: 300; 9 | } 10 | &.table { 11 | thead th { 12 | border-top: none; 13 | } 14 | th, 15 | td { 16 | padding-top: 1.1rem; 17 | padding-bottom: 1rem; 18 | } 19 | a { 20 | margin: 0; 21 | color: #212529; 22 | } 23 | .label-table { 24 | margin: 0; 25 | padding: 0; 26 | line-height: 15px; 27 | height: 15px; 28 | } 29 | .btn-table { 30 | margin: 0px 1px; 31 | padding: 3px 7px; 32 | .fa { 33 | font-size: 11px; 34 | } 35 | } 36 | } 37 | &.table-hover tbody tr:hover { 38 | background-color: rgba(0, 0, 0, 0.075); 39 | transition: 0.5s; 40 | } 41 | .th-lg { 42 | min-width: 9rem; 43 | } 44 | &.table-sm { 45 | th, 46 | td { 47 | padding-top: 0.6rem; 48 | padding-bottom: 0.6rem; 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /public/sass/mdb/free/_cards-basic.scss: -------------------------------------------------------------------------------- 1 | // Card Basic 2 | .card { 3 | font-weight: 400; 4 | border-radius: .3rem; 5 | &:not([class*="card-outline-"]) { 6 | border: 0; 7 | @extend .z-depth-1; 8 | } 9 | img { 10 | border-radius: 2px 2px 0 0; 11 | } 12 | .card-body { 13 | position: relative; 14 | h5 { 15 | margin-bottom: 1rem; 16 | font-size: 1rem; 17 | font-weight: 400; 18 | } 19 | h3 { 20 | margin-bottom: 1rem; 21 | font-weight: 400; 22 | } 23 | h4 { 24 | font-weight: 400; 25 | } 26 | p { 27 | margin-bottom: 1rem; 28 | } 29 | } 30 | .card-title { 31 | a { 32 | transition: 0.3s; 33 | &:hover { 34 | transition: 0.3s; 35 | } 36 | } 37 | } 38 | .card-text { 39 | font-size: 0.9rem; 40 | color: #747373; 41 | font-weight: 400; 42 | } 43 | .card-footer { 44 | &.links-light { 45 | a { 46 | font-size: 15px; 47 | color: #757575; 48 | &:hover { 49 | color: #d50000; 50 | transition: 0.4s; 51 | } 52 | .fa { 53 | font-size: 17px; 54 | } 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /public/css/futiba.css: -------------------------------------------------------------------------------- 1 | .intro-2 { 2 | background: url('../img/campo.png')no-repeat center center; 3 | background-size: cover; 4 | } 5 | 6 | .top-nav-collapse { 7 | background-color: #499542 !important; 8 | } 9 | 10 | .navbar:not(.top-nav-collapse) { 11 | background: transparent !important; 12 | } 13 | 14 | @media (max-width: 768px) { 15 | .navbar:not(.top-nav-collapse) { 16 | background: #459542 !important; 17 | } 18 | } 19 | 20 | h6 { 21 | line-height: 1.7; 22 | } 23 | 24 | .hm-gradient .full-bg-img { 25 | background: -moz-linear-gradient(45deg, rgba(38, 161, 27, 0.7), rgba(35, 210, 29, 0.7) 100%); 26 | background: -webkit-linear-gradient(45deg, rgba(81, 161, 27, 0.7), rgba(35, 210, 29, 0.7) 100%); 27 | background: -webkit-gradient(linear, 45deg, from(rgba(69, 161, 27, 0.7)), to(rgba(35, 210, 29, 0.7))); 28 | background: -o-linear-gradient(45deg, rgba(38, 161, 27, 0.7), rgba(35, 210, 29, 0.7) 100%); 29 | background: linear-gradient(to 45deg, rgba(31, 161, 27, 0.7), rgba(35, 210, 29, 0.7) 100%); 30 | } 31 | 32 | @media (max-width: 450px) { 33 | .margins { 34 | margin-right: 1rem; 35 | margin-left: 1rem; 36 | } 37 | } 38 | 39 | .navbar-inner { 40 | min-height: 0px; 41 | } 42 | 43 | .navbar-brand, 44 | .navbar-nav li a { 45 | line-height: 50px; 46 | height: 50px; 47 | padding-top: 0; 48 | } 49 | 50 | .navbar-brand img { 51 | margin: 6px; 52 | } -------------------------------------------------------------------------------- /public/sass/mdb/free/_carousel-basic.scss: -------------------------------------------------------------------------------- 1 | // CAROUSELS BASIC 2 | .carousel { 3 | .carousel-control-prev-icon, 4 | .carousel-control-next-icon { 5 | width: 36px; 6 | height: 36px; 7 | } 8 | .carousel-control-prev-icon { 9 | background-image: url(#{$image-path}/svg/arrow_left.svg); 10 | } 11 | .carousel-control-next-icon { 12 | background-image: url(#{$image-path}/svg/arrow_right.svg); 13 | } 14 | .carousel-indicators { 15 | li { 16 | max-width: 10px; 17 | height: 10px; 18 | border-radius: 50%; 19 | cursor:pointer; 20 | } 21 | } 22 | .video-fluid { 23 | height: 100%; 24 | width: 100%; 25 | } 26 | } 27 | 28 | .carousel-fade { 29 | .carousel-item { 30 | opacity: 0; 31 | transition-duration: .6s; 32 | transition-property: opacity; 33 | } 34 | 35 | .carousel-item.active, 36 | .carousel-item-next.carousel-item-left, 37 | .carousel-item-prev.carousel-item-right { 38 | opacity: 1; 39 | } 40 | 41 | .active.carousel-item-left, 42 | .active.carousel-item-right { 43 | opacity: 0; 44 | } 45 | 46 | .carousel-item-next, 47 | .carousel-item-prev, 48 | .carousel-item.active, 49 | .active.carousel-item-left, 50 | .active.carousel-item-prev { 51 | transform: translateX(0); 52 | 53 | @supports (transform-style: preserve-3d) { 54 | transform: translate3d(0, 0, 0); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /public/img/lightbox/default-skin.svg: -------------------------------------------------------------------------------- 1 | default-skin 2 -------------------------------------------------------------------------------- /public/sass/mdb/free/_footer.scss: -------------------------------------------------------------------------------- 1 | // FOOTER 2 | footer { 3 | &.page-footer { 4 | margin-top: $footer-margin-top; 5 | padding-top: $footer-padding-top; 6 | color: $white; 7 | .container-fluid { 8 | width:auto; 9 | } 10 | .footer-copyright { 11 | overflow: hidden; 12 | height: $footer-copyright-height; 13 | line-height: $footer-copyright-line-height; 14 | color: rgba($white, .6); 15 | background-color: rgba($black, .2); 16 | text-align: center; 17 | font-size: 0.9rem; 18 | } 19 | a { 20 | color: $white; 21 | } 22 | .title { 23 | text-transform: uppercase; 24 | } 25 | .call-to-action { 26 | text-align: center; 27 | padding-top: $footer-call-to-action-pt; 28 | padding-bottom: $footer-call-to-action-pb; 29 | ul li { 30 | display: inline-block; 31 | padding-right: $footer-call-to-action-pr; 32 | } 33 | } 34 | .social-section { 35 | ul li { 36 | display: inline-block; 37 | } 38 | ul { 39 | a { 40 | margin-left: 0; 41 | padding-left: 0; 42 | } 43 | } 44 | } 45 | ul { 46 | list-style-type:none; 47 | padding:0; 48 | } 49 | } 50 | } 51 | 52 | /* Instagram photos */ 53 | ul.instagram-photos { 54 | list-style-type: none; 55 | padding:0; 56 | li { 57 | display: inline-block; 58 | max-width: $footer-insta-photos-max-width; 59 | margin: $footer-insta-photos-margin; 60 | img { 61 | margin: 0; 62 | @extend .z-depth-1-half; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /utils/mailer.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const nodemailer = require('nodemailer') 4 | 5 | const init = async (account, options) => { 6 | if (account && options) { 7 | let transporter = nodemailer.createTransport({ 8 | host: 'smtp.ethereal.email', 9 | port: 587, 10 | secure: false, // true for 465, false for other ports 11 | auth: { 12 | user: account.user, // generated ethereal user 13 | pass: account.pass // generated ethereal password 14 | } 15 | }) 16 | 17 | // setup email data with unicode symbols 18 | let mailOptions = { 19 | from: `"${options.name}" <${options.email}>`, // sender address 20 | to: 'futibaclub@example.com', // list of receivers 21 | subject: `FutibaClub SAC - ${options.subject}`, // Subject line 22 | text: `Olá Futibaclub\n${options.message}`, // plain text body 23 | html: `

Olá Futibaclub!

${options.message}

` // html body 24 | } 25 | 26 | // send mail with defined transport object 27 | const transporterMailed = await transporter.sendMail(mailOptions) 28 | 29 | const reply = { 30 | error: null, 31 | messageId: transporterMailed.messageId, 32 | previewUrl: nodemailer.getTestMessageUrl(transporterMailed) 33 | } 34 | 35 | return reply 36 | } else { 37 | const err = 'Mailer : Parametros {account} e {options} para criação de mailer indefinidos' 38 | 39 | const reply = { 40 | error: err, 41 | messageId: null, 42 | previewUrl: null 43 | } 44 | 45 | return reply 46 | } 47 | } 48 | 49 | module.exports = init 50 | -------------------------------------------------------------------------------- /public/sass/mdb/free/_pagination.scss: -------------------------------------------------------------------------------- 1 | .pagination { 2 | .page-link { 3 | background-color: transparent; 4 | font-size: 0.8rem; 5 | } 6 | .active .page-link { 7 | border-radius: 2px; 8 | transition: all 0.2s linear; 9 | @extend.z-depth-1; 10 | &:hover { 11 | color: #fff; 12 | } 13 | } 14 | } 15 | 16 | // Circle pagination 17 | .pagination { 18 | &.pagination-circle { 19 | .page-link { 20 | border-radius: 50%; 21 | margin-left: 2px; 22 | margin-right: 2px; 23 | &:hover { 24 | border-radius: 50%; 25 | } 26 | } 27 | .active .page-link { 28 | border-radius: 50%; 29 | } 30 | } 31 | } 32 | 33 | .pagination .page-link { 34 | border: 0; 35 | transition: all 0.3s linear; 36 | &:hover { 37 | transition: all 0.3s linear; 38 | background-color: #eee; 39 | } 40 | &:focus { 41 | background-color: transparent; 42 | } 43 | } 44 | 45 | .pagination { 46 | &.pg-blue { 47 | .active .page-link { 48 | background-color: $primary-color; 49 | } 50 | } 51 | &.pg-red { 52 | .active .page-link { 53 | background-color: $danger-color; 54 | } 55 | } 56 | &.pg-teal { 57 | .active .page-link { 58 | background-color: $default-color; 59 | } 60 | } 61 | &.pg-darkgrey { 62 | .active .page-link { 63 | background-color: $special-color; 64 | } 65 | } 66 | &.pg-dark { 67 | .active .page-link { 68 | background-color: $elegant-color; 69 | } 70 | } 71 | &.pg-bluegrey { 72 | .active .page-link { 73 | background-color: #3F729B; 74 | } 75 | } 76 | &.pg-amber { 77 | .active .page-link { 78 | background-color: #ff6f00; 79 | } 80 | } 81 | &.pg-purple { 82 | .active .page-link { 83 | background-color: #5e35b1; 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /views/account/sign-in.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <% include ../_partials/painel/header %> 7 | 8 | 9 | 10 |
11 | 12 | <% include ../_partials/navbar %> 13 |
14 | 15 |
16 |
17 |
18 |
19 |

Entrar

20 |
21 | 22 | 23 | 24 |
25 | 26 |
27 | 28 | 29 | 30 |
31 | 32 | <% if(locals.error) { %> 33 |
34 | <%= error %> 35 |
36 | <% } %> 37 | 38 |
39 | 40 |
41 |
42 |
43 |
44 |
45 | 46 | 47 | <% include ../_partials/painel/footer %> 48 | 49 | 50 | -------------------------------------------------------------------------------- /public/sass/mdb.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Material Design for Bootstrap 4 3 | * Version: MDB Free 4.4.5 4 | * 5 | * 6 | * Copyright: Material Design for Bootstrap 7 | * https://mdbootstrap.com/ 8 | * 9 | * Read the license: https://mdbootstrap.com/license/ 10 | * 11 | * 12 | * Documentation: https://mdbootstrap.com/ 13 | * 14 | * Getting started: https://mdbootstrap.com/getting-started/ 15 | * 16 | * Tutorials: https://mdbootstrap.com/bootstrap-tutorial/ 17 | * 18 | * Templates: https://mdbootstrap.com/templates/ 19 | * 20 | * Support: https://mdbootstrap.com/support/ 21 | * 22 | * Contact: office@mdbootstrap.com 23 | * 24 | * Atribution: Animate CSS, Twitter Bootstrap, Materialize CSS, Normalize CSS, Waves JS, WOW JS, Toastr, Chart.js , Hammer.js 25 | * 26 | */ 27 | 28 | @charset "UTF-8"; 29 | 30 | // Bootstrap // 31 | 32 | @import "mdb/free/data/functions"; 33 | @import "mdb/free/data/variables-b4"; 34 | 35 | // MDB Framework // 36 | @import "mdb/free/data/prefixer"; 37 | @import "mdb/free/data/mixins"; 38 | @import "mdb/free/data/colors"; 39 | @import "mdb/free/data/variables"; 40 | @import "mdb/free/global"; 41 | @import "mdb/free/animations"; 42 | @import "mdb/free/helpers"; 43 | @import "mdb/free/typography"; 44 | @import "mdb/free/buttons"; 45 | @import "mdb/free/cards-basic"; 46 | @import "mdb/free/dropdowns"; 47 | @import "mdb/free/navbar"; 48 | @import "mdb/free/pagination"; 49 | @import "mdb/free/badge"; 50 | @import "mdb/free/breadcrumb"; 51 | @import "mdb/free/modals"; 52 | @import "mdb/free/progress"; 53 | @import "mdb/free/carousel-basic"; 54 | @import "mdb/free/collapse"; 55 | @import "mdb/free/jumbotron"; 56 | @import "mdb/free/masks"; 57 | @import "mdb/free/waves"; 58 | @import "mdb/free/forms-basic"; 59 | @import "mdb/free/msc"; 60 | @import "mdb/free/footer"; 61 | @import "mdb/free/list-group"; 62 | @import "mdb/free/tables"; 63 | 64 | // Custom Sass 65 | @import "mdb/custom"; 66 | -------------------------------------------------------------------------------- /public/sass/mdb/free/_global.scss: -------------------------------------------------------------------------------- 1 | .z-depth-0 { 2 | box-shadow: none !important; 3 | } 4 | 5 | .z-depth-1 { 6 | box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12); 7 | } 8 | 9 | .z-depth-1-half { 10 | box-shadow: 0 5px 11px 0 rgba(0, 0, 0, 0.18), 0 4px 15px 0 rgba(0, 0, 0, 0.15); 11 | } 12 | 13 | .z-depth-2 { 14 | box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); 15 | } 16 | 17 | .z-depth-3 { 18 | box-shadow: 0 12px 15px 0 rgba(0, 0, 0, 0.24), 0 17px 50px 0 rgba(0, 0, 0, 0.19); 19 | } 20 | 21 | .z-depth-4 { 22 | box-shadow: 0 16px 28px 0 rgba(0, 0, 0, 0.22), 0 25px 55px 0 rgba(0, 0, 0, 0.21); 23 | } 24 | 25 | .z-depth-5 { 26 | box-shadow: 0 27px 24px 0 rgba(0, 0, 0, 0.2), 0 40px 77px 0 rgba(0, 0, 0, 0.22); 27 | } 28 | 29 | .hoverable { 30 | @include transition(box-shadow .55s); 31 | box-shadow: 0; 32 | &:hover { 33 | @include transition(box-shadow .45s); 34 | box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); 35 | } 36 | } 37 | 38 | a { 39 | color: $link-color; 40 | text-decoration: none; 41 | cursor: pointer; 42 | // Gets rid of tap active state 43 | -webkit-tap-highlight-color: transparent; 44 | &:hover, 45 | &:focus { 46 | text-decoration: none; 47 | } 48 | } 49 | 50 | a:not([href]):not([tabindex]), a:not([href]):not([tabindex]):focus, a:not([href]):not([tabindex]):hover { 51 | color: inherit; 52 | text-decoration: none; 53 | } 54 | 55 | //Disabled cursor 56 | .disabled { 57 | cursor: not-allowed!important; 58 | } 59 | 60 | // .list-group, 61 | .jumbotron, 62 | .navbar, 63 | .badge, 64 | .chip { 65 | @extend .z-depth-1; 66 | } 67 | 68 | .list-group .list-group-item, 69 | .jumbotron, 70 | .navbar-toggler, 71 | .badge { 72 | @include border-radius(2px); 73 | } 74 | 75 | .flex-center { 76 | ul { 77 | list-style-type:none; 78 | padding:0; 79 | } 80 | } 81 | 82 | // Media 83 | .media { 84 | .avatar-sm { 85 | width: 56px; 86 | border-radius: 50%; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /npm/encryptedRedisClient.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class EncryptedRedisClient { 4 | constructor (redisClient, safe) { 5 | this.redisClient = redisClient 6 | this.safe = safe 7 | } 8 | 9 | get (key, cb) { 10 | this.redisClient.get(key, (err, reply) => { 11 | if (err) { 12 | cb(err) 13 | } 14 | 15 | if (reply) { 16 | cb(null, this.safe.decrypt(reply)) 17 | } else { 18 | cb(null, []) 19 | } 20 | }) 21 | } 22 | 23 | getAsync (key, cb) { 24 | return new Promise((resolve, reject) => { 25 | try { 26 | this.redisClient.get(key, (err, reply) => { 27 | if (err) { 28 | reject(cb(err)) 29 | } 30 | 31 | if (reply) { 32 | resolve(cb(null, this.safe.decrypt(reply))) 33 | } else { 34 | resolve(cb(null, [])) 35 | } 36 | }) 37 | } catch (exception) { 38 | reject(exception) 39 | } 40 | }) 41 | } 42 | 43 | set (key, value, cb) { 44 | const encValue = this.safe.encrypt(value) 45 | this.redisClient.set(key, encValue, cb) 46 | } 47 | 48 | setAsync (key, value, cb) { 49 | return new Promise((resolve, reject) => { 50 | try { 51 | const encValue = this.safe.encrypt(value) 52 | const reply = this.redisClient.set(key, encValue, cb) 53 | 54 | resolve(reply) 55 | } catch (exception) { 56 | reject(exception) 57 | } 58 | }) 59 | } 60 | 61 | setex (key, seconds, value, cb) { 62 | const encValue = this.safe.encrypt(value) 63 | this.redisClient.setex(key, seconds, encValue, cb) 64 | } 65 | 66 | setexAsync (key, seconds, value, cb) { 67 | return new Promise((resolve, reject) => { 68 | try { 69 | const encValue = this.safe.encrypt(value) 70 | const reply = this.redisClient.setex(key, seconds, encValue, cb) 71 | 72 | resolve(reply) 73 | } catch (exception) { 74 | reject(exception) 75 | } 76 | }) 77 | } 78 | } 79 | 80 | module.exports = EncryptedRedisClient 81 | -------------------------------------------------------------------------------- /public/sass/mdb/free/_masks.scss: -------------------------------------------------------------------------------- 1 | // Masks 2 | .view { 3 | overflow: hidden; 4 | position: relative; 5 | cursor: default; 6 | .mask { 7 | background-attachment: fixed; 8 | } 9 | .mask, 10 | .full-bg-img { 11 | width: 100%; 12 | height: 100%; 13 | position: absolute; 14 | overflow: hidden; 15 | top: 0; 16 | left: 0; 17 | } 18 | img, video { 19 | display: block; 20 | position: relative; 21 | } 22 | } 23 | 24 | // Zoom and overlays 25 | .hm-zoom, 26 | .overlay { 27 | img, 28 | video { 29 | @include transition(all 0.2s linear); 30 | } 31 | &:hover { 32 | .mask { 33 | opacity: 1; 34 | } 35 | } 36 | } 37 | .hm-zoom { 38 | &:hover { 39 | img { 40 | transform: scale(1.1); 41 | } 42 | } 43 | } 44 | .overlay { 45 | .mask { 46 | opacity: 0; 47 | @include transition(all 0.4s ease-in-out); 48 | } 49 | } 50 | 51 | // Overlay patterns 52 | $patterns: ( 53 | 1: "01", 54 | 2: "02", 55 | 3: "03", 56 | 4: "04", 57 | 5: "05", 58 | 6: "06", 59 | 7: "07", 60 | 8: "08", 61 | 9: "09" 62 | ); 63 | 64 | @each $no, $filename in $patterns { 65 | .pattern-#{$no} { 66 | background: url('../img/overlays/#{$filename}.png'); 67 | } 68 | } 69 | 70 | // Overlay masks 71 | @each $name, $color in $rgba-colors { 72 | .hm-#{$name} { 73 | .mask, 74 | .full-bg-img { 75 | background-color: $color; 76 | } 77 | } 78 | } 79 | 80 | .full-height, 81 | .full-height body, 82 | .full-height header, 83 | .full-height header .view { 84 | height: 100%; 85 | } 86 | 87 | .intro { 88 | min-height: 1000px; 89 | position: relative; 90 | @media (max-width: 768px) { 91 | min-height: 1000px; 92 | } 93 | } 94 | 95 | .intro-video { 96 | video { 97 | top: 50%; 98 | left: 50%; 99 | min-width: 100%; 100 | min-height: 100%; 101 | width: auto; 102 | height: auto; 103 | z-index: -100; 104 | transform: translateX(-50%) translateY(-50%); 105 | background-repeat: no-repeat; 106 | background-size: cover; 107 | background-position: center center; 108 | transition: 1s opacity; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /views/account/profile.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <% include ../_partials/painel/header %> 7 | 8 | 9 | 10 |
11 | 12 | <% include ../_partials/navbar %> 13 |
14 | 15 |
16 |
17 |
18 |
19 |

Alterar dados do perfil

20 | 21 |
22 | 23 | 24 | 25 |
26 |
27 | 28 | 29 | 30 |
31 | 32 |
33 | 34 | 35 | 36 |
37 | 38 | <% if(locals.error) { %> 39 |
40 | <%= error %> 41 |
42 | <% } %> 43 | 44 |
45 | 46 |
47 |
48 |
49 |
50 |
51 | 52 | 53 | <% include ../_partials/painel/footer %> 54 | 55 | 56 | -------------------------------------------------------------------------------- /views/account/new.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <% include ../_partials/painel/header %> 7 | 8 | 9 | 10 |
11 | 12 | <% include ../_partials/navbar %> 13 |
14 | 15 |
16 |
17 |
18 |
19 |

Criar uma conta

20 | 21 |
22 | 23 | 24 | 25 |
26 |
27 | 28 | 29 | 30 |
31 | 32 |
33 | 34 | 35 | 36 |
37 | 38 | <% if(locals.error) { %> 39 |
40 | <%= error %> 41 |
42 | <% } %> 43 | 44 |
45 | 46 |
47 |
48 |
49 |
50 |
51 | 52 | 53 | <% include ../_partials/painel/footer %> 54 | 55 | 56 | -------------------------------------------------------------------------------- /views/_partials/menu-items.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /utils/safe.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Crypto = require('crypto') 4 | const algorithm = 'aes-256-gcm' 5 | 6 | class Safe { 7 | constructor (key, iv) { 8 | this.key = key 9 | this.iv = iv 10 | } 11 | 12 | encryptAsync (data) { 13 | return new Promise((resolve, reject) => { 14 | try { 15 | const cipher = Crypto.createCipheriv(algorithm, this.key, this.iv) 16 | let encrypted = cipher.update(JSON.stringify(data), 'utf8', 'hex') 17 | encrypted += cipher.final('hex') 18 | const tag = cipher.getAuthTag() 19 | 20 | resolve(JSON.stringify({ 21 | content: encrypted, 22 | tag: tag 23 | })) 24 | } catch (exception) { 25 | reject(exception) 26 | } 27 | }) 28 | } 29 | 30 | encrypt (data) { 31 | try { 32 | const cipher = Crypto.createCipheriv(algorithm, this.key, this.iv) 33 | let encrypted = cipher.update(JSON.stringify(data), 'utf8', 'hex') 34 | encrypted += cipher.final('hex') 35 | const tag = cipher.getAuthTag() 36 | 37 | return JSON.stringify({ 38 | content: encrypted, 39 | tag: tag 40 | }) 41 | } catch (exception) { 42 | throw new Error(exception.message) 43 | } 44 | } 45 | 46 | decryptAsync (encrypted) { 47 | return new Promise((resolve, reject) => { 48 | try { 49 | const data = JSON.parse(encrypted) 50 | data.tag = Buffer.from(data.tag) 51 | 52 | const decipher = Crypto.createDecipheriv(algorithm, this.key, this.iv) 53 | decipher.setAuthTag(data.tag) 54 | let decrypted = decipher.update(data.content, 'hex', 'utf8') 55 | decrypted += decipher.final('utf8') 56 | 57 | resolve(JSON.parse(decrypted)) 58 | } catch (exception) { 59 | reject(exception) 60 | } 61 | }) 62 | } 63 | 64 | decrypt (encrypted) { 65 | try { 66 | const data = JSON.parse(encrypted) 67 | data.tag = Buffer.from(data.tag) 68 | 69 | const decipher = Crypto.createDecipheriv(algorithm, this.key, this.iv) 70 | decipher.setAuthTag(data.tag) 71 | let decrypted = decipher.update(data.content, 'hex', 'utf8') 72 | decrypted += decipher.final('utf8') 73 | 74 | return JSON.parse(decrypted) 75 | } catch (exception) { 76 | throw new Error(exception.message) 77 | } 78 | } 79 | } 80 | 81 | module.exports = Safe 82 | -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | /* Your custom styles */ 2 | .dropdown-menu { 3 | position: absolute; 4 | top: 100%; 5 | left: 0; 6 | z-index: 1000; 7 | display: none; 8 | float: left; 9 | min-width: 10rem; 10 | padding: .5rem .5rem; 11 | margin: .125rem 0 0; 12 | font-size: 1rem; 13 | color: #212529; 14 | text-align: left; 15 | list-style: none; 16 | background-color: #fff; 17 | background-clip: padding-box; 18 | border: 1px solid rgba(0, 0, 0, .15); 19 | border-radius: .25rem; 20 | } 21 | 22 | .navbar .dropdown-menu a { 23 | font-size: .9375rem; 24 | font-weight: 300; 25 | padding: 0px 10px; 26 | color: #495057 !important; 27 | } 28 | 29 | .navbar .dropdown-menu a:hover { 30 | color: rgba(139, 104, 104, 0.5) !important; 31 | } 32 | 33 | 34 | .nav-tabs .nav-item.show .nav-link, .nav-tabs .nav-link.active { 35 | color: #495057 !important; 36 | } 37 | 38 | 39 | .nav-tabs .nav-item.show .nav-link, .nav-tabs .nav-link { 40 | color: rgba(139, 104, 104, 0.5) !important; 41 | } 42 | 43 | .team-title { 44 | width: 140px !important; 45 | line-height: 35px; 46 | } 47 | 48 | select.form-control:not([size]):not([multiple]) { 49 | margin-left: 2.65rem; 50 | height: calc(2.25rem + 2px); 51 | width: calc(100% - 2.65rem); 52 | width: -webkit-fill-available; 53 | } 54 | 55 | select.md-select { 56 | background-color: transparent; 57 | border: none; 58 | border-bottom: 1px solid #bdbdbd; 59 | border-radius: 0; 60 | outline: 0; 61 | height: 2.1rem; 62 | width: 100%; 63 | font-size: 1rem; 64 | color: rgb(117, 117, 117); 65 | font: 300 16px Roboto; 66 | cursor: text; 67 | box-shadow: none; 68 | box-sizing: content-box; 69 | } 70 | 71 | select.md-select > option:not(:disabled), select.md-select > option:not(:checked) { 72 | color: rgb(117, 117, 117); 73 | font: 300 16px Roboto; 74 | cursor: text; 75 | } 76 | 77 | select.md-select > option:disabled { 78 | color: rgba(117, 117, 117, 0.568); 79 | font: 300 16px Roboto; 80 | cursor: text; 81 | } 82 | 83 | select.md-select > option:checked:not(:disabled){ 84 | color: #495057; 85 | font: 400 16px Roboto; 86 | cursor: text; 87 | } 88 | 89 | .md-select-dropdowned { 90 | color: #495057 !important; 91 | font: 400 16px Roboto !important; 92 | cursor: text !important; 93 | } 94 | 95 | textarea { 96 | color: #495057; 97 | font: 400 1rem Roboto; 98 | cursor: text; 99 | } 100 | -------------------------------------------------------------------------------- /ranking.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const route = express.Router() 3 | const encRedisClient = require('./cache/setup') 4 | 5 | const init = connection => { 6 | let classification = null 7 | 8 | route.get('/', async (req, res) => { 9 | await encRedisClient.getAsync('invalidatedCache', async (err, reply) => { 10 | if (err) { 11 | console.log(err.message) 12 | } 13 | 14 | if (reply) { 15 | classification = null 16 | } 17 | }) 18 | 19 | if (!classification) { 20 | const groupsRanking = await encRedisClient.getAsync('groupsRanking', async (err, reply) => { 21 | if (err) { 22 | console.log(err.message) 23 | } else { 24 | if (reply.length > 0) { 25 | return reply 26 | } else { 27 | const [groups] = await connection.execute('SELECT groups.id, groups.name, SUM(guessings.score) as score FROM `groups` LEFT JOIN guessings ON guessings.group_id = groups.id GROUP BY groups.id ORDER BY score DESC') 28 | 29 | await encRedisClient.setexAsync('groupsRanking', 84600, groups, (err) => { 30 | if (err) { 31 | console.log(err.message) 32 | } 33 | }) 34 | 35 | return groups 36 | } 37 | } 38 | }) 39 | 40 | const usersRanking = await encRedisClient.getAsync('usersRanking', async (err, reply) => { 41 | if (err) { 42 | console.log(err.message) 43 | } else { 44 | if (reply.length > 0) { 45 | return reply 46 | } else { 47 | const [users] = await connection.execute('SELECT users.id, users.name, SUM(guessings.score) as score FROM `users` LEFT JOIN guessings on guessings.user_id = users.id GROUP BY users.id ORDER BY score DESC') 48 | 49 | await encRedisClient.setexAsync('usersRanking', 84600, users, (err) => { 50 | if (err) { 51 | console.log(err.message) 52 | } 53 | }) 54 | 55 | return users 56 | } 57 | } 58 | }) 59 | 60 | await encRedisClient.setAsync('invalidatedCache', false, (err) => { 61 | if (err) { 62 | console.log(err.message) 63 | } 64 | }) 65 | 66 | classification = { 67 | groupsRanking, 68 | usersRanking 69 | } 70 | } 71 | 72 | res.render('ranking', classification) 73 | }) 74 | 75 | return route 76 | } 77 | 78 | module.exports = init 79 | -------------------------------------------------------------------------------- /public/sass/mdb/free/_buttons.scss: -------------------------------------------------------------------------------- 1 | // Buttons 2 | button:focus { 3 | outline:0 !important; 4 | } 5 | .btn { 6 | @extend .z-depth-1; 7 | font-size: 0.8rem; 8 | padding: 0.85rem 2.13rem; 9 | margin: 6px; 10 | @extend .white-text; 11 | border-radius: 2px; 12 | border: 0; 13 | transition: .2s ease-out; 14 | text-transform: uppercase; 15 | white-space: normal !important; 16 | word-wrap: break-word; 17 | cursor: pointer; 18 | &:hover, 19 | &:active, 20 | &:focus, 21 | &:active:focus { 22 | @extend .z-depth-1-half; 23 | outline: 0; 24 | } 25 | .fa { 26 | font-size: 1rem; 27 | position: relative; 28 | vertical-align: middle; 29 | margin-top: -2px; 30 | &.right { 31 | margin-left: 3px; 32 | } 33 | &.left { 34 | margin-right: 3px; 35 | } 36 | } 37 | &.btn-lg { 38 | font-size: 0.9rem; 39 | padding: 1rem 2.4rem; 40 | .fa { 41 | font-size: 1.2rem; 42 | } 43 | } 44 | &.btn-md { 45 | font-size: 0.7rem; 46 | padding: 0.7rem 1.6rem; 47 | .fa { 48 | font-size: 0.9rem; 49 | } 50 | } 51 | &.btn-sm { 52 | font-size: 0.6rem; 53 | padding: 0.5rem 1.6rem; 54 | &.btn-table { 55 | padding: 0.5rem 0.9rem; 56 | } 57 | .fa { 58 | font-size: 0.7rem; 59 | } 60 | } 61 | &.btn-tb { 62 | padding: 0.3rem 1rem; 63 | } 64 | &.disabled, 65 | &:disabled { 66 | cursor: not-allowed; 67 | pointer-events: none; 68 | } 69 | &[class*="btn-outline-"] { 70 | padding-top:.76rem; 71 | padding-bottom: .76rem; 72 | &.btn-sm { 73 | padding-top:.42rem; 74 | padding-bottom: .42rem; 75 | } 76 | &.btn-md { 77 | padding-top:.58rem; 78 | padding-bottom: .58rem; 79 | } 80 | &.btn-lg { 81 | padding-top:.9rem; 82 | padding-bottom: .9rem; 83 | } 84 | } 85 | } 86 | 87 | .btn-secondary.disabled, .btn-secondary:disabled { 88 | background-color: #b579d2!important; 89 | border-color: #b579d2 !important; 90 | } 91 | 92 | .btn-group { 93 | .btn { 94 | margin: 0; 95 | } 96 | } 97 | .btn-block { 98 | margin: inherit; 99 | } 100 | .btn-split { 101 | padding-left: 0.85rem; 102 | padding-right: 1.25rem; 103 | } 104 | .btn-link { 105 | background-color: transparent; 106 | @extend .black-text; 107 | box-shadow: none !important; 108 | &:hover, 109 | &:focus { 110 | background-color: transparent; 111 | box-shadow: none !important; 112 | } 113 | } 114 | .btn-flat { 115 | box-shadow: none; 116 | &:active, 117 | &:focus, 118 | &:hover { 119 | box-shadow: none !important; 120 | } 121 | } 122 | @each $btn-name, $color-value in $material-colors { 123 | @include make-button($btn-name, $color-value); 124 | @include make-outline-button($btn-name, $color-value); 125 | } 126 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

5 | 6 | 7 |

8 | 9 | ## DevPleno - FullStack Academy (FutibaClub) 10 | O FutibaClub é um aplicativo web, para você palpitar resultados de jogos de futebol da Copa do Mundo e do Brasileirão, nele os usuários poderão também gerenciar grupos aonde outros usuários poderão solicitar a participação e sendo aprovada pelo proprietário do grupo, palpitar sobre os resultados dos jogos. 11 | 12 | Após os jogos terem sido concluídos seus resultados podem ser lançados no aplicativo e neste momento automatiamente a classificação dos usuários e grupos será calculada. 13 | 14 | ## :hammer_and_wrench: Tecnologias Envolvidas 15 | - NodeJS; 16 | - MySQL; 17 | - Redis IO; 18 | - Criptografia; 19 | - Soft Count; 20 | - Nodemailer; 21 | - Ethereal Email. 22 | 23 | ## :gear: Funcionalidades 24 | 25 | - Criação de Contas de Usuários; 26 | - Gerencia de Grupos de Usuários; 27 | - Gerencia de Jogos e seus Resultados; 28 | - Classificação de Usuários e Grupos; 29 | - Gerenciamento de Dados do Perfil; 30 | - Formulário de Contato. 31 | 32 | > O formulário de contato foi implementado usando o package [node-mailer](https://nodemailer.com/about/). Para fins de demonstração utilizei o `transport` [Ethereal Email](https://ethereal.email/) para visualizar as mensagens enviadas por SMTP dispensando a necessidade de usar um SMTP real. 33 | 34 | > Para visualizar a mensagem de e-mail enviada, basta clicar no ícone presente no alerta que irá surgir após o envio, logo acima do botão `enviar` da seção de contatos. 35 | 36 | ## :open_book: Todo 37 | Algumas funcionalidades extras que não encontravam-se no escopo do treinamento estão na pendência de serem desenvolvidas. Estas funcionalidades foram sugeridas como melhoria para o projeto de forma a constarem no portifólio de aplicações/projetos desenvolvidos. As funcionalidades a serem desenvolvidas são: 38 | - ~~Gerenciamento de Dados do Perfil~~; 39 | - ~~Formulário de Contato~~; 40 | - ~~Efetuar deploy e hospedar~~; 41 | - Refatorar código; 42 | - Implmentar testes; 43 | - Implementar CI/CD. 44 | 45 | > Ultima atualização : 04/02/2021 46 | 47 | ## :eye: Aplicação Rodando 48 | :point_right: [FutibaClub](https://futibaclub.vsouza.rio.br/) 49 | 50 | ## :anger: Informações importantes: 51 | 52 | Este projeto foi entregue como parte do **Fullstack Academy** (edição FutibaClub) promovido pelo **DevPleno** (www.devpleno.com). 53 | 54 | **Participante:** Vitor de Souza Rodrigues 55 | 56 | **Chave do Certificado:** A8D5-6D98-EB16-ED2B 57 | 58 | O certificado pode ser consultado em: https://certificados.devpleno.com -------------------------------------------------------------------------------- /public/sass/mdb/free/data/_functions.scss: -------------------------------------------------------------------------------- 1 | // Bootstrap functions 2 | // 3 | // Utility mixins and functions for evalutating source code across our variables, maps, and mixins. 4 | 5 | // Ascending 6 | // Used to evaluate Sass maps like our grid breakpoints. 7 | @mixin _assert-ascending($map, $map-name) { 8 | $prev-key: null; 9 | $prev-num: null; 10 | @each $key, $num in $map { 11 | @if $prev-num == null { 12 | // Do nothing 13 | } @else if not comparable($prev-num, $num) { 14 | @warn "Potentially invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} whose unit makes it incomparable to #{$prev-num}, the value of the previous key '#{$prev-key}' !"; 15 | } @else if $prev-num >= $num { 16 | @warn "Invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} which isn't greater than #{$prev-num}, the value of the previous key '#{$prev-key}' !"; 17 | } 18 | $prev-key: $key; 19 | $prev-num: $num; 20 | } 21 | } 22 | 23 | // Starts at zero 24 | // Another grid mixin that ensures the min-width of the lowest breakpoint starts at 0. 25 | @mixin _assert-starts-at-zero($map) { 26 | $values: map-values($map); 27 | $first-value: nth($values, 1); 28 | @if $first-value != 0 { 29 | @warn "First breakpoint in `$grid-breakpoints` must start at 0, but starts at #{$first-value}."; 30 | } 31 | } 32 | 33 | // Replace `$search` with `$replace` in `$string` 34 | // Used on our SVG icon backgrounds for custom forms. 35 | // 36 | // @author Hugo Giraudel 37 | // @param {String} $string - Initial string 38 | // @param {String} $search - Substring to replace 39 | // @param {String} $replace ('') - New value 40 | // @return {String} - Updated string 41 | @function str-replace($string, $search, $replace: "") { 42 | $index: str-index($string, $search); 43 | 44 | @if $index { 45 | @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace); 46 | } 47 | 48 | @return $string; 49 | } 50 | 51 | // Color contrast 52 | @mixin color-yiq($color) { 53 | $r: red($color); 54 | $g: green($color); 55 | $b: blue($color); 56 | 57 | $yiq: (($r * 299) + ($g * 587) + ($b * 114)) / 1000; 58 | 59 | @if ($yiq >= 150) { 60 | color: #111; 61 | } @else { 62 | color: #fff; 63 | } 64 | } 65 | 66 | // Retreive color Sass maps 67 | @function color($key: "blue") { 68 | @return map-get($colors, $key); 69 | } 70 | 71 | @function theme-color($key: "primary") { 72 | @return map-get($theme-colors, $key); 73 | } 74 | 75 | @function grayscale($key: "100") { 76 | @return map-get($grays, $key); 77 | } 78 | 79 | // Request a theme color level 80 | @function theme-color-level($color-name: "primary", $level: 0) { 81 | $color: theme-color($color-name); 82 | $color-base: if($level > 0, #000, #fff); 83 | 84 | @if $level < 0 { 85 | // Lighter values need a quick double negative for the Sass math to work 86 | @return mix($color-base, $color, $level * -1 * $theme-color-interval); 87 | } @else { 88 | @return mix($color-base, $color, $level * $theme-color-interval); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | const express = require('express') 3 | const cookieParser = require("cookie-parser") 4 | const bodyParser = require('body-parser') 5 | const session = require('express-session') 6 | const csrf = require('lusca').csrf 7 | const ratelimit = require('express-rate-limit') 8 | const mysql = require('mysql2/promise') 9 | 10 | const middleware = require('./middleware') 11 | const mailer = require('./utils/mailer') 12 | const admin = require('./admin') 13 | const account = require('./account') 14 | const groups = require('./groups') 15 | const ranking = require('./ranking') 16 | 17 | // Get Enviroment Variables 18 | const enviroment = process.env.NODE_ENV 19 | const port = process.env.PORT 20 | const appName = process.env.APP_NAME 21 | const dbHost = process.env.DB_HOST 22 | const dbPort = process.env.DB_PORT 23 | const dbDatabase = process.env.DB_DATABASE 24 | const dbUsername = process.env.DB_USERNAME 25 | const dbPassword = process.env.DB_PASSWORD 26 | const sessionSecret = process.env.SESSION_SECRET 27 | const etherealUser = process.env.ETHEREAL_USER 28 | const etherealPasswd = process.env.ETHEREAL_PASSWD 29 | 30 | const app = express() 31 | const limiter = ratelimit({ 32 | windowMs: 15 * 60 * 1000, 33 | max: 100, 34 | }) 35 | app.use(limiter) 36 | app.use(express.static('public')) 37 | app.use(cookieParser()) 38 | app.use(bodyParser.urlencoded({ 39 | extended: false 40 | })) 41 | app.use(session({ 42 | secret: sessionSecret, 43 | cookie: { keys: [sessionSecret], secure: enviroment !== "development", httpOnly: true, maxAge: 24 * 60 * 60 * 1000 }, 44 | saveUninitialized: true, 45 | resave: true 46 | })) 47 | app.use(csrf()) 48 | app.use(middleware) 49 | app.set('view engine', 'ejs') 50 | 51 | const init = async () => { 52 | try { 53 | const connection = await mysql.createConnection({ 54 | host: dbHost, 55 | port: dbPort, 56 | user: dbUsername, 57 | password: dbPassword, 58 | database: dbDatabase 59 | }) 60 | 61 | app.get('/', async (req, res) => { 62 | res.render('home') 63 | }) 64 | 65 | app.post('/', async (req, res) => { 66 | const account = { 67 | user: etherealUser, 68 | pass: etherealPasswd 69 | } 70 | 71 | const subjects = { 72 | 1: 'Reclamações', 73 | 2: 'Sugestões', 74 | 3: 'Informações', 75 | 4: 'Outros' 76 | } 77 | 78 | const mailerOptions = { 79 | name: req.body.name, 80 | email: req.body.email, 81 | subject: subjects[req.body.subject], 82 | message: req.body.message 83 | } 84 | 85 | const reply = await mailer(account, mailerOptions) 86 | 87 | res.render('home', reply) 88 | }) 89 | 90 | app.use(account(connection)) 91 | app.use('/groups', groups(connection)) 92 | app.use('/ranking', ranking(connection)) 93 | app.use('/admin', admin(connection)) 94 | 95 | app.listen(port, '0.0.0.0', error => { 96 | if (error) { 97 | console.log(`Error in Startup ${appName} Server\nError Message ${error.message}`) 98 | } else { 99 | console.log(`${appName} Server Running...`) 100 | } 101 | }) 102 | } catch (error) { 103 | console.log(`Error occurred in Initialize App\nError Message: ${error.message}`) 104 | } 105 | } 106 | 107 | init() 108 | -------------------------------------------------------------------------------- /views/groups.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <% include _partials/painel/header %> 7 | 8 | 9 | 10 |
11 | 12 | <% include _partials/navbar %> 13 |
14 | 15 |
16 |
17 |

Grupos

18 |
19 |
20 | 21 | 22 | 23 |
24 |
25 | 26 |
27 |
28 |
29 |
30 | 31 |
32 |
33 | <% if (groups.length !== 0) { %> 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | <% groups.forEach( group => { %> 45 | 46 | 47 | 48 | 61 | 70 | 71 | <% }) %> 72 | 73 |
#GrupoRelaçãoAções
<%= group.id %><%= group.name %> 49 | <% switch (group.role) { 50 | case 'owner' : %> 51 | Proprietário 52 | <% break 53 | case 'pending' : %> 54 | Pendente 55 | <% break 56 | case 'user' : %> 57 | Usuário 58 | <% break 59 | } %> 60 | 62 | Ver 63 | <% if(group.role === 'owner') { %> 64 | Excluir 65 | <% } %> 66 | <% if(!group.role) { %> 67 | Participar 68 | <% } %> 69 |
74 | <% }else{ %> 75 |

Nenhum Grupo Encontrado.

76 | <% } %> 77 |
78 |
79 | 80 | 81 | <% include _partials/painel/footer %> 82 | 83 | 84 | -------------------------------------------------------------------------------- /public/sass/mdb/free/_typography.scss: -------------------------------------------------------------------------------- 1 | // Typography 2 | // ROBOTO FONT 3 | @font-face { 4 | font-family: "Roboto"; 5 | src: local(Roboto Thin), 6 | url('#{$roboto-font-path}Roboto-Thin.eot'); 7 | src: url("#{$roboto-font-path}Roboto-Thin.eot?#iefix") format('embedded-opentype'), 8 | url("#{$roboto-font-path}Roboto-Thin.woff2") format("woff2"), 9 | url("#{$roboto-font-path}Roboto-Thin.woff") format("woff"), 10 | url("#{$roboto-font-path}Roboto-Thin.ttf") format("truetype"); 11 | font-weight: 200; 12 | } 13 | 14 | @font-face { 15 | font-family: "Roboto"; 16 | src: local(Roboto Light), 17 | url('#{$roboto-font-path}Roboto-Light.eot'); 18 | src: url("#{$roboto-font-path}Roboto-Light.eot?#iefix") format('embedded-opentype'), 19 | url("#{$roboto-font-path}Roboto-Light.woff2") format("woff2"), 20 | url("#{$roboto-font-path}Roboto-Light.woff") format("woff"), 21 | url("#{$roboto-font-path}Roboto-Light.ttf") format("truetype"); 22 | font-weight: 300; 23 | } 24 | 25 | @font-face { 26 | font-family: "Roboto"; 27 | src: local(Roboto Regular), 28 | url('#{$roboto-font-path}Roboto-Regular.eot'); 29 | src: url("#{$roboto-font-path}Roboto-Regular.eot?#iefix") format('embedded-opentype'), 30 | url("#{$roboto-font-path}Roboto-Regular.woff2") format("woff2"), 31 | url("#{$roboto-font-path}Roboto-Regular.woff") format("woff"), 32 | url("#{$roboto-font-path}Roboto-Regular.ttf") format("truetype"); 33 | font-weight: 400; 34 | } 35 | 36 | @font-face { 37 | font-family: "Roboto"; 38 | src: url('#{$roboto-font-path}Roboto-Medium.eot'); 39 | src: url("#{$roboto-font-path}Roboto-Medium.eot?#iefix") format('embedded-opentype'), 40 | url("#{$roboto-font-path}Roboto-Medium.woff2") format("woff2"), 41 | url("#{$roboto-font-path}Roboto-Medium.woff") format("woff"), 42 | url("#{$roboto-font-path}Roboto-Medium.ttf") format("truetype"); 43 | font-weight: 500; 44 | } 45 | 46 | @font-face { 47 | font-family: "Roboto"; 48 | src: url('#{$roboto-font-path}Roboto-Bold.eot'); 49 | src: url("#{$roboto-font-path}Roboto-Bold.eot?#iefix") format('embedded-opentype'), 50 | url("#{$roboto-font-path}Roboto-Bold.woff2") format("woff2"), 51 | url("#{$roboto-font-path}Roboto-Bold.woff") format("woff"), 52 | url("#{$roboto-font-path}Roboto-Bold.ttf") format("truetype"); 53 | font-weight: 700; 54 | } 55 | 56 | body { 57 | font-family: 'Roboto', sans-serif; 58 | font-weight: 300; 59 | } 60 | 61 | h1, 62 | h2, 63 | h3, 64 | h4, 65 | h5, 66 | h6 { 67 | font-weight: 300; 68 | } 69 | 70 | @each $key, $val in $grid-breakpoints { 71 | @include media-breakpoint-up($key) { 72 | $y: map-get($responsive-headings, $key); 73 | @each $name, 74 | $value in $y { 75 | .#{$name}-responsive { 76 | font-size: $value; 77 | } 78 | } 79 | } 80 | } 81 | 82 | @each $size, $length in $spacers { 83 | .fs-#{$size} { 84 | font-size: $length !important; 85 | } 86 | } 87 | 88 | .divider-new { 89 | display: flex; 90 | flex-direction: row; 91 | justify-content: center; 92 | align-items: center; 93 | font-weight: 300; 94 | margin-top: 45px; 95 | margin-bottom: 45px; 96 | h2 { 97 | margin-top: 5px; 98 | } 99 | &:before { 100 | content: ''; 101 | height: 1.5px; 102 | background: #c6c6c6; 103 | flex: 1; 104 | margin: 0 .45em 0 0; 105 | } 106 | &:after { 107 | content: ''; 108 | height: 1.5px; 109 | background: #c6c6c6; 110 | flex: 1; 111 | margin: 0 0 0 .45em; 112 | } 113 | } 114 | 115 | blockquote { 116 | padding: .5rem 1rem; 117 | font-size: 1.25rem; 118 | border-left: .25rem solid #eceeef; 119 | p { 120 | font-size: 0.9rem; 121 | padding-left: 2rem; 122 | padding-top: 1rem; 123 | padding-bottom: 1rem; 124 | } 125 | } -------------------------------------------------------------------------------- /public/sass/mdb/free/_helpers.scss: -------------------------------------------------------------------------------- 1 | // Margin and Padding 2 | 3 | @each $breakpoint in map-keys($grid-breakpoints) { 4 | @include media-breakpoint-up($breakpoint) { 5 | $infix: breakpoint-infix($breakpoint, $grid-breakpoints); 6 | 7 | @each $prop, $abbrev in (margin: m, padding: p) { 8 | @each $size, $length in $spacers { 9 | 10 | .#{$abbrev}#{$infix}-#{$size} { #{$prop}: $length !important; } 11 | .#{$abbrev}t#{$infix}-#{$size} { #{$prop}-top: $length !important; } 12 | .#{$abbrev}r#{$infix}-#{$size} { #{$prop}-right: $length !important; } 13 | .#{$abbrev}b#{$infix}-#{$size} { #{$prop}-bottom: $length !important; } 14 | .#{$abbrev}l#{$infix}-#{$size} { #{$prop}-left: $length !important; } 15 | .#{$abbrev}x#{$infix}-#{$size} { 16 | #{$prop}-right: $length !important; 17 | #{$prop}-left: $length !important; 18 | } 19 | .#{$abbrev}y#{$infix}-#{$size} { 20 | #{$prop}-top: $length !important; 21 | #{$prop}-bottom: $length !important; 22 | } 23 | } 24 | } 25 | 26 | // Some special margin utils 27 | .m#{$infix}-auto { margin: auto !important; } 28 | .mt#{$infix}-auto { margin-top: auto !important; } 29 | .mr#{$infix}-auto { margin-right: auto !important; } 30 | .mb#{$infix}-auto { margin-bottom: auto !important; } 31 | .ml#{$infix}-auto { margin-left: auto !important; } 32 | .mx#{$infix}-auto { 33 | margin-right: auto !important; 34 | margin-left: auto !important; 35 | } 36 | .my#{$infix}-auto { 37 | margin-top: auto !important; 38 | margin-bottom: auto !important; 39 | } 40 | } 41 | } 42 | 43 | @each $key, $val in $grid-breakpoints { 44 | @include media-breakpoint-up($key) { 45 | $y: map-get($extreme-padding, $key); 46 | .e-px { 47 | padding-left: $y; 48 | padding-right: $y; 49 | } 50 | } 51 | } 52 | 53 | .img-fluid { 54 | max-width: 100%; 55 | height:auto; 56 | } 57 | 58 | .inline-ul>li { 59 | display: inline; 60 | } 61 | 62 | .list-inline-div>div { 63 | display: inline-block; 64 | } 65 | 66 | // Center text on mobile 67 | .center-on-small-only { 68 | @media #{$medium-and-down} { 69 | text-align: center; 70 | .img-fluid { 71 | display: inline; 72 | } 73 | } 74 | } 75 | 76 | .flex-center { 77 | display: flex; 78 | justify-content: center; 79 | align-items: center; 80 | height: 100%; 81 | p { 82 | margin: 0; 83 | } 84 | ul { 85 | text-align: center; 86 | li { 87 | margin-bottom: 1rem; 88 | } 89 | } 90 | } 91 | 92 | .hidden-md-down { 93 | @media (max-width: $medium-screen){ 94 | display: none!important; 95 | } 96 | } 97 | 98 | .mb-r { 99 | @media (min-width: $medium-screen){ 100 | margin-bottom: 3rem!important; 101 | } 102 | @media (max-width: $medium-screen){ 103 | margin-bottom: 2rem!important; 104 | } 105 | } 106 | 107 | .font-bold { 108 | font-weight: 500; 109 | } 110 | 111 | .font-up { 112 | text-transform: uppercase; 113 | } 114 | 115 | // Dividers light and dark variations 116 | .hr-light { 117 | border-top: 1px solid #fff; 118 | } 119 | 120 | .hr-dark { 121 | border-top: 1px solid #666; 122 | } 123 | 124 | // Blockquote contextual 125 | .blockquote { 126 | .bq-title { 127 | font-weight: 400; 128 | font-size: 1.5rem; 129 | margin-bottom: 0; 130 | } 131 | p { 132 | font-size: 1.1rem; 133 | } 134 | } 135 | 136 | .bq-primary { 137 | border-left: 3px solid $primary-color; 138 | .bq-title { 139 | color: $primary-color; 140 | } 141 | } 142 | 143 | .bq-warning { 144 | border-left: 3px solid $warning-color; 145 | .bq-title { 146 | color: $warning-color; 147 | } 148 | } 149 | 150 | .bq-danger { 151 | border-left: 3px solid $danger-color; 152 | .bq-title { 153 | color: $danger-color; 154 | } 155 | } 156 | 157 | .bq-success { 158 | border-left: 3px solid $success-color; 159 | .bq-title { 160 | color: $success-color; 161 | } 162 | } -------------------------------------------------------------------------------- /views/admin/games.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <% include ../_partials/painel/header %> 7 | 8 | 9 | 10 |
11 | 12 | <% include ../_partials/navbar %> 13 |
14 | 15 |
16 |
17 |

Gerenciamento de Jogos

18 |
19 |
20 | 21 | 22 | 23 |
24 | 25 |
26 | 27 | 28 | 29 |
30 |
31 | 32 |
33 |
34 |
35 |
36 | 37 |
38 |
39 |
40 | <% if (games.length !== 0) { %> 41 |

Resultados dos Jogos

42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | <% games.forEach( game => { %> 52 | 53 | 54 | 63 | 66 | 67 | <% }) %> 68 | 69 |
#JogosAções
<%= game.id %> 55 |
56 | <%= game.team_a %> 57 | 58 | vs 59 | 60 | <%= game.team_b %> 61 |
62 |
64 | Excluir 65 |
70 |
71 | 72 |
73 |
74 | <% } else { %> 75 |

Nenhum Jogo Encontrado.

76 | <% } %> 77 |
78 |
79 | 80 | 81 | <% include ../_partials/painel/footer %> 82 | 83 | 84 | -------------------------------------------------------------------------------- /public/sass/mdb/free/_waves.scss: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Waves v0.7.5 4 | * http://fian.my.id/Waves 5 | * 6 | * Copyright 2014-2016 Alfiana E. Sibuea and other contributors 7 | * Released under the MIT license 8 | * https://github.com/fians/Waves/blob/master/LICENSE 9 | */ 10 | 11 | @mixin waves-transition($transition){ 12 | -webkit-transition: $transition; 13 | -moz-transition: $transition; 14 | -o-transition: $transition; 15 | transition: $transition; 16 | } 17 | 18 | @mixin waves-transform($string){ 19 | -webkit-transform: $string; 20 | -moz-transform: $string; 21 | -ms-transform: $string; 22 | -o-transform: $string; 23 | transform: $string; 24 | } 25 | 26 | @mixin waves-box-shadow($shadow){ 27 | -webkit-box-shadow: $shadow; 28 | box-shadow: $shadow; 29 | } 30 | 31 | .waves-effect { 32 | position: relative; 33 | cursor: pointer; 34 | overflow: hidden; 35 | user-select: none; 36 | -webkit-tap-highlight-color: transparent; 37 | z-index: 1; 38 | .waves-ripple { 39 | position: absolute; 40 | border-radius: 50%; 41 | width: 100px; 42 | height: 100px; 43 | margin-top:-50px; 44 | margin-left:-50px; 45 | opacity: 0; 46 | background: rgba(0,0,0,0.2); 47 | $gradient: rgba(0,0,0,0.2) 0,rgba(0,0,0,.3) 40%,rgba(0,0,0,.4) 50%,rgba(0,0,0,.5) 60%,rgba(255,255,255,0) 70%; 48 | background: -webkit-radial-gradient($gradient); 49 | background: -o-radial-gradient($gradient); 50 | background: -moz-radial-gradient($gradient); 51 | background: radial-gradient($gradient); 52 | @include waves-transition(all 0.5s ease-out); 53 | transition-property: transform, opacity; 54 | @include waves-transform(scale(0) translate(0,0)); 55 | pointer-events: none; 56 | } 57 | 58 | &.waves-light .waves-ripple { 59 | background: rgba(255,255,255,0.4); 60 | $gradient: rgba(255,255,255,0.2) 0,rgba(255,255,255,.3) 40%,rgba(255,255,255,.4) 50%,rgba(255,255,255,.5) 60%,rgba(255,255,255,0) 70%; 61 | background: -webkit-radial-gradient($gradient); 62 | background: -o-radial-gradient($gradient); 63 | background: -moz-radial-gradient($gradient); 64 | background: radial-gradient($gradient); 65 | } 66 | 67 | &.waves-classic .waves-ripple { 68 | background: rgba(0,0,0,0.2); 69 | } 70 | 71 | &.waves-classic.waves-light .waves-ripple { 72 | background: rgba(255,255,255,0.4); 73 | } 74 | } 75 | 76 | .waves-notransition { 77 | @include waves-transition(none #{"!important"}); 78 | } 79 | 80 | .waves-button, 81 | .waves-circle { 82 | @include waves-transform(translateZ(0)); 83 | -webkit-mask-image: -webkit-radial-gradient(circle, white 100%, black 100%); 84 | } 85 | 86 | .waves-button, 87 | .waves-button:hover, 88 | .waves-button:visited, 89 | .waves-button-input { 90 | white-space: nowrap; 91 | vertical-align: middle; 92 | cursor: pointer; 93 | border: none; 94 | outline: none; 95 | color: inherit; 96 | background-color: rgba(0, 0, 0, 0); 97 | font-size: 1em; 98 | line-height:1em; 99 | text-align: center; 100 | text-decoration: none; 101 | z-index: 1; 102 | } 103 | 104 | .waves-button { 105 | padding: 0.85em 1.1em; 106 | border-radius: 0.2em; 107 | } 108 | 109 | .waves-button-input { 110 | margin: 0; 111 | padding: 0.85em 1.1em; 112 | } 113 | 114 | .waves-input-wrapper { 115 | border-radius: 0.2em; 116 | vertical-align: middle; 117 | display:inline-block; 118 | &.waves-button { 119 | padding: 0; 120 | } 121 | 122 | .waves-button-input { 123 | position: relative; 124 | top: 0; 125 | left: 0; 126 | z-index: 1; 127 | } 128 | } 129 | 130 | .waves-circle { 131 | text-align: center; 132 | width: 2.5em; 133 | height: 2.5em; 134 | line-height: 2.5em; 135 | border-radius: 50%; 136 | } 137 | 138 | .waves-float { 139 | -webkit-mask-image: none; 140 | @include waves-box-shadow(0px 1px 1.5px 1px rgba(0, 0, 0, 0.12)); 141 | @include waves-transition(all 300ms); 142 | 143 | &:active { 144 | @include waves-box-shadow(0px 8px 20px 1px rgba(0, 0, 0, 0.30)); 145 | } 146 | } 147 | 148 | .waves-block { 149 | display: block; 150 | } 151 | 152 | a { 153 | &.waves-effect, 154 | &.waves-light { 155 | display:inline-block; 156 | } 157 | } -------------------------------------------------------------------------------- /admin.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const route = express.Router() 3 | const encRedisClient = require('./cache/setup') 4 | 5 | const init = connection => { 6 | route.get('/', (req, res) => { 7 | res.send('Olá Administrador') 8 | }) 9 | 10 | route.get('/games', async (req, res) => { 11 | const [rows, fields] = await connection.execute('SELECT games.team_a, games.team_b, games.id, games.result_a, games.result_b FROM `games`') 12 | 13 | res.render('admin/games', { 14 | games: rows 15 | }) 16 | }) 17 | 18 | route.post('/games', async (req, res) => { 19 | const { 20 | team_a: teamA, 21 | team_b: teamB 22 | } = req.body 23 | await connection.execute('INSERT INTO `games` (games.team_a, games.team_b) VALUES (?, ?)', [ 24 | teamA, 25 | teamB 26 | ]) 27 | 28 | res.redirect('/admin/games') 29 | }) 30 | 31 | route.post('/games/results', async (req, res) => { 32 | const games = [] 33 | Object 34 | .keys(req.body) 35 | .forEach(game => { 36 | const parts = game.split('_') 37 | const results = { 38 | game_id: parseInt(parts[1]), 39 | result_a: parseInt(req.body[game].team_a), 40 | result_b: parseInt(req.body[game].team_b) 41 | } 42 | games.push(results) 43 | }) 44 | 45 | for (let i = 0; i < games.length; i++) { 46 | const game = games[i] 47 | 48 | await connection.execute('UPDATE `games` SET games.result_a = ?, games.result_b = ? WHERE games.id = ?', [ 49 | game.result_a, 50 | game.result_b, 51 | game.game_id 52 | ]) 53 | 54 | const [guessings] = await connection.execute('SELECT guessings.id, guessings.game_id, guessings.group_id, guessings.user_id, guessings.result_a, guessings.result_b, guessings.score FROM `guessings` WHERE guessings.game_id = ?', [ 55 | game.game_id 56 | ]) 57 | 58 | const batch = guessings.map(guess => { 59 | let score = 0 60 | 61 | if (guess.result_a === game.result_a && guess.result_b === game.result_b) { 62 | score = 100 63 | } else if (guess.result_a === game.result_a || guess.result_b === game.result_b) { 64 | score += 25 65 | if (guess.result_a < guess.result_b && game.result_a < game.result_b) { 66 | score += 25 67 | } else if (guess.result_a > guess.result_b && game.result_a > game.result_b) { 68 | score += 25 69 | } 70 | } else if ((guess.result_a < guess.result_b && game.result_a < game.result_b) || (guess.result_a > guess.result_b && game.result_a > game.result_b)) { 71 | score += 25 72 | } 73 | 74 | return connection.execute('UPDATE `guessings` SET guessings.score = ? WHERE guessings.id = ?', [ 75 | score, 76 | guess.id 77 | ]) 78 | }) 79 | 80 | await Promise.all(batch) 81 | 82 | const [groups] = await connection.execute('SELECT groups.id, groups.name, SUM(guessings.score) as score FROM `groups` LEFT JOIN guessings ON guessings.group_id = groups.id GROUP BY groups.id ORDER BY score DESC') 83 | await encRedisClient.setexAsync('groupsRanking', 84600, groups, (err) => { 84 | if (err) { 85 | console.log(err.message) 86 | } 87 | }) 88 | 89 | const [users] = await connection.execute('SELECT users.id, users.name, SUM(guessings.score) as score FROM `users` LEFT JOIN guessings on guessings.user_id = users.id GROUP BY users.id ORDER BY score DESC') 90 | await encRedisClient.setexAsync('usersRanking', 84600, users, (err) => { 91 | if (err) { 92 | console.log(err.message) 93 | } 94 | }) 95 | 96 | await encRedisClient.setAsync('invalidatedCache', true, (err) => { 97 | if (err) { 98 | console.log(err.message) 99 | } 100 | }) 101 | } 102 | 103 | res.redirect('/admin/games') 104 | }) 105 | 106 | route.get('/games/delete/:id', async (req, res) => { 107 | await connection.execute('DELETE FROM `games` WHERE games.id = ? LIMIT 1', [ 108 | req.params.id 109 | ]) 110 | 111 | res.redirect('/admin/games') 112 | }) 113 | 114 | return route 115 | } 116 | 117 | module.exports = init 118 | -------------------------------------------------------------------------------- /account.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const route = express.Router() 3 | const crypto = require('./utils/crypto') 4 | 5 | const init = connection => { 6 | route.get('/sign-in', (req, res) => { 7 | res.render('account/sign-in') 8 | }) 9 | 10 | route.post('/sign-in', async (req, res) => { 11 | const { 12 | email, 13 | passwd 14 | } = req.body 15 | 16 | const [rows, fields] = await connection.execute('SELECT users.id, users.name, users.email, users.passwd, users.`role` FROM `users` WHERE users.email = ?', [email]) 17 | if (rows.length !== 0 && rows[0].passwd === crypto(passwd)) { 18 | const userDb = rows[0] 19 | const user = { 20 | id: userDb.id, 21 | name: userDb.name, 22 | role: userDb.role 23 | } 24 | req.session.user = user 25 | 26 | res.redirect('/') 27 | } else { 28 | res.render('account/sign-in', { 29 | error: 'Usuário e/ou senha inválidos.', 30 | email: email, 31 | passwd: passwd 32 | }) 33 | } 34 | }) 35 | 36 | route.get('/sign-out', (req, res) => { 37 | req.session.destroy(err => { 38 | if (err) { 39 | console.log(`Error Destroying Session\nError: ${err.code}\nMessage ${err.message}`) 40 | } 41 | 42 | res.redirect('/') 43 | }) 44 | }) 45 | 46 | route.get('/new-account', (req, res) => { 47 | res.render('account/new') 48 | }) 49 | 50 | route.post('/new-account', async (req, res) => { 51 | const { 52 | name, 53 | email, 54 | passwd 55 | } = req.body 56 | 57 | const [rows, fields] = await connection.execute('SELECT users.id, users.name, users.email, users.passwd, users.`role` FROM `users` WHERE users.email = ?', [email]) 58 | if (rows.length === 0) { 59 | const [resultSetHeader, resultSet] = await connection.execute('INSERT INTO `users` (users.name, users.email, users.passwd, users.role) VALUES (?, ?, ?, ?)', [ 60 | name, 61 | email, 62 | crypto(passwd), 63 | 'user' 64 | ]) 65 | 66 | const user = { 67 | id: resultSetHeader.insertId, 68 | name: name, 69 | role: 'user' 70 | } 71 | req.session.user = user 72 | 73 | res.redirect('/') 74 | } else { 75 | res.render('account/new', { 76 | error: 'Usuário já existente.', 77 | name: name, 78 | email: email, 79 | passwd: passwd 80 | }) 81 | } 82 | }) 83 | 84 | route.get('/profile', async (req, res) => { 85 | if (req.session.user) { 86 | const userId = req.session.user.id 87 | const [rows, fields] = await connection.execute('SELECT users.id, users.name, users.email FROM `users` WHERE id = ?', [userId]) 88 | 89 | if (rows.length > 0) { 90 | res.render('account/profile', { 91 | id: rows[0].id, 92 | name: rows[0].name, 93 | email: rows[0].email 94 | }) 95 | } else { 96 | req.session.destroy(err => { 97 | if (err) { 98 | console.log(`Error Destroying Session\nError: ${err.code}\nMessage ${err.message}`) 99 | } 100 | 101 | res.redirect('/sign-in') 102 | }) 103 | } 104 | } else { 105 | res.redirect('/sign-in') 106 | } 107 | }) 108 | 109 | route.post('/profile/:id', async (req, res) => { 110 | const [rows, fields] = await connection.execute('SELECT users.id, users.name, users.email, users.passwd, users.`role` FROM `users` WHERE id = ? LIMIT 1', [req.params.id]) 111 | 112 | const { 113 | name, 114 | email, 115 | passwd 116 | } = req.body 117 | 118 | if (rows.length > 0) { 119 | await connection.execute('UPDATE `users` SET users.name = ?, users.email = ?, users.passwd = ? WHERE users.id = ?', [ 120 | name, 121 | email, 122 | crypto(passwd), 123 | req.params.id 124 | ]) 125 | 126 | const user = { 127 | id: rows[0].id, 128 | name: name, 129 | role: rows[0].role 130 | } 131 | req.session.user = user 132 | 133 | res.redirect('/') 134 | } else { 135 | res.redirect('/sign-in') 136 | } 137 | }) 138 | 139 | return route 140 | } 141 | 142 | module.exports = init 143 | -------------------------------------------------------------------------------- /views/ranking.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <% include _partials/painel/header %> 7 | 8 | 9 | 10 |
11 | 12 | <% include _partials/navbar %> 13 |
14 | 15 |
16 |
17 |

Classificações

18 | <% if (groupsRanking.length !== 0 && usersRanking.length !== 0) { %> 19 | 27 |
28 |
29 | <% if (usersRanking && usersRanking.length > 0) { %> 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | <% groupsRanking.forEach( groupRanking => { %> 40 | 41 | 42 | 43 | 44 | 45 | <% }) %> 46 | 47 |
#GrupoPontuação
<%= groupRanking.id %><%= groupRanking.name %><%= groupRanking.score ? groupRanking.score : 0 %>
48 | <% } %> 49 |
50 |
51 | <% if (usersRanking && usersRanking.length > 0) { %> 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | <% usersRanking.forEach( userRanking => { %> 62 | 63 | 64 | 65 | 66 | 67 | <% }) %> 68 | 69 |
#UsuárioPontuação
<%= userRanking.id %><%= userRanking.name %><%= userRanking.score ? userRanking.score : 0 %>
70 | <% } %> 71 |
72 |
73 | <% } else { %> 74 |

Nenhum Resultado foi Encontrado.

75 | <% } %> 76 |
77 |
78 | 79 | 80 | <% include _partials/painel/footer %> 81 | 82 | 83 | -------------------------------------------------------------------------------- /public/sass/mdb/free/_navbar.scss: -------------------------------------------------------------------------------- 1 | // Navbar 2 | .navbar { 3 | font-weight: $font-weight; 4 | padding-right: 1rem !important; 5 | form { 6 | input { 7 | margin: 0 $navbar-form-input-mr $navbar-form-input-mb $navbar-form-input-ml; 8 | height: $navbar-form-input-height; 9 | } 10 | } 11 | .navbar-brand { 12 | align-self: flex-start; 13 | overflow: visible; 14 | } 15 | .breadcrumb { 16 | margin: 0; 17 | background-color: inherit; 18 | font-weight: $font-weight; 19 | // padding-left: 1rem; 20 | font-size: $navbar-double-font-size; 21 | padding: 0.3em 0 0 1em; 22 | } 23 | .navbar-toggler { 24 | border-width: 0; 25 | } 26 | &.double-nav, 27 | .nav-flex-icons { 28 | flex-direction: row; 29 | } 30 | .container { 31 | @media (max-width: $medium-screen) { 32 | width: 100%; 33 | .navbar-toggler-right { 34 | right: 0; 35 | } 36 | } 37 | } 38 | &.navbar-dark { 39 | .navbar-nav { 40 | .nav-item { 41 | .nav-link.disabled, .nav-link.disabled:hover { 42 | color: rgba(255, 255, 255, 0.5); 43 | } 44 | } 45 | } 46 | } 47 | &.navbar-light { 48 | .navbar-nav { 49 | .nav-item { 50 | .nav-link.disabled, .nav-link.disabled:hover { 51 | color: rgba(0, 0, 0, 0.5); 52 | } 53 | } 54 | } 55 | } 56 | .nav-item { 57 | .nav-link { 58 | display:block; 59 | &.disabled { 60 | &:active { 61 | pointer-events:none; 62 | } 63 | } 64 | .fa { 65 | padding-left: $navbar-flex-icons-padding-lg; 66 | padding-right: $navbar-flex-icons-padding-lg; 67 | } 68 | @media (max-width: $medium-screen) { 69 | padding-left: $navbar-flex-icons-padding-md; 70 | padding-right: $navbar-flex-icons-padding-md; 71 | } 72 | } 73 | } 74 | .dropdown-menu { 75 | position: absolute !important; 76 | margin-top:0; 77 | a { 78 | font-size: $navbar-dropdown-font-size; 79 | font-weight: $font-weight; 80 | padding: $navbar-dropdown-menu-padding; 81 | color: $black !important; 82 | &:hover { 83 | color: $white !important; 84 | } 85 | } 86 | &.dropdown-menu-right { 87 | @media (max-width: $small-screen) { 88 | right: auto !important; 89 | } 90 | } 91 | } 92 | @each $name, $data in $navbar-option { 93 | &.navbar-#{$name} { 94 | .navbar-toggler-icon { 95 | background-image: map-get($data, "navbar-toggler-icon"); 96 | cursor: pointer; 97 | } 98 | .breadcrumb, 99 | .navbar-nav { 100 | .nav-item { 101 | .nav-link { 102 | color: map-get($data, "navbar-option-color"); 103 | @include transition(.35s); 104 | &:hover { 105 | color: map-get($data, "navbar-link-hover-color"); 106 | } 107 | } 108 | &.active > .nav-link { 109 | background-color: map-get($data, "navbar-active-link-bg-color"); 110 | &:hover { 111 | color: map-get($data, "navbar-option-color"); 112 | } 113 | } 114 | } 115 | } 116 | .navbar-toggler { 117 | color: map-get($data, "navbar-option-color"); 118 | } 119 | form { 120 | input[type=text] { 121 | border-bottom: 1px solid map-get($data, "navbar-option-color"); 122 | &:focus:not([readonly]) { 123 | border-color: $input-focus-color; 124 | } 125 | } 126 | .form-control { 127 | color: map-get($data, "navbar-option-color"); 128 | @include placeholder { 129 | color: map-get($data, "navbar-option-color"); 130 | font-weight: $font-weight; 131 | } 132 | } 133 | } 134 | } 135 | } 136 | &.scrolling-navbar { 137 | z-index: 100; 138 | @media (min-width: $small-screen) { 139 | @include transition (background .5s ease-in-out, padding .5s ease-in-out); 140 | padding-top: $navbar-scrolling-padding; 141 | padding-bottom: $navbar-scrolling-padding; 142 | .navbar-nav > li { 143 | transition-duration: 1s; 144 | } 145 | &.top-nav-collapse { 146 | padding-top: $navbar-top-collapse-padding; 147 | padding-bottom: $navbar-top-collapse-padding; 148 | } 149 | } 150 | } 151 | } 152 | 153 | .intro-margin { 154 | @media (min-width: $small-screen) { 155 | &.view { 156 | overflow: visible; 157 | margin-top: -56px; 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /support_files/db_scheema/futibaclub.sql: -------------------------------------------------------------------------------- 1 | -- ----------------------------------------------------- 2 | -- Table `vhxa9e9tjj60dtyr`.`users` 3 | -- ----------------------------------------------------- 4 | CREATE TABLE IF NOT EXISTS `vhxa9e9tjj60dtyr`.`users` ( 5 | `id` INT NOT NULL AUTO_INCREMENT, 6 | `name` VARCHAR(245) NULL, 7 | `email` VARCHAR(255) NULL, 8 | `passwd` VARCHAR(255) NULL, 9 | `role` VARCHAR(45) NULL, 10 | PRIMARY KEY (`id`)) 11 | ENGINE = InnoDB; 12 | 13 | 14 | -- ----------------------------------------------------- 15 | -- Table `vhxa9e9tjj60dtyr`.`groups` 16 | -- ----------------------------------------------------- 17 | CREATE TABLE IF NOT EXISTS `vhxa9e9tjj60dtyr`.`groups` ( 18 | `id` INT NOT NULL AUTO_INCREMENT, 19 | `name` VARCHAR(245) NULL, 20 | PRIMARY KEY (`id`)) 21 | ENGINE = InnoDB; 22 | 23 | 24 | -- ----------------------------------------------------- 25 | -- Table `vhxa9e9tjj60dtyr`.`groups_users` 26 | -- ----------------------------------------------------- 27 | CREATE TABLE IF NOT EXISTS `vhxa9e9tjj60dtyr`.`groups_users` ( 28 | `id` INT NOT NULL AUTO_INCREMENT, 29 | `user_id` INT NOT NULL, 30 | `group_id` INT NOT NULL, 31 | `role` VARCHAR(45) NULL, 32 | PRIMARY KEY (`id`), 33 | INDEX `fk_groups_users_users_idx` (`user_id` ASC), 34 | INDEX `fk_groups_users_groups1_idx` (`group_id` ASC)) 35 | ENGINE = InnoDB; 36 | 37 | -- ----------------------------------------------------- 38 | -- Table `vhxa9e9tjj60dtyr`.`games` 39 | -- ----------------------------------------------------- 40 | CREATE TABLE IF NOT EXISTS `vhxa9e9tjj60dtyr`.`games` ( 41 | `id` INT NOT NULL AUTO_INCREMENT, 42 | `team_a` VARCHAR(245) NULL, 43 | `team_b` VARCHAR(245) NULL, 44 | `result_a` INT NULL, 45 | `result_b` INT NULL, 46 | PRIMARY KEY (`id`)) 47 | ENGINE = InnoDB; 48 | 49 | 50 | -- ----------------------------------------------------- 51 | -- Table `vhxa9e9tjj60dtyr`.`guessings` 52 | -- ----------------------------------------------------- 53 | CREATE TABLE IF NOT EXISTS `vhxa9e9tjj60dtyr`.`guessings` ( 54 | `id` INT NOT NULL AUTO_INCREMENT, 55 | `result_a` INT NULL, 56 | `result_b` INT NULL, 57 | `score` INT NULL, 58 | `game_id` INT NOT NULL, 59 | `group_id` INT NOT NULL, 60 | `user_id` INT NOT NULL, 61 | PRIMARY KEY (`id`), 62 | INDEX `fk_guessings_games1_idx` (`game_id` ASC), 63 | INDEX `fk_guessings_groups1_idx` (`group_id` ASC), 64 | INDEX `fk_guessings_users1_idx` (`user_id` ASC)) 65 | ENGINE = InnoDB; 66 | 67 | 68 | -- ----------------------------------------------------- 69 | -- Table `vhxa9e9tjj60dtyr`.`comments` 70 | -- ----------------------------------------------------- 71 | CREATE TABLE IF NOT EXISTS `vhxa9e9tjj60dtyr`.`comments` ( 72 | `id` INT NOT NULL AUTO_INCREMENT, 73 | `comment` TEXT NULL, 74 | `guessing_id` INT NOT NULL, 75 | `user_id` INT NOT NULL, 76 | PRIMARY KEY (`id`), 77 | INDEX `fk_comments_guessings1_idx` (`guessing_id` ASC), 78 | INDEX `fk_comments_users1_idx` (`user_id` ASC)) 79 | ENGINE = InnoDB; 80 | 81 | -- ----------------------------------------------------- 82 | -- Constrains 83 | -- ----------------------------------------------------- 84 | ALTER TABLE `vhxa9e9tjj60dtyr`.`groups_users` 85 | ADD CONSTRAINT `fk_groups_users_users` 86 | FOREIGN KEY (`user_id`) 87 | REFERENCES `vhxa9e9tjj60dtyr`.`users` (`id`) 88 | ON DELETE CASCADE 89 | ON UPDATE CASCADE; 90 | 91 | ALTER TABLE `vhxa9e9tjj60dtyr`.`groups_users` 92 | ADD CONSTRAINT `fk_groups_users_groups` 93 | FOREIGN KEY (`group_id`) 94 | REFERENCES `vhxa9e9tjj60dtyr`.`groups` (`id`) 95 | ON DELETE CASCADE 96 | ON UPDATE CASCADE; 97 | 98 | 99 | ALTER TABLE `vhxa9e9tjj60dtyr`.`guessings` 100 | ADD CONSTRAINT `fk_guessings_games` 101 | FOREIGN KEY (`game_id`) 102 | REFERENCES `vhxa9e9tjj60dtyr`.`games` (`id`) 103 | ON DELETE CASCADE 104 | ON UPDATE CASCADE; 105 | 106 | ALTER TABLE `vhxa9e9tjj60dtyr`.`guessings` 107 | ADD CONSTRAINT `fk_guessings_groups` 108 | FOREIGN KEY (`group_id`) 109 | REFERENCES `vhxa9e9tjj60dtyr`.`groups` (`id`) 110 | ON DELETE CASCADE 111 | ON UPDATE CASCADE; 112 | 113 | ALTER TABLE `vhxa9e9tjj60dtyr`.`guessings` 114 | ADD CONSTRAINT `fk_guessings_users` 115 | FOREIGN KEY (`user_id`) 116 | REFERENCES `vhxa9e9tjj60dtyr`.`users` (`id`) 117 | ON DELETE CASCADE 118 | ON UPDATE CASCADE; 119 | 120 | ALTER TABLE `vhxa9e9tjj60dtyr`.`comments` 121 | ADD CONSTRAINT `fk_comments_guessings` 122 | FOREIGN KEY (`guessing_id`) 123 | REFERENCES `vhxa9e9tjj60dtyr`.`guessings` (`id`) 124 | ON DELETE CASCADE 125 | ON UPDATE CASCADE; 126 | 127 | ALTER TABLE `vhxa9e9tjj60dtyr`.`comments` 128 | ADD CONSTRAINT `fk_comments_users` 129 | FOREIGN KEY (`user_id`) 130 | REFERENCES `vhxa9e9tjj60dtyr`.`users` (`id`) 131 | ON DELETE CASCADE 132 | ON UPDATE CASCADE; -------------------------------------------------------------------------------- /views/home.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <% include _partials/page/header %> 7 | 8 | 9 | 10 |
11 | 12 | <% include _partials/navbar %> 13 | 14 | 15 |
16 |
17 |
18 |
19 |
20 |
21 | escudo 22 |

Desafie seus amigos

23 |
24 |
Quem consegue acertar mais jogos da Copa e do Brasileirão? 25 |
26 |
27 | <% if (!locals.user) { %> 28 | Criar conta 29 | <% } %> 30 | Ver classificação 31 | 32 |
33 |
34 |
35 | mock 36 |
37 |
38 |
39 |
40 |
41 |
42 | 43 |
44 |
45 | 46 |
47 |

Escreva para nós

48 | 49 |
50 | 51 | 52 | 53 |
54 | 55 |
56 | 57 | 58 | 59 |
60 | 61 |
62 | 63 | 70 |
71 | 72 |
73 | 74 | 75 | 76 |
77 | 78 | <% if(locals.error) { %> 79 | 83 | <% } else if (locals.previewUrl) { %> 84 | 88 | <% } %> 89 | 90 |
91 | 92 |
93 |
94 | 95 |
96 |
97 | 98 | 99 | <% include _partials/page/footer %> 100 | 101 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /groups.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const route = express.Router() 3 | 4 | const init = connection => { 5 | route.get('/', async (req, res) => { 6 | const [rows, fields] = await connection.execute('SELECT groups.id, groups.name, groups_users.role FROM `groups` LEFT JOIN groups_users ON groups.id = groups_users.group_id AND groups_users.user_id = ?', [ 7 | req.session.user.id 8 | ]) 9 | res.render('groups', { 10 | groups: rows 11 | }) 12 | }) 13 | 14 | route.post('/', async (req, res) => { 15 | const [resultSetHeader, resultSet] = await connection.execute('INSERT INTO `groups` (groups.name) VALUES (?)', [ 16 | req.body.name 17 | ]) 18 | 19 | await connection.execute('INSERT INTO `groups_users` (groups_users.group_id, groups_users.user_id, groups_users.role) VALUES (?, ?, ?)', [ 20 | resultSetHeader.insertId, 21 | req.session.user.id, 22 | 'owner' 23 | ]) 24 | 25 | res.redirect('/groups') 26 | }) 27 | 28 | route.get('/:id', async (req, res) => { 29 | const [groupRows, groupFields] = await connection.execute('SELECT groups.id, groups.name, groups_users.role FROM `groups` LEFT JOIN groups_users ON groups_users.group_id = groups.id AND groups_users.user_id = ? WHERE groups.id = ?', [ 30 | req.session.user.id, 31 | req.params.id 32 | ]) 33 | const [pendingRows, pendingFields] = await connection.execute("SELECT groups_users.id, groups_users.role, groups_users.user_id, groups_users.group_id, users.name FROM `groups_users` INNER JOIN users ON groups_users.user_id = users.id AND groups_users.group_id = ? AND groups_users.role LIKE 'pending'", [ 34 | req.params.id 35 | ]) 36 | const [gameRows, gameFields] = await connection.execute('SELECT games.id, games.result_a, games.result_b, games.team_a, games.team_b, guessings.result_a as guess_a, guessings.result_b as guess_b, guessings.score FROM `games` LEFT JOIN guessings ON games.id = guessings.game_id AND guessings.user_id = ? AND guessings.group_id = ?', [ 37 | req.session.user.id, 38 | req.params.id 39 | ]) 40 | 41 | res.render('group', { 42 | pendings: pendingRows, 43 | group: groupRows[0], 44 | games: gameRows 45 | }) 46 | }) 47 | 48 | route.post('/:id', async (req, res) => { 49 | const guessings = [] 50 | Object 51 | .keys(req.body) 52 | .forEach(game => { 53 | const parts = game.split('_') 54 | const guess = { 55 | game_id: parts[1], 56 | result_a: req.body[game].team_a, 57 | result_b: req.body[game].team_b 58 | } 59 | guessings.push(guess) 60 | }) 61 | 62 | const batch = guessings.map(guess => { 63 | return connection.execute('INSERT INTO `guessings` (guessings.result_a, guessings.result_b, guessings.game_id, guessings.group_id, guessings.user_id) VALUES (?, ?, ?, ?, ?)', [ 64 | guess.result_a, 65 | guess.result_b, 66 | guess.game_id, 67 | req.params.id, 68 | req.session.user.id 69 | ]) 70 | }) 71 | await Promise.all(batch) 72 | 73 | res.redirect('/groups/' + req.params.id) 74 | }) 75 | 76 | route.get('/:id/participate', async (req, res) => { 77 | const [rows, fields] = await connection.execute('SELECT groups_users.id, groups_users.role, groups_users.user_id, groups_users.group_id FROM `groups_users` WHERE user_id = ? AND group_id = ?', [ 78 | req.session.user.id, 79 | req.params.id 80 | ]) 81 | 82 | if (rows.length === 0) { 83 | const [resultSetHeader, resultSet] = await connection.execute('INSERT INTO `groups_users` (groups_users.group_id, groups_users.user_id, groups_users.role) VALUES (?, ?, ?)', [ 84 | req.params.id, 85 | req.session.user.id, 86 | 'pending' 87 | ]) 88 | } 89 | 90 | res.redirect('/groups') 91 | }) 92 | 93 | route.get('/:groupId/approval/:groupsUsersId/:op', async (req, res) => { 94 | const [rows, fields] = await connection.execute('SELECT groups.id, groups.name, groups_users.role FROM `groups` LEFT JOIN groups_users ON groups_users.group_id = groups.id AND groups_users.user_id = ? WHERE groups.id = ?', [ 95 | req.session.user.id, 96 | req.params.groupId 97 | ]) 98 | 99 | if (rows.length === 0 || rows[0].role !== 'owner') { 100 | res.redirect('/groups/' + req.params.groupId) 101 | } else { 102 | if (req.params.op === 'yes') { 103 | await connection.execute('UPDATE `groups_users` SET groups_users.role = "user" WHERE groups_users.id = ?', [ 104 | req.params.groupsUsersId 105 | ]) 106 | } else { 107 | await connection.execute('DELETE FROM `groups_users` WHERE groups_users.id = ?', [ 108 | req.params.groupsUsersId 109 | ]) 110 | } 111 | 112 | res.redirect('/groups/' + req.params.groupId) 113 | } 114 | }) 115 | 116 | route.get('/delete/:id', async (req, res) => { 117 | const [rows, fields] = await connection.execute('SELECT groups.id, groups.name, groups_users.role FROM `groups` LEFT JOIN groups_users ON groups_users.group_id = groups.id AND groups_users.user_id = ? WHERE groups.id = ?', [ 118 | req.session.user.id, 119 | req.params.id 120 | ]) 121 | 122 | if (rows.length > 0 || rows[0].role === 'owner') { 123 | await connection.execute('DELETE FROM `groups` WHERE groups.id = ?', [ 124 | req.params.id 125 | ]) 126 | } 127 | 128 | res.redirect('/groups') 129 | }) 130 | 131 | return route 132 | } 133 | 134 | module.exports = init 135 | -------------------------------------------------------------------------------- /public/sass/mdb/free/_forms-basic.scss: -------------------------------------------------------------------------------- 1 | // Forms basic 2 | // Text inputs 3 | input[type=text], 4 | input[type=password], 5 | input[type=email], 6 | input[type=url], 7 | input[type=time], 8 | input[type=date], 9 | input[type=datetime-local], 10 | input[type=tel], 11 | input[type=number], 12 | input[type=search-md], 13 | input[type=search], 14 | textarea.md-textarea { 15 | // General Styles 16 | background-color: transparent; 17 | border: none; 18 | border-bottom: 1px solid $input-border-color; 19 | border-radius: 0; 20 | outline: none; 21 | height: 2.1rem; 22 | width: 100%; 23 | font-size: $input-font-size; 24 | box-shadow: none; 25 | box-sizing: content-box; 26 | @include transition(all .3s); 27 | 28 | // Disabled & readonly 29 | &:disabled, &[readonly="readonly"] { 30 | color: $input-disabled-color; 31 | border-bottom: 1px dotted $input-disabled-color; 32 | background-color: transparent; 33 | + label { 34 | color: $input-disabled-color; 35 | background-color: transparent; 36 | } 37 | } 38 | // Focused input style 39 | &:focus:not([readonly]) { 40 | border-bottom: 1px solid $input-focus-color; 41 | box-shadow: 0 1px 0 0 $input-focus-color; 42 | // Focused label style 43 | + label { 44 | color: $input-focus-color; 45 | } 46 | } 47 | 48 | // Valid input style 49 | &.valid, &:focus.valid { 50 | border-bottom: 1px solid $input-success-color; 51 | box-shadow: 0 1px 0 0 $input-success-color; 52 | } 53 | 54 | &.valid + label:after, &:focus.valid + label:after { 55 | content: attr(data-success); 56 | color: $input-success-color; 57 | opacity: 1; 58 | } 59 | // Invalid input style 60 | &.invalid, &:focus.invalid { 61 | border-bottom: 1px solid $input-error-color; 62 | box-shadow: 0 1px 0 0 $input-error-color; 63 | } 64 | &.invalid + label:after, &:focus.invalid + label:after { 65 | content: attr(data-error); 66 | color: $input-error-color; 67 | opacity: 1; 68 | } 69 | // Form message shared styles 70 | + label:after { 71 | display: block; 72 | content: ""; 73 | position: absolute; 74 | top: 65px; 75 | opacity: 0; 76 | transition: .2s opacity ease-out, .2s color ease-out; 77 | } 78 | 79 | &.input-alternate { 80 | padding: 0 15px; 81 | height: 2.1rem; 82 | box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .2), 0 1px 1px 0 rgba(0, 0, 0, .14), 0 2px 1px -1px rgba(0, 0, 0, .12); 83 | font-size: 0.875rem; 84 | border-bottom: 0; 85 | transition: none !important; 86 | &:hover, 87 | &:focus { 88 | box-shadow: 0 3px 8px 0 rgba(0, 0, 0, .2), 0 0 0 1px rgba(0, 0, 0, .08) !important; 89 | border-bottom: 0; 90 | } 91 | } 92 | } 93 | 94 | // Input with label 95 | .form-control { 96 | padding: 0; 97 | padding-bottom: 0.6rem; 98 | padding-top: 0.5rem; 99 | font-size: 1rem; 100 | line-height: 1.5; 101 | background-color: transparent; 102 | background-image: none; 103 | border-radius: 0; 104 | margin-top: 0.2rem; 105 | margin-bottom: 1rem; 106 | &:focus { 107 | background: transparent; 108 | box-shadow: none; 109 | } 110 | &:disabled, 111 | &[readonly] { 112 | background-color: transparent; 113 | border-bottom: 1px solid #bdbdbd; 114 | } 115 | } 116 | // Input + label wrapper styles 117 | .md-form { 118 | position: relative; 119 | margin-bottom: 1.5rem; 120 | label { 121 | @include transition(.2s ease-out); 122 | color: #757575; 123 | position: absolute; 124 | top: 0.8rem; 125 | left: 0; 126 | font-size: 1rem; 127 | cursor: text; 128 | &.active { 129 | @include transform(translateY(-140%)); 130 | font-size: $label-font-size; 131 | } 132 | } 133 | // Icon 134 | .prefix { 135 | @include transition(color .2s); 136 | position: absolute; 137 | width: 3rem; 138 | font-size: 2rem; 139 | padding-top: .5rem; 140 | ~ input, ~ textarea { 141 | margin-left: 3rem; 142 | width: 92%; 143 | width: calc(100% - 3rem); 144 | } 145 | ~ label { 146 | margin-left: 3rem; 147 | } 148 | &.active { 149 | color: $input-focus-color; 150 | } 151 | } 152 | 153 | @media #{$medium-and-down} { 154 | .prefix ~ input { 155 | width: 86%; 156 | width: calc(100% - 3rem); 157 | } 158 | } 159 | @media #{$small-and-down} { 160 | .prefix ~ input { 161 | width: 80%; 162 | width: calc(100% - 3rem); 163 | } 164 | } 165 | .btn { 166 | margin-bottom: 1.5rem; 167 | } 168 | 169 | &.form-sm { 170 | input { 171 | padding-bottom: 0.2rem; 172 | padding-top: 0.2rem; 173 | font-size: 0.8rem; 174 | line-height: 0.5; 175 | } 176 | label { 177 | font-size: 0.9rem; 178 | } 179 | .prefix { 180 | font-size: 1.5rem; 181 | top: 0.4rem; 182 | } 183 | .prefix ~ input, .prefix ~ textarea, .prefix ~ label { 184 | margin-left: 2.2rem; 185 | } 186 | } 187 | 188 | &.input-group { 189 | padding-left: 1rem; 190 | @include placeholder { 191 | color: #999; 192 | padding-top: 2px; 193 | } 194 | } 195 | } 196 | 197 | .form-group { 198 | display:block; 199 | } 200 | 201 | .form-inline { 202 | fieldset { 203 | margin-right: 1.5rem; 204 | } 205 | .form-group { 206 | margin-right: 2rem; 207 | } 208 | } 209 | // Default textarea 210 | textarea { 211 | width: 100%; 212 | height: 3rem; 213 | background-color: transparent; 214 | &.md-textarea { 215 | overflow-y: hidden; 216 | padding: 1.6rem 0; 217 | resize: none; 218 | min-height: 3rem; 219 | } 220 | } 221 | 222 | // For textarea autoresize 223 | .hiddendiv { 224 | display: none; 225 | white-space: pre-wrap; 226 | word-wrap: break-word; 227 | overflow-wrap: break-word; 228 | padding-top: 1.2rem; 229 | } 230 | 231 | .orange-gradient{ 232 | background: linear-gradient(40deg, #FFD86F, #FC6262); 233 | } -------------------------------------------------------------------------------- /support_files/templates/grupos.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Futiba Club - Fullstack Academy - DevPleno 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 60 |
61 | 62 |
63 |
64 |
65 |
66 |

Entrar

67 |
68 | 69 | 70 | 71 |
72 | 73 |
74 | 75 | 76 | 77 |
78 | 79 |
80 | 81 |
82 |
83 |
84 |

Criar uma conta

85 | 86 |
87 | 88 | 89 | 90 |
91 |
92 | 93 | 94 | 95 |
96 | 97 |
98 | 99 | 100 | 101 |
102 | 103 |
104 | 105 |
106 |
107 |
108 |
109 |
110 | 111 | 112 | 113 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /views/group.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <% include _partials/painel/header %> 7 | 8 | 9 | 10 |
11 | 12 | <% include _partials/navbar %> 13 |
14 | 15 |
16 |
17 |

Grupo <%= group.name %>

18 | <% if (group.role === 'owner') { %> 19 | <% if (pendings.length > 0) { %> 20 |

Usuários Pendentes

21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | <% pendings.forEach( pending => { %> 32 | 33 | 34 | 35 | 48 | 52 | 53 | <% }) %> 54 | 55 |
#UsuárioRelaçãoAções
<%= pending.id %><%= pending.name %> 36 | <% switch (pending.role) { 37 | case 'owner' : %> 38 | Proprietário 39 | <% break 40 | case 'pending' : %> 41 | Pendente 42 | <% break 43 | case 'user' : %> 44 | Usuário 45 | <% break 46 | } %> 47 | 49 | Permitir 50 | Negar 51 |
56 | <% } %> 57 | <% } %> 58 | <% if (group.role === 'pending') { %> 59 |

Sua Aprovação Ainda Encontra-se Pendente.

60 | <% } else { %> 61 |

Palpites dos Jogos

62 | <% let disableds = 0 %> 63 |
64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | <% games.forEach( game => { %> 75 | <% if ((game.result_a !== null && game.result_b !== null) || (game.guess_a !== null && game.guess_b !== null)) { disableds += 1 } %> 76 | 77 | 78 | 95 | 96 | 97 | 98 | <% }) %> 99 | 100 |
#GameResultadoPontuação
<%= game.id %> 79 |
80 | <%= game.team_a %> 81 | <% if(game.guess_a || game.result_a) { %> 82 |

<%= game.guess_a %>

83 | <%} else { %> 84 | 85 | <% } %> 86 | vs 87 | <% if(game.guess_b || game.result_b) { %> 88 |

<%= game.guess_b %>

89 | <%} else { %> 90 | 91 | <% } %> 92 | <%= game.team_b %> 93 |
94 |
<%= (game.result_a !== null) ? game.result_a : '' %> x <%= (game.result_b !== null) ? game.result_b : '' %><%= (game.score !== null) ? game.score : 0 %>
101 |
102 | 103 |
104 |
105 | <% } %> 106 |
107 |
108 | 109 | 110 | <% include _partials/painel/footer %> 111 | 112 | 113 | -------------------------------------------------------------------------------- /public/sass/mdb/free/data/_mixins.scss: -------------------------------------------------------------------------------- 1 | /********************* 2 | Mixins 3 | **********************/ 4 | 5 | @mixin hover { 6 | @if $enable-hover-media-query { 7 | // See Media Queries Level 4: http://drafts.csswg.org/mediaqueries/#hover 8 | // Currently shimmed by https://github.com/twbs/mq4-hover-shim 9 | @media (hover: hover) { 10 | &:hover { 11 | @content 12 | } 13 | } 14 | } 15 | @else { 16 | &:hover { 17 | @content 18 | } 19 | } 20 | } 21 | 22 | @mixin hover-focus { 23 | @if $enable-hover-media-query { 24 | &:focus { 25 | @content 26 | } 27 | @include hover { 28 | @content 29 | } 30 | } 31 | @else { 32 | &:focus, 33 | &:hover { 34 | @content 35 | } 36 | } 37 | } 38 | 39 | @mixin plain-hover-focus { 40 | @if $enable-hover-media-query { 41 | &:focus { 42 | @content 43 | } 44 | @include hover { 45 | @content 46 | } 47 | } 48 | @else { 49 | &:focus, 50 | &:hover { 51 | @content 52 | } 53 | } 54 | } 55 | 56 | @mixin hover-focus-active { 57 | @if $enable-hover-media-query { 58 | &:focus, 59 | &:active { 60 | @content 61 | } 62 | @include hover { 63 | @content 64 | } 65 | } 66 | @else { 67 | &:focus, 68 | &:active, 69 | &:hover { 70 | @content 71 | } 72 | } 73 | } 74 | 75 | @mixin border-radius($args) { 76 | -webkit-border-radius: $args; 77 | -moz-border-radius: $args; 78 | -ms-border-radius: $args; 79 | -o-border-radius: $args; 80 | border-radius: $args; 81 | } 82 | 83 | @mixin placeholder { 84 | &::-webkit-input-placeholder { 85 | @content 86 | } 87 | &:-moz-placeholder { 88 | @content 89 | } 90 | &::-moz-placeholder { 91 | @content 92 | } 93 | &::-ms-placeholder { 94 | @content 95 | } 96 | &::placeholder { 97 | @content 98 | } 99 | } 100 | 101 | // New mixins 102 | @mixin make-button($name, $color) { 103 | .btn-#{$name} { 104 | @function set-notification-text-color($color) { 105 | @if (lightness($color) > 80) { 106 | @return $black; // Lighter backgorund, return dark color 107 | } @else { 108 | @return $white; // Darker background, return light color 109 | } 110 | } 111 | background-color: $color !important; 112 | color: set-notification-text-color($color) !important; 113 | &:hover { 114 | background-color: lighten($color, 5%); 115 | } 116 | &:focus, 117 | &.focus { 118 | @extend .z-depth-1-half; 119 | } 120 | &:focus, 121 | &:active, 122 | &.active { 123 | background-color: darken($color, 13%); 124 | } 125 | &.dropdown-toggle { 126 | background-color: $color!important; 127 | &:hover, 128 | &:focus { 129 | background-color: lighten($color, 5%) !important; 130 | } 131 | } 132 | &:not([disabled]):not(.disabled):active, 133 | &:not([disabled]):not(.disabled).active, 134 | .show > &.dropdown-toggle { 135 | @extend .z-depth-1-half; 136 | background-color: darken($color, 13%) !important; 137 | } 138 | &:not([disabled]):not(.disabled):active:focus, 139 | &:not([disabled]):not(.disabled).active:focus, 140 | .show > &.dropdown-toggle:focus { 141 | @extend .z-depth-1-half; 142 | } 143 | } 144 | } 145 | 146 | @mixin make-outline-button($name, $color) { 147 | .btn-outline-#{$name} { 148 | border: 2px solid $color; 149 | color: $color !important; 150 | background-color: transparent; 151 | &:hover, 152 | &:focus, 153 | &:active, 154 | &:active:focus, 155 | &.active { 156 | background-color: transparent; 157 | color: $color; 158 | border-color: $color; 159 | } 160 | &:not([disabled]):not(.disabled):active, 161 | &:not([disabled]):not(.disabled).active, 162 | .show > &.dropdown-toggle { 163 | @extend .z-depth-1-half; 164 | background-color: transparent !important; 165 | border-color: $color !important; 166 | } 167 | &:not([disabled]):not(.disabled):active:focus, 168 | &:not([disabled]):not(.disabled).active:focus, 169 | .show > &.dropdown-toggle:focus { 170 | @extend .z-depth-1-half; 171 | } 172 | } 173 | } 174 | 175 | @mixin make-gradient-button($name, $value) { 176 | .btn.#{$name}-gradient { 177 | background: linear-gradient(40deg, map-get($value, start), map-get($value, end)) !important; 178 | transition:.5s ease; 179 | &:hover, 180 | &:focus, 181 | &:active, 182 | &:active:focus 183 | &.active { 184 | background: linear-gradient(40deg, lighten(map-get($value, start), 5%), lighten(map-get($value, end), 5%)); 185 | } 186 | } 187 | } 188 | 189 | // Button sizes 190 | @mixin button-size($padding-y, $padding-x, $font-size, $line-height, $border-radius) { 191 | padding: $padding-y $padding-x; 192 | font-size: $font-size; 193 | line-height: $line-height; 194 | @include border-radius($border-radius); 195 | } 196 | 197 | 198 | @mixin transition($transition...) { 199 | @if $enable-transitions { 200 | @if length($transition) == 0 { 201 | transition: $transition-base; 202 | } @else { 203 | transition: $transition; 204 | } 205 | } 206 | } 207 | 208 | @mixin transform($args) { 209 | -webkit-transform: $args; 210 | -moz-transform: $args; 211 | -ms-transform: $args; 212 | -o-transform: $args; 213 | transform: $args; 214 | } 215 | 216 | @function breakpoint-min($name, $breakpoints: $grid-breakpoints) { 217 | $min: map-get($breakpoints, $name); 218 | @return if($min != 0, $min, null); 219 | } 220 | 221 | @mixin media-breakpoint-up($name, $breakpoints: $grid-breakpoints) { 222 | $min: breakpoint-min($name, $breakpoints); 223 | @if $min { 224 | @media (min-width: $min) { 225 | @content; 226 | } 227 | } @else { 228 | @content; 229 | } 230 | } 231 | 232 | // Flexbox 233 | @mixin flexbox() { 234 | display: -webkit-box; 235 | display: -moz-box; 236 | display: -ms-flexbox; 237 | display: -webkit-flex; 238 | display: flex; 239 | } 240 | @mixin flex($values) { 241 | -webkit-box-flex: $values; 242 | -moz-box-flex: $values; 243 | -webkit-flex: $values; 244 | -ms-flex: $values; 245 | flex: $values; 246 | } 247 | @mixin order($val) { 248 | -webkit-box-ordinal-group: $val; 249 | -moz-box-ordinal-group: $val; 250 | -ms-flex-order: $val; 251 | -webkit-order: $val; 252 | order: $val; 253 | } 254 | @mixin align($align) { 255 | -webkit-flex-align: $align; 256 | -ms-flex-align: $align; 257 | -webkit-align-items: $align; 258 | align-items: $align; 259 | } 260 | @mixin justify-content($val) { 261 | -webkit-justify-content: $val; 262 | justify-content: $val; 263 | } 264 | 265 | @function breakpoint-infix($name, $breakpoints: $grid-breakpoints) { 266 | @return if(breakpoint-min($name, $breakpoints) == null, "", "-#{$name}"); 267 | } 268 | -------------------------------------------------------------------------------- /public/sass/mdb/free/data/_variables.scss: -------------------------------------------------------------------------------- 1 | // Fonts Path 2 | $roboto-font-path: "../font/roboto/" !default; 3 | 4 | // Full Palette 5 | $enable_full_palette: true; 6 | 7 | // $btn-floating-icon-margin: 5px; 8 | 9 | //Tabs 10 | $tabs-padding: 0.7rem; 11 | $tabs-margin-x: 1rem; 12 | $tabs-margin-y: -20px; 13 | $pills-padding: 0.6rem; 14 | 15 | // Spacers 16 | $spacer: 1rem !default; 17 | $spacer-x: $spacer !default; 18 | $spacer-y: $spacer !default; 19 | $spacers: ( 20 | 0: 0, 21 | 1: ($spacer * .25), 22 | 2: ($spacer * .5), 23 | 3: $spacer, 24 | 4: ($spacer * 1.5), 25 | 5: ($spacer * 3), 26 | 6: ($spacer * 5) 27 | ) !default; 28 | 29 | 30 | // Extreme Padding 31 | $extreme-padding: ( 32 | "xs": 0, 33 | "sm": 5%, 34 | "md": 10%, 35 | "lg": 15%, 36 | "xl": 20% 37 | ); 38 | 39 | // Navbar 40 | $font-weight: 300; 41 | 42 | $navbar-light-toggler-icon: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.9)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%3C/svg%3E") !default; 43 | $navbar-light-bg-active-color: rgba($black, .1) !default; 44 | $navbar-light-hover-color: rgba($black, .75); 45 | 46 | $navbar-dark-toggler-icon: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.9)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%3C/svg%3E") !default; 47 | $navbar-dark-bg-active-color: rgba($white, .1) !default; 48 | $navbar-dark-hover-color: rgba($white, .75); 49 | 50 | $navbar-scrolling-padding: 12px; 51 | $navbar-top-collapse-padding: 5px; 52 | $navbar-flex-icons-padding-md: 6px; 53 | $navbar-flex-icons-padding-lg: 3px; 54 | $navbar-form-input-mr: 5px; 55 | $navbar-form-input-mb: 1px; 56 | $navbar-form-input-ml: 8px; 57 | $navbar-form-input-height: 1rem; 58 | $navbar-dropdown-font-size: 0.9375rem; 59 | $navbar-dropdown-menu-padding: 10px; 60 | $navbar-padding-dropdown: 1rem; 61 | $navbar-avatar-height: 35px; 62 | $navbar-double-small-padding: 4px; 63 | $navbar-double-font-size: 15px; 64 | $navbar-breadcrumb-dn-padding: 1rem; 65 | // $navbar-button-collapse-top: 1px; 66 | $navbar-button-collapse-left: 10px; 67 | $navbar-button-collapse-font-size: 1.5rem; 68 | $navbar-button-collapse-fs-sm: 1.4rem; 69 | $navbar-button-collapse-mx: 10px; 70 | // $navbar-button-collapse-mt: 1px; 71 | 72 | //Dropdown 73 | $dropdown-menu-padding: 0.5rem; 74 | $dropdown-item-padding: 1rem; 75 | 76 | // Badges 77 | $badge-pill-padding-x: 0.6rem !default; 78 | $badge-pill-border-radius: 10rem !default; 79 | 80 | // Progress 81 | $progress-bar-height: 4px; 82 | $progress-bar-wider-height: 20px; 83 | $progress-bar-narrower-height: 1px; 84 | $progress-bar-margin-y: 1rem; 85 | $progress-bar-bg-color: map-get($grey, lighten-3); 86 | 87 | // Forms 88 | // Text Inputs + Textarea 89 | $input-border-color: map-get($grey, "lighten-1"); 90 | $input-focus-color: $primary-color; 91 | $input-font-size: 1rem; 92 | $form-control-margin: 0.5rem; 93 | $form-control-border-color: $primary-color; 94 | // TODO 95 | $input-disabled-color: rgba(0,0,0, .46) !default; 96 | $input-disabled-solid-color: $primary-color !default; 97 | 98 | // Radio Buttons 99 | $radio-fill-color: $primary-color !default; 100 | $radio-empty-color: #5a5a5a !default; 101 | 102 | // Dropdown 103 | $dropdown-bg-color: #fff !default; 104 | $dropdown-hover-bg-color: #eee !default; 105 | $dropdown-color: $primary-color !default; 106 | $dropdown-item-height: 40px !default; 107 | 108 | // Inputs 109 | $input-bg-color: #fff !default; 110 | $input-error-color: $error-color !default; 111 | $input-success-color: $success-color !default; 112 | $input-focus-color: $primary-color !default; 113 | $label-font-size: .8rem !default; 114 | $input-disabled-color: rgba(0,0,0, .46) !default; 115 | // $input-disabled-solid-color: $primary-color !default; //trochę dziwny kolor był tutaj wpisany dlatego zamieniłem 116 | $input-disabled-solid-color: #BDBDBD; 117 | 118 | // Table 119 | $product-table-img-max-height: 150px; 120 | $product-table-img-min-width: 50px; 121 | $table-inverse-color-border: $white !default; 122 | 123 | // Footer 124 | $footer-margin-top: 20px; 125 | $footer-padding-top: 20px; 126 | $footer-copyright-height: 50px; 127 | $footer-copyright-line-height: 50px; 128 | $footer-call-to-action-pt: 1.3rem; 129 | $footer-call-to-action-pb: 0.5rem; 130 | $footer-call-to-action-pr: 10px; 131 | $footer-insta-photos-max-width: 100px; 132 | $footer-insta-photos-margin: 4px; 133 | 134 | // Images Path 135 | $image-path: '../img/'; 136 | 137 | // Reponsive Headings 138 | $responsive-headings: ( 139 | "xs": ( 140 | "h1": 150%, 141 | "h2": 145%, 142 | "h3": 135%, 143 | "h4": 135%, 144 | "h5": 135% 145 | ), 146 | "sm": ( 147 | "h1": 170%, 148 | "h2": 140%, 149 | "h3": 125%, 150 | "h4": 125%, 151 | "h5": 125% 152 | ), 153 | "md": ( 154 | "h1": 200%, 155 | "h2": 170%, 156 | "h3": 140%, 157 | "h4": 125%, 158 | "h5": 125% 159 | ), 160 | "lg": ( 161 | "h1": 200%, 162 | "h2": 170%, 163 | "h3": 140%, 164 | "h4": 125%, 165 | "h5": 125% 166 | ), 167 | "xl": ( 168 | "h1": 250%, 169 | "h2": 200%, 170 | "h3": 170%, 171 | "h4": 140%, 172 | "h5": 125% 173 | ), 174 | ); 175 | 176 | /*** Global ***/ 177 | // Media Query Ranges 178 | $small-screen-up: 601px !default; 179 | $medium-screen-up: 993px !default; 180 | $large-screen-up: 1201px !default; 181 | $small-screen: 600px !default; 182 | $medium-screen: 992px !default; 183 | $large-screen: 1200px !default; 184 | $sidenav-breakpoint: 1440px !default; 185 | 186 | $medium-and-up: "only screen and (min-width : #{$small-screen-up})" !default; 187 | $large-and-up: "only screen and (min-width : #{$medium-screen-up})" !default; 188 | $small-and-down: "only screen and (max-width : #{$small-screen})" !default; 189 | $medium-and-down: "only screen and (max-width : #{$medium-screen})" !default; 190 | $medium-only: "only screen and (min-width : #{$small-screen-up}) and (max-width : #{$medium-screen})" !default; 191 | $hide-sidenav: "only screen and (max-width : #{$sidenav-breakpoint})" !default; 192 | 193 | // Link color 194 | $link-color: #0275d8; 195 | 196 | $navbar-option: ( 197 | "light": ( 198 | "navbar-toggler-icon": $navbar-light-toggler-icon, 199 | "navbar-option-color": $black, 200 | "navbar-link-hover-color": $navbar-light-hover-color, 201 | "navbar-active-link-bg-color": $navbar-light-bg-active-color, 202 | "navbar-placeholder-color": $black !important 203 | ), 204 | "dark": ( 205 | "navbar-toggler-icon": $navbar-dark-toggler-icon, 206 | "navbar-option-color": $white, 207 | "navbar-link-hover-color": $navbar-dark-hover-color, 208 | "navbar-active-link-bg-color": $navbar-dark-bg-active-color, 209 | "navbar-placeholder-color": $white !important 210 | ) 211 | ); -------------------------------------------------------------------------------- /public/sass/mdb/free/_modals.scss: -------------------------------------------------------------------------------- 1 | // Distance 2 | $modal-distance:10px; 3 | $modal-info-color: #5394ff; 4 | $modal-success-color: #01d36b; 5 | $modal-warning-color: #ff8e38; 6 | $modal-danger-color: #ff4b4b; 7 | 8 | // Styles for body 9 | body { 10 | &.modal-open { 11 | padding-right: 0 !important; 12 | overflow: auto; 13 | } 14 | &.scrollable { 15 | overflow-y: auto; 16 | } 17 | } 18 | 19 | // *** ENHANCED BOOTSTRAP MODALS ***/// 20 | // General styles 21 | .modal-dialog { 22 | .modal-content { 23 | @include border-radius(2px); 24 | @extend .z-depth-1-half; 25 | border: 0; 26 | } 27 | } 28 | 29 | // Position & Size 30 | .modal { 31 | padding-right:0 !important; 32 | .modal-dialog { 33 | @media (min-width: 768px) { 34 | &.modal-top { 35 | top: 0; 36 | } 37 | &.modal-left { 38 | left: 0; 39 | } 40 | &.modal-right { 41 | right: 0; 42 | } 43 | &.modal-bottom { 44 | bottom: 0; 45 | } 46 | &.modal-top-left { 47 | top: $modal-distance; 48 | left: $modal-distance; 49 | } 50 | &.modal-top-right { 51 | top: $modal-distance; 52 | right: $modal-distance; 53 | } 54 | &.modal-bottom-left { 55 | left: $modal-distance; 56 | bottom: $modal-distance; 57 | } 58 | &.modal-bottom-right { 59 | right: $modal-distance; 60 | bottom: $modal-distance; 61 | } 62 | } 63 | } 64 | &.fade { 65 | &.top:not(.show) .modal-dialog { 66 | transform: translate3d(0, -25%, 0); 67 | } 68 | &.left:not(.show) .modal-dialog { 69 | transform: translate3d(-25%, 0, 0); 70 | } 71 | &.right:not(.show) .modal-dialog { 72 | transform: translate3d(25%, 0, 0); 73 | } 74 | &.bottom:not(.show) .modal-dialog { 75 | transform: translate3d(0, 25%, 0); 76 | } 77 | } 78 | @media (min-width: $medium-screen) { 79 | &.modal-scrolling { 80 | position: relative; 81 | .modal-dialog { 82 | position: fixed; 83 | z-index: 1050; 84 | } 85 | } 86 | &.modal-content-clickable { 87 | top:auto; 88 | bottom:auto; 89 | .modal-dialog { 90 | position:fixed; 91 | } 92 | } 93 | .modal-fluid { 94 | width: 100%; 95 | max-width: 100%; 96 | .modal-content { 97 | width: 100%; 98 | } 99 | } 100 | .modal-frame { 101 | position: absolute; 102 | width: 100%; 103 | max-width: 100%; 104 | margin: 0; 105 | &.modal-bottom { 106 | bottom: 0; 107 | } 108 | } 109 | .modal-full-height { 110 | display: flex; 111 | position: absolute; 112 | width: 400px; 113 | height: 100%; 114 | margin: 0; 115 | top: 0; 116 | right: 0; 117 | &.modal-top, &.modal-bottom { 118 | display:block; 119 | width:100%; 120 | max-width:100%; 121 | height:auto; 122 | } 123 | &.modal-top { 124 | bottom:auto; 125 | } 126 | &.modal-bottom { 127 | top:auto; 128 | } 129 | .modal-content { 130 | width:100%; 131 | } 132 | &.modal-lg { 133 | max-width:90%; 134 | width:90%; 135 | @media (min-width:$medium-screen) { 136 | max-width:800px; 137 | width:800px; 138 | } 139 | @media (min-width:$large-screen) { 140 | max-width:1000px; 141 | width:1000px; 142 | } 143 | } 144 | } 145 | .modal-side { 146 | position: absolute; 147 | right: $modal-distance; 148 | bottom: $modal-distance; 149 | margin: 0; 150 | width: 400px; 151 | } 152 | } 153 | } 154 | 155 | // Styles 156 | .modal-dialog { 157 | .btn .fa { 158 | color:#fff !important; 159 | } 160 | [class*="btn-outline-"] .fa { 161 | color: inherit !important; 162 | } 163 | // Cascading modals 164 | &.cascading-modal { 165 | margin-top: 10%; 166 | .close { 167 | color:#fff; 168 | opacity:1; 169 | text-shadow:none; 170 | } 171 | // Cascading header 172 | .modal-header { 173 | text-align: center; 174 | margin: -2rem 1rem 1rem 1rem; 175 | padding: 1.5rem; 176 | border: none; 177 | @extend .z-depth-1-half; 178 | @include border-radius(3px); 179 | .close { 180 | margin-right: 1rem; 181 | } 182 | .title { 183 | width: 100%; 184 | margin-bottom: 0; 185 | font-size: 1.25rem; 186 | .fa { 187 | margin-right: 9px; 188 | } 189 | } 190 | .social-buttons { 191 | margin-top: 1.5rem; 192 | a { 193 | font-size: 1rem; 194 | } 195 | } 196 | } 197 | // Cascading tabs nav 198 | .modal-c-tabs { 199 | .nav-tabs { 200 | margin: -1.5rem 1rem 0 1rem; 201 | @extend .z-depth-1; 202 | } 203 | .tab-content { 204 | padding: 1.7rem 0 0 0; 205 | } 206 | } 207 | .nav-tabs { 208 | display:flex; 209 | li { 210 | flex:1; 211 | a { 212 | text-align:center; 213 | font-weight:normal; 214 | } 215 | } 216 | } 217 | // Footer customization 218 | .modal-body, 219 | .modal-footer { 220 | color: #616161; 221 | padding-right: 2rem; 222 | padding-left: 2rem; 223 | .additional-option { 224 | text-align: center; 225 | margin-top: 1rem; 226 | } 227 | } 228 | // Cascading avatar 229 | &.modal-avatar { 230 | margin-top: 6rem; 231 | .modal-header { 232 | @extend .z-depth-0; 233 | @extend .img-fluid; 234 | margin: -6rem 2rem -1rem 2rem; 235 | img { 236 | width: 130px; 237 | @extend .z-depth-2; 238 | margin-left: auto; 239 | margin-right: auto; 240 | } 241 | } 242 | } 243 | } 244 | // Modal notify 245 | &.modal-notify { 246 | .heading { 247 | margin: 0; 248 | padding: 0.3rem; 249 | color: #fff; 250 | font-size: 1.15rem; 251 | } 252 | .modal-header { 253 | @extend .z-depth-1; 254 | border: 0; 255 | } 256 | .close { 257 | opacity: 1; 258 | } 259 | .modal-body { 260 | padding: 1.5rem; 261 | color: #616161; 262 | } 263 | .btn-outline-secondary-modal { 264 | background-color: transparent; 265 | } 266 | // Modal info 267 | &.modal-info { 268 | .modal-header { 269 | background-color: $modal-info-color; 270 | } 271 | .fa { 272 | color: $modal-info-color; 273 | } 274 | .badge { 275 | background-color: $modal-info-color; 276 | } 277 | @include make-button('primary-modal', $modal-info-color); 278 | @include make-outline-button('secondary-modal', $modal-info-color); 279 | } 280 | // Modal warning 281 | &.modal-warning { 282 | .modal-header { 283 | background-color: $modal-warning-color; 284 | } 285 | .fa { 286 | color: $modal-warning-color; 287 | } 288 | .badge { 289 | background-color: $modal-warning-color; 290 | } 291 | @include make-button('primary-modal', $modal-warning-color); 292 | @include make-outline-button('secondary-modal', $modal-warning-color); 293 | } 294 | // Modal success 295 | &.modal-success { 296 | .modal-header { 297 | background-color: $modal-success-color; 298 | } 299 | .fa { 300 | color: $modal-success-color; 301 | } 302 | .badge { 303 | background-color: $modal-success-color; 304 | } 305 | @include make-button('primary-modal', $modal-success-color); 306 | @include make-outline-button('secondary-modal', $modal-success-color); 307 | } 308 | // Modal danger 309 | &.modal-danger { 310 | .modal-header { 311 | background-color: $modal-danger-color; 312 | } 313 | .fa { 314 | color: $modal-danger-color; 315 | } 316 | .badge { 317 | background-color: $modal-danger-color; 318 | } 319 | @include make-button('primary-modal', $modal-danger-color); 320 | @include make-outline-button('secondary-modal', $modal-danger-color); 321 | } 322 | } 323 | } -------------------------------------------------------------------------------- /support_files/templates/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Futiba Club - Fullstack Academy - DevPleno 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 60 | 61 | 62 |
63 |
64 |
65 |
66 |
67 |
68 | 69 |

Desafie seus amigos

70 |
71 |
Quem consegue acertar mais jogos da Copa e do Brasileirão? 72 |
73 |
74 | Criar conta 75 | Ver classificação 76 | 77 |
78 |
79 |
80 | 81 |
82 |
83 |
84 |
85 |
86 |
87 | 88 |
89 |
90 |

Classificação Grupos

91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 |
#GrupoPontos
1Peladeiros da noite500
2Masters of None400
3Filhos do Pelé300
117 |
118 |
119 | 120 |
121 |
122 |
123 |
124 |
125 |
126 |

Entrar

127 |
128 | 129 | 130 | 131 |
132 | 133 |
134 | 135 | 136 | 137 |
138 | 139 |
140 | 141 |
142 |
143 |
144 |
145 | 146 |
147 |
148 |
149 |

Criar uma conta

150 | 151 |
152 | 153 | 154 | 155 |
156 |
157 | 158 | 159 | 160 |
161 | 162 |
163 | 164 | 165 | 166 |
167 | 168 |
169 | 170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 | 178 |
179 | 180 |
181 | 182 |

Write to us

183 | 184 |
185 | 186 | 187 | 188 |
189 | 190 |
191 | 192 | 193 | 194 |
195 | 196 |
197 | 198 | 199 | 200 |
201 | 202 |
203 | 204 | 205 | 206 |
207 | 208 |
209 | 210 |
211 | 212 |
213 | 214 |
215 | 216 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | -------------------------------------------------------------------------------- /public/js/popper.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) Federico Zivolo 2017 3 | Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). 4 | */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=getComputedStyle(e,null);return t?o[t]:o}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll)/.test(r+s+p)?e:n(o(e))}function r(e){var o=e&&e.offsetParent,i=o&&o.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TD','TABLE'].indexOf(o.nodeName)&&'static'===t(o,'position')?r(o):o:e?e.ownerDocument.documentElement:document.documentElement}function p(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||r(e.firstElementChild)===e)}function s(e){return null===e.parentNode?e:s(e.parentNode)}function d(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,i=o?e:t,n=o?t:e,a=document.createRange();a.setStart(i,0),a.setEnd(n,0);var l=a.commonAncestorContainer;if(e!==l&&t!==l||i.contains(n))return p(l)?l:r(l);var f=s(e);return f.host?d(f.host,t):d(e,s(t).host)}function a(e){var t=1=o.clientWidth&&i>=o.clientHeight}),l=0i[e]&&!t.escapeWithReference&&(n=_(p[o],i[e]-('right'===e?p.width:p.height))),pe({},o,n)}};return n.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';p=se({},p,s[t](e))}),e.offsets.popper=p,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,i=t.reference,n=e.placement.split('-')[0],r=X,p=-1!==['top','bottom'].indexOf(n),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(i[s])&&(e.offsets.popper[d]=r(i[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var i;if(!F(e.instance.modifiers,'arrow','keepTogether'))return e;var n=o.element;if('string'==typeof n){if(n=e.instance.popper.querySelector(n),!n)return e;}else if(!e.instance.popper.contains(n))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',g=a?'bottom':'right',u=L(n)[l];d[g]-us[g]&&(e.offsets.popper[m]+=d[m]+u-s[g]),e.offsets.popper=c(e.offsets.popper);var b=d[m]+d[l]/2-u/2,w=t(e.instance.popper),y=parseFloat(w['margin'+f],10),E=parseFloat(w['border'+f+'Width'],10),v=b-e.offsets.popper[m]-y-E;return v=J(_(s[l]-u,v),0),e.arrowElement=n,e.offsets.arrow=(i={},pe(i,m,Math.round(v)),pe(i,h,''),i),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(k(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=y(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement),i=e.placement.split('-')[0],n=x(i),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case le.FLIP:p=[i,n];break;case le.CLOCKWISE:p=q(i);break;case le.COUNTERCLOCKWISE:p=q(i,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(i!==s||p.length===d+1)return e;i=e.placement.split('-')[0],n=x(i);var a=e.offsets.popper,l=e.offsets.reference,f=X,m='left'===i&&f(a.right)>f(l.left)||'right'===i&&f(a.left)f(l.top)||'bottom'===i&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===i&&h||'right'===i&&c||'top'===i&&g||'bottom'===i&&u,w=-1!==['top','bottom'].indexOf(i),y=!!t.flipVariations&&(w&&'start'===r&&h||w&&'end'===r&&c||!w&&'start'===r&&g||!w&&'end'===r&&u);(m||b||y)&&(e.flipped=!0,(m||b)&&(i=p[d+1]),y&&(r=K(r)),e.placement=i+(r?'-'+r:''),e.offsets.popper=se({},e.offsets.popper,S(e.instance.popper,e.offsets.reference,e.placement)),e=C(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport'},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],i=e.offsets,n=i.popper,r=i.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return n[p?'left':'top']=r[o]-(s?n[p?'width':'height']:0),e.placement=x(t),e.offsets.popper=c(n),e}},hide:{order:800,enabled:!0,fn:function(e){if(!F(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=T(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.right