Como já citado anteriormente, Helpers são contruídos de acordo com a especificação machinepack E NÃO FUNCIONARÃO se forem escritos de outra forma, pois são interpretados pelo core do Sails. Observe que os Hooks e as Policies são funcões JavaScript padrão e não seguem o modelo machinepack.
Helpers devem estar localizados no diretório \api\helpers
para que sejam acessíveis
em qualquer parte do seu aplicativo. Utilize await sails.helpers.nomeHelper(params)
para
utilizá-los
Hooks devem estar localizados no diretório \api\hooks
para que funcionem e serão
executados durante a inicialização.
Policies devem estar localizadas no diretório \api\policies
para que sejam
localizadas
pelo core Sails enquanto executa o arquivo de configuracao config/policies.js
, durante a inicialização da aplicação.
Seguem abaixo alguns exemplos funcionais que pode utilizar livremente:
/* is-jwt-valid.js (Policy) ╔╗ ╔╗╔═══╗╔╗ ╔══╗╔═══╗╔═══╗╔═══╗╔═══╗╔═══╗ ╔╗╔╗╔╗╔╗╔════╗ ║╚╗╔╝║║╔═╗║║║ ╚╣╠╝╚╗╔╗║║╔═╗║╚╗╔╗║║╔═╗║║╔═╗║ ║║║║║║║║║╔╗╔╗║ ╚╗║║╔╝║║ ║║║║ ║║ ║║║║║║ ║║ ║║║║║║ ║║║╚═╝║ ║║║║║║║║╚╝║║╚╝ ║╚╝║ ║╚═╝║║║ ╔╗ ║║ ║║║║║╚═╝║ ║║║║║║ ║║║╔╗╔╝ ╔╗║║║╚╝╚╝║ ║║ ╚╗╔╝ ║╔═╗║║╚═╝║╔╣╠╗╔╝╚╝║║╔═╗║╔╝╚╝║║╚═╝║║║║╚╗ ║╚╝║╚╗╔╗╔╝ ╔╝╚╗ ╚╝ ╚╝ ╚╝╚═══╝╚══╝╚═══╝╚╝ ╚╝╚═══╝╚═══╝╚╝╚═╝ ╚══╝ ╚╝╚╝ ╚══╝ */ let jwt = require('jsonwebtoken') module.exports = async function (req, res, proceed) { let token = req.headers['authorization'] + '' if (token && token.startsWith('Bearer')) { token = token.substring(7) /* verifica se o TOKEN recebido esta valido. */ /* o TOKEN foi gerado anteriormente usando a */ /* mesma JWT_SECRET */ jwt.verify(token, process.env.JWT_SECRET, function (err, decoded) { if (!err) { return proceed(); } else { console.log('Unauthorized!') return res.forbidden(); } }); } else { console.log('Invalid Token') return res.forbidden(); } };
/* is-auth-basic-valid.js (Policy) ╔╗ ╔╗╔═══╗╔╗ ╔══╗╔═══╗╔═══╗╔═══╗╔═══╗╔═══╗ ╔═══╗╔╗ ╔╗╔════╗╔╗ ╔╗ ╔══╗ ╔═══╗╔═══╗╔══╗╔═══╗ ║╚╗╔╝║║╔═╗║║║ ╚╣╠╝╚╗╔╗║║╔═╗║╚╗╔╗║║╔═╗║║╔═╗║ ║╔═╗║║║ ║║║╔╗╔╗║║║ ║║ ║╔╗║ ║╔═╗║║╔═╗║╚╣╠╝║╔═╗║ ╚╗║║╔╝║║ ║║║║ ║║ ║║║║║║ ║║ ║║║║║║ ║║║╚═╝║ ║║ ║║║║ ║║╚╝║║╚╝║╚═╝║ ║╚╝╚╗║║ ║║║╚══╗ ║║ ║║ ╚╝ ║╚╝║ ║╚═╝║║║ ╔╗ ║║ ║║║║║╚═╝║ ║║║║║║ ║║║╔╗╔╝ ║╚═╝║║║ ║║ ║║ ║╔═╗║ ║╔═╗║║╚═╝║╚══╗║ ║║ ║║ ╔╗ ╚╗╔╝ ║╔═╗║║╚═╝║╔╣╠╗╔╝╚╝║║╔═╗║╔╝╚╝║║╚═╝║║║║╚╗ ║╔═╗║║╚═╝║ ╔╝╚╗ ║║ ║║ ║╚═╝║║╔═╗║║╚═╝║╔╣╠╗║╚═╝║ ╚╝ ╚╝ ╚╝╚═══╝╚══╝╚═══╝╚╝ ╚╝╚═══╝╚═══╝╚╝╚═╝ ╚╝ ╚╝╚═══╝ ╚══╝ ╚╝ ╚╝ ╚═══╝╚╝ ╚╝╚═══╝╚══╝╚═══╝ */ module.exports = async function (req, res, proceed) { let auth = req.headers['authorization'] + '' if (auth && auth.startsWith('Basic ')) { auth = auth.substring(6) let decoded = window.atob(auth); let splited = decoded.split(`:`) let login = splited[0] let password = splited[1] /* adicione aqui seu algoritimo de verificacao */ /* do login e senha contra a informacao de seu BD */ /* retornando 'false' se a verificacao falhar */ return false } else { console.log('Invalid Token') return res.forbidden(); } };
/* gerador-dv.js (Helper) ╔═══╗╔═══╗╔═══╗╔═══╗╔═══╗╔═══╗╔═══╗ ╔═══╗╔═══╗ ╔═══╗╔══╗╔═══╗ ╔╗ ╔╗╔═══╗╔═══╗╔══╗ ╔═══╗╔══╗╔═══╗╔═══╗╔═══╗╔═══╗╔═══╗ ║╔═╗║║╔══╝║╔═╗║║╔═╗║╚╗╔╗║║╔═╗║║╔═╗║ ╚╗╔╗║║╔══╝ ╚╗╔╗║╚╣╠╝║╔═╗║ ║╚╗╔╝║║╔══╝║╔═╗║╚╣╠╝ ║╔══╝╚╣╠╝║╔═╗║║╔═╗║╚╗╔╗║║╔═╗║║╔═╗║ ║║ ╚╝║╚══╗║╚═╝║║║ ║║ ║║║║║║ ║║║╚═╝║ ║║║║║╚══╗ ║║║║ ║║ ║║ ╚╝ ╚╗║║╔╝║╚══╗║╚═╝║ ║║ ║╚══╗ ║║ ║║ ╚╝║║ ║║ ║║║║║║ ║║║╚═╝║ ║║╔═╗║╔══╝║╔╗╔╝║╚═╝║ ║║║║║║ ║║║╔╗╔╝ ║║║║║╔══╝ ║║║║ ║║ ║║╔═╗ ║╚╝║ ║╔══╝║╔╗╔╝ ║║ ║╔══╝ ║║ ║║ ╔╗║╚═╝║ ║║║║║║ ║║║╔╗╔╝ ║╚╩═║║╚══╗║║║╚╗║╔═╗║╔╝╚╝║║╚═╝║║║║╚╗ ╔╝╚╝║║╚══╗ ╔╝╚╝║╔╣╠╗║╚╩═║╔╗ ╚╗╔╝ ║╚══╗║║║╚╗╔╣╠╗╔╝╚╗ ╔╣╠╗║╚═╝║║╔═╗║╔╝╚╝║║╚═╝║║║║╚╗ ╚═══╝╚═══╝╚╝╚═╝╚╝ ╚╝╚═══╝╚═══╝╚╝╚═╝ ╚═══╝╚═══╝ ╚═══╝╚══╝╚═══╝╚╝ ╚╝ ╚═══╝╚╝╚═╝╚══╝╚══╝ ╚══╝╚═══╝╚╝ ╚╝╚═══╝╚═══╝╚╝╚═╝ */ module.exports = { friendlyName: 'Gerador de Digito Verificador', description: 'Calcula o valor de um determinado digito verificador.', extendedDescription: `Calcula o valor de um determinado digito verificador (DV) a partir dos pesos relativos aplicáveis ao cálculo. Se aplica tanto para CNPJ quanto para CPF, não se limitando a estes. Utilize os pesos: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11] para CPF e [2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5, 6] para CNPJ `, inputs: { digits: {type: 'string', required: true, description: 'Digitos ainda nao validados (sem o DV)', example: '316675800001'}, weights: {type: 'ref', required: true, description: 'Array de pesos relativos atribuídos ao calculo', example: '[2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5, 6]'}, }, exits: { success: { description: 'Calculo realizado com sucesso' }, error: { description: 'Erro ao tentar calcular o DV', } }, fn: async function ({digits, weights}) { const digitsLength = digits.length; const digitsLengthWithoutChecker = weights.length - 1; const sum = digits.split('').reduce((acc, digit, idx) => { return acc + +digit * weights[digitsLength - 1 - idx]; }, 0); const sumDivisionRemainder = sum % 11; const checker = sumDivisionRemainder < 2 ? 0 : 11 - sumDivisionRemainder; if (digitsLength === digitsLengthWithoutChecker) { return await sails.helpers.geradorDv(`${digits}${checker}`, weights); } return `${digits[digitsLength - 1]}${checker}`; } };
/* formata-cnpj-cpf.js (Helper) ╔═══╗╔═══╗╔═══╗╔═╗╔═╗╔═══╗╔════╗╔═══╗╔═══╗╔═══╗╔═══╗ ╔═══╗╔═╗ ╔╗╔═══╗ ╔╗ ╔═══╗╔═══╗ ╔═══╗ ║╔══╝║╔═╗║║╔═╗║║║╚╝║║║╔═╗║║╔╗╔╗║║╔═╗║╚╗╔╗║║╔═╗║║╔═╗║ ║╔═╗║║║╚╗║║║╔═╗║ ║║ ║╔═╗║║╔═╗║ ║╔══╝ ║╚══╗║║ ║║║╚═╝║║╔╗╔╗║║║ ║║╚╝║║╚╝║║ ║║ ║║║║║║ ║║║╚═╝║ ║║ ╚╝║╔╗╚╝║║╚═╝║ ║║ ║║ ╚╝║╚═╝║ ║╚══╗ ║╔══╝║║ ║║║╔╗╔╝║║║║║║║╚═╝║ ║║ ║╚═╝║ ║║║║║║ ║║║╔╗╔╝ ║║ ╔╗║║╚╗║║║╔══╝╔╗║║ ╔═══╗ ║║ ╔╗║╔══╝ ║╔══╝ ╔╝╚╗ ║╚═╝║║║║╚╗║║║║║║║╔═╗║ ╔╝╚╗ ║╔═╗║╔╝╚╝║║╚═╝║║║║╚╗ ║╚═╝║║║ ║║║║║ ║╚╝║ ╚═══╝ ║╚═╝║║║ ╔╝╚╗ ╚══╝ ╚═══╝╚╝╚═╝╚╝╚╝╚╝╚╝ ╚╝ ╚══╝ ╚╝ ╚╝╚═══╝╚═══╝╚╝╚═╝ ╚═══╝╚╝ ╚═╝╚╝ ╚══╝ ╚═══╝╚╝ ╚══╝ */ module.exports = { friendlyName: 'Formata CNPJ ou CPF Brasileiros', description: 'Recebe uma sequencia de numeros sem pontos ou traços e retorna formatado', inputs: { tipo: {type: 'string', required: true, example: 'CNPJ', description: 'Pode ser CNPJ ou CPF'}, digitos: { type: 'string', required: true, example: '01459385730', description: 'Pode ser CNPJ com 14 posições ou CPF com 11 posicões' } }, exits: { success: { description: 'OK.', }, }, fn: async function ({tipo, digitos}) { tipo = tipo + ''.toUpperCase(); digitos = digitos + ''; if(tipo!=='CNPJ'&&tipo!=='CPF') {return 'Tipo Invalido. Informe CNPJ ou CPF';} /* Assume CPF como padrao */ let correctDigitsLength = 11; let firstDotPosition = 2; let secondDotPosition = 5; let slashPosition = -1; let dashPosition = 8; if (tipo === 'CNPJ') { correctDigitsLength = 14; firstDotPosition = 1; secondDotPosition = 4; slashPosition = 7; dashPosition = 11; } if (digitos.length < 11 || digitos.length > 14) { return `O numero informado ${digitos} deve ter no minimo 11 e no maximo 14 digitos: `; } else { const cleanDigits = digitos.replace(/\D/g, ''); return cleanDigits .slice(0, correctDigitsLength) .split('') .reduce((acc, digit, idx) => { const result = `${acc}${digit}`; if (idx !== digitos.length - 1) { if (idx === firstDotPosition || idx === secondDotPosition) { return `${result}.`; } if (idx === slashPosition) { return `${result}/`; } if (idx === dashPosition) { return `${result}-`; } } return result; }, ''); } } };
/* jwt-login.js (Helper) ╔═══╗╔╗ ╔╗╔════╗╔═══╗╔═╗ ╔╗╔════╗╔══╗╔═══╗╔═══╗╔═══╗╔═══╗╔═══╗ ╔╗╔╗╔╗╔╗╔════╗ ║╔═╗║║║ ║║║╔╗╔╗║║╔══╝║║╚╗║║║╔╗╔╗║╚╣╠╝║╔═╗║║╔═╗║╚╗╔╗║║╔═╗║║╔═╗║ ║║║║║║║║║╔╗╔╗║ ║║ ║║║║ ║║╚╝║║╚╝║╚══╗║╔╗╚╝║╚╝║║╚╝ ║║ ║║ ╚╝║║ ║║ ║║║║║║ ║║║╚═╝║ ║║║║║║║║╚╝║║╚╝ ║╚═╝║║║ ║║ ║║ ║╔══╝║║╚╗║║ ║║ ║║ ║║ ╔╗║╚═╝║ ║║║║║║ ║║║╔╗╔╝ ╔╗║║║╚╝╚╝║ ║║ ║╔═╗║║╚═╝║ ╔╝╚╗ ║╚══╗║║ ║║║ ╔╝╚╗ ╔╣╠╗║╚═╝║║╔═╗║╔╝╚╝║║╚═╝║║║║╚╗ ║╚╝║╚╗╔╗╔╝ ╔╝╚╗ ╚╝ ╚╝╚═══╝ ╚══╝ ╚═══╝╚╝ ╚═╝ ╚══╝ ╚══╝╚═══╝╚╝ ╚╝╚═══╝╚═══╝╚╝╚═╝ ╚══╝ ╚╝╚╝ ╚══╝ */ let jwt = require('jsonwebtoken'); module.exports = { friendlyName: 'Login e Autenticacao por Token (JWT)', description: 'Recebe uma requisição HTTP contendo como parametros de query (?name=xyz), valida a senha e retorna um JWT', extendedDescription: `!! Se faz necessario desabilitar o atributo CSRF no dicionario exportado em [security.js], !! !! caso contrario, nao funcionara, sendo retornado "forbidden" !!`, inputs: { emailAddress: { type: 'string', required: true, description: 'email utilizado pelo Usuário na criação do mesmo' }, password: { type: 'string', required: true, description: 'senha em texto claro' }, }, exits: { success: { description: 'Autenticado e retornado um JWT Assinado' }, }, fn: async function ({ emailAddress, password }) { // procura pelo email informado acima, no banco de dados. // (observe que o 'lowerCase' garante que a pesquisa seja 'case insensitive', // independentemente do banco de dados que se esta utilizando) // Observação: User deve estar definido no arquivo 'model/user.js'! let userRecord = await User.findOne({ emailAddress: emailAddress.toLowerCase(), }); // Se nenhum User for encontrado, então dispare o erro de saída "badCombo". if (!userRecord) { throw new Error('Not Found'); } // Se o usuário existe, mas a senha está errada, dispare o erro "badCombo". // Observe que o método 'checkPassword' deve estar definido no diretório /api/helpers await sails.helpers.passwords.checkPassword(password, userRecord.password) .intercept('incorrect', 'badCombo'); return { token: (jwt.sign({ id: userRecord.id, exp: Math.floor(Date.now() / 1000) + (60 * 60), sub: userRecord.emailAddress, rol: [...userRecord.rules] }, process.env.JWT_SECRET)) }; // JWT_SECRET deve ter sido previamente exportado para o ambiente Node.js } };
/* send-twillio-sms.js (Helper) ╔═══╗╔═╗ ╔╗╔╗ ╔╗╔══╗╔═══╗╔═══╗╔═══╗╔═══╗ ╔═══╗╔═══╗ ╔═══╗╔═╗╔═╗╔═══╗ ║╔══╝║║╚╗║║║╚╗╔╝║╚╣╠╝║╔═╗║╚╗╔╗║║╔═╗║║╔═╗║ ╚╗╔╗║║╔══╝ ║╔═╗║║║╚╝║║║╔═╗║ ║╚══╗║╔╗╚╝║╚╗║║╔╝ ║║ ║║ ║║ ║║║║║║ ║║║╚═╝║ ║║║║║╚══╗ ║╚══╗║╔╗╔╗║║╚══╗ ║╔══╝║║╚╗║║ ║╚╝║ ║║ ║╚═╝║ ║║║║║║ ║║║╔╗╔╝ ║║║║║╔══╝ ╚══╗║║║║║║║╚══╗║ ║╚══╗║║ ║║║ ╚╗╔╝ ╔╣╠╗║╔═╗║╔╝╚╝║║╚═╝║║║║╚╗ ╔╝╚╝║║╚══╗ ║╚═╝║║║║║║║║╚═╝║ ╚═══╝╚╝ ╚═╝ ╚╝ ╚══╝╚╝ ╚╝╚═══╝╚═══╝╚╝╚═╝ ╚═══╝╚═══╝ ╚═══╝╚╝╚╝╚╝╚═══╝ */ const accountSid = process.env.TWILLIO_ACCOUNT_SID; const authToken = process.env.TWILLIO_AUTH_TOKEN; const twillioNumber = process.env.TWILLIO_PHONE_NUMBER const client = require('twilio')(accountSid, authToken); module.exports = { friendlyName: 'Send SMS', description: 'Send a SMS by Twillio provider.', extendedDescription: 'To get available this SMS feature we should to create an Twillio account at https://www.twilio.com/try-twilio/?utm_source=sendgrid&utm_medium=consoledash', inputs: { to: {type: 'string', required: true}, body: {type: 'string', required: true} }, exits: { success: { description: 'SMS sent by Twillio successfully!' }, error: { description: 'If some kind of err occur, it often is due to credentials issues', } }, fn: async function ({to, body}) { console.log(`Enviando SMS para ${to}...`) let message = await client.messages .create({ body: body, from: twillioNumber, to: to }) console.log(message.sid) } }
/* format-brazilian-date.js (Helper) ╔═══╗╔═══╗╔═══╗╔═╗╔═╗╔═══╗╔════╗╔═══╗╔═══╗╔═══╗╔═══╗ ╔═══╗╔═══╗ ╔═══╗╔═══╗╔════╗╔═══╗ ╔══╗ ╔═══╗ ║╔══╝║╔═╗║║╔═╗║║║╚╝║║║╔═╗║║╔╗╔╗║║╔═╗║╚╗╔╗║║╔═╗║║╔═╗║ ╚╗╔╗║║╔══╝ ╚╗╔╗║║╔═╗║║╔╗╔╗║║╔═╗║ ║╔╗║ ║╔═╗║ ║╚══╗║║ ║║║╚═╝║║╔╗╔╗║║║ ║║╚╝║║╚╝║║ ║║ ║║║║║║ ║║║╚═╝║ ║║║║║╚══╗ ║║║║║║ ║║╚╝║║╚╝║║ ║║ ║╚╝╚╗║╚═╝║ ║╔══╝║║ ║║║╔╗╔╝║║║║║║║╚═╝║ ║║ ║╚═╝║ ║║║║║║ ║║║╔╗╔╝ ║║║║║╔══╝ ║║║║║╚═╝║ ║║ ║╚═╝║ ║╔═╗║║╔╗╔╝ ╔╝╚╗ ║╚═╝║║║║╚╗║║║║║║║╔═╗║ ╔╝╚╗ ║╔═╗║╔╝╚╝║║╚═╝║║║║╚╗ ╔╝╚╝║║╚══╗ ╔╝╚╝║║╔═╗║ ╔╝╚╗ ║╔═╗║ ║╚═╝║║║║╚╗ ╚══╝ ╚═══╝╚╝╚═╝╚╝╚╝╚╝╚╝ ╚╝ ╚══╝ ╚╝ ╚╝╚═══╝╚═══╝╚╝╚═╝ ╚═══╝╚═══╝ ╚═══╝╚╝ ╚╝ ╚══╝ ╚╝ ╚╝ ╚═══╝╚╝╚═╝ */ module.exports = { friendlyName: 'Format brazilian date', description: '', inputs: { source: {type: 'string'}, }, exits: { success: { description: 'All done.', }, }, fn: async function ({source}) { return new Date(source).toLocaleString('pt-br') } };
/* send-mailgun-email.js (Helper) ╔═══╗╔═╗ ╔╗╔╗ ╔╗╔══╗╔═══╗╔═══╗╔═══╗╔═══╗ ╔═══╗╔═══╗ ╔═╗╔═╗╔═══╗╔══╗╔╗ ╔═══╗╔╗ ╔╗╔═╗ ╔╗ ║╔══╝║║╚╗║║║╚╗╔╝║╚╣╠╝║╔═╗║╚╗╔╗║║╔═╗║║╔═╗║ ╚╗╔╗║║╔══╝ ║║╚╝║║║╔═╗║╚╣╠╝║║ ║╔═╗║║║ ║║║║╚╗║║ ║╚══╗║╔╗╚╝║╚╗║║╔╝ ║║ ║║ ║║ ║║║║║║ ║║║╚═╝║ ║║║║║╚══╗ ║╔╗╔╗║║║ ║║ ║║ ║║ ║║ ╚╝║║ ║║║╔╗╚╝║ ║╔══╝║║╚╗║║ ║╚╝║ ║║ ║╚═╝║ ║║║║║║ ║║║╔╗╔╝ ║║║║║╔══╝ ║║║║║║║╚═╝║ ║║ ║║ ╔╗║║╔═╗║║ ║║║║╚╗║║ ║╚══╗║║ ║║║ ╚╗╔╝ ╔╣╠╗║╔═╗║╔╝╚╝║║╚═╝║║║║╚╗ ╔╝╚╝║║╚══╗ ║║║║║║║╔═╗║╔╣╠╗║╚═╝║║╚╩═║║╚═╝║║║ ║║║ ╚═══╝╚╝ ╚═╝ ╚╝ ╚══╝╚╝ ╚╝╚═══╝╚═══╝╚╝╚═╝ ╚═══╝╚═══╝ ╚╝╚╝╚╝╚╝ ╚╝╚══╝╚═══╝╚═══╝╚═══╝╚╝ ╚═╝ */ var api_key = process.env.MAIL_GUN_API_KEY; var domain = process.env.MAIL_GUN_DOMAIN; var mailgun = require('mailgun-js')({apiKey: api_key, domain: domain}); module.exports = { friendlyName: 'Send MailGun', description: 'Send a Mail by MailGun provider.', extendedDescription: 'To get available this MailGun feature we should to create an MailGun account at https://signup.mailgun.com/new/signup', inputs: { from: {type: 'string', required: true}, to: {type: 'string', required: true}, subject: {type: 'string', required: true}, text: {type: 'string', required: true} }, exits: { success: { description: 'MailGun sent successfully!' }, error: { description: 'If some kind of err occur, it often is due to credentials issues', } }, fn: async function ({from, to, subject, text}) { console.log(`Sendind MailGun to ${to}...`) var data = { from: from, to: to, subject: subject, text: text // or html: if you wanna send html instead pure txt }; mailgun.messages().send(data, function (error, body) { console.log(body); }); } }
Componentes são instâncias reutilizáveis do Vue com um nome. Nesse caso,
//╔═══╗╔═══╗╔═╗╔═╗╔═══╗╔═══╗╔═╗ ╔╗╔═══╗╔═╗ ╔╗╔════╗╔═══╗ ╔═══╗╔═══╗╔═══╗╔══╗╔═╗ ╔╗╔═══╗╔═══╗╔═══╗╔═══╗ //║╔═╗║║╔═╗║║║╚╝║║║╔═╗║║╔═╗║║║╚╗║║║╔══╝║║╚╗║║║╔╗╔╗║║╔══╝ ║╔═╗║║╔═╗║║╔═╗║╚╣╠╝║║╚╗║║║╔═╗║╚╗╔╗║║╔═╗║║╔═╗║ //║║ ╚╝║║ ║║║╔╗╔╗║║╚═╝║║║ ║║║╔╗╚╝║║╚══╗║╔╗╚╝║╚╝║║╚╝║╚══╗ ║╚═╝║║║ ║║║║ ╚╝ ║║ ║╔╗╚╝║║║ ║║ ║║║║║║ ║║║╚═╝║ //║║ ╔╗║║ ║║║║║║║║║╔══╝║║ ║║║║╚╗║║║╔══╝║║╚╗║║ ║║ ║╔══╝ ║╔══╝║╚═╝║║║╔═╗ ║║ ║║╚╗║║║╚═╝║ ║║║║║║ ║║║╔╗╔╝ //║╚═╝║║╚═╝║║║║║║║║║ ║╚═╝║║║ ║║║║╚══╗║║ ║║║ ╔╝╚╗ ║╚══╗ ║║ ║╔═╗║║╚╩═║╔╣╠╗║║ ║║║║╔═╗║╔╝╚╝║║╚═╝║║║║╚╗ //╚═══╝╚═══╝╚╝╚╝╚╝╚╝ ╚═══╝╚╝ ╚═╝╚═══╝╚╝ ╚═╝ ╚══╝ ╚═══╝ ╚╝ ╚╝ ╚╝╚═══╝╚══╝╚╝ ╚═╝╚╝ ╚╝╚═══╝╚═══╝╚╝╚═╝ /* \assets\js\components\paginador.js */ /** * * ----------------------------------------------------------------------------- * Um paginador para grandes conjuntos de dados: * * @property content um array contendo o conjunto de registros, paginado * @property number o numero da pagina cujo cursor deve estar * @property size a quantidade de registros maxima esperada, por pagina * @property total o numero total de registros (filtrados) da base de dados * * How to: para incorporar a paginacao: * <paginador :content="page.content" :number="page.number" :size="page.size" :total="page.totalpages" v-on:paginated="paginate($event)"></paginador> * ----------------------------------------------------------------------------- */ parasails.registerComponent('paginador', { // ╔═╗╦═╗╔═╗╔═╗╔═╗ // ╠═╝╠╦╝║ ║╠═╝╚═╗ // ╩ ╩╚═╚═╝╩ ╚═╝ props: [ 'content', 'number', 'size', 'total' ], // ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗ // ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣ // ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝ data: function () { return { cont: [], num: 0, siz: 0, tot: 0 }; }, // ╦ ╦╔╦╗╔╦╗╦ // ╠═╣ ║ ║║║║ // ╩ ╩ ╩ ╩ ╩╩═╝ template: ` <div id='paginador' class='text-center'> <em class="fa fa-fast-backward m-2" @click='goToFirst()'></em> <em class="fa fa-backward m-2" @click='goToPrevious()'></em> <em class="badget m-2">{{+number + 1}}</em> <em class="fa fa-forward m-2" @click='goToNext()'></em> <em class="fa fa-fast-forward m-2" @click='goToLast()'></em> <br> <em class="fw-bold">[ {{+number * +size}} - {{(+number * +size) + +size }} / {{ (total) }} ]</em> </div> `, // ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗ // ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣ // ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝ beforeMount: function () { this.cont = this.content; this.num = this.number; this.siz = this.size; this.tot = this.total; }, beforeDestroy: function () { }, // ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗ // ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗ // ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝ methods: { goToFirst: function () { this.num = 0; this.$emit('paginated', { content: this.cont, number: this.num, size: this.siz, total: this.tot }); }, goToPrevious: function () { this.num = this.number - 1; if (this.num < 0) {this.num = 0;} this.$emit('paginated', { content: this.cont, number: this.num, size: this.siz, total: this.tot }); }, goToNext: function () { if(this.content.length < this.size) return; this.num = this.number + 1; let maxPage = this.total / this.size; if (this.num > maxPage) {this.num = maxPage;} this.$emit('paginated', { content: this.cont, number: this.num, size: this.siz, total: this.tot }); }, goToLast: function () { if(this.content.length < this.size) return; this.num = this.total / this.size; this.$emit('paginated', { content: this.cont, number: this.num, size: this.siz, total: this.tot }); }, } });
Um recurso útil para inclusão de dados falsos na base de dados, para testes
Para criar um Script utilize o comando sails generate script cria-dados-para-teste
Modifique o script/gerador de dados falsos de Churchs, conforme abaixo:
Para executar o Script utilize o comando sails run cria-dados-para-teste
Para incorporar o Script dentro de outro código, utilize await require('<path>/scripts/cria-dados-para-teste').fn()
module.exports = { // ___ ___ ___ _ ___ _____ ___ ___ ___ _____ ___ ___ _____ ___ ___ ___ _____ ___ _____ ___ //( _ \ ( _ \ | _ \ (_)( _ \ (_ _) ( _ \ ( _ \ ( _ \( _ )| _ \ ( _ \( _ ) ( _ \ ( _ \ ( _ \( _ )( _ \ ( _ )( _ \ //| (_(_)| ( (_)| (_) )| || |_) )/|| | | | ) || (_(_) | ( (_) (_) || (_) )| ( (_) (_) | | | ) || (_(_) | | ) | (_) || | ) || ( ) || (_(_) // \__ \ | | _ | / | || __/(_)| | | | | )| _)_ | | _( _ )| / | | __( _ ) | | | )| _)_ | | | ) _ )| | | )| | | | \__ \ //( )_) || (_( )| |\ \ | || | | | | |_) || (_( ) | (_( ) | | || |\ \ | |(_ ) | | | | |_) || (_( ) | |_) | | | || |_) || (_) |( )_) | // \(___)(____/ (_) (_)(_)(_) ( ) (____/ (____/ (____/(_) (_)(_) (_)(____/(_) (_) (____/ (____/ (____/(_) (_)(____/ (_____) \(___) friendlyName: 'Cria dados para teste', description: 'Cria dados para teste utilizando a biblioteca faker como geradora de dados', fn: async function () { sails.log('Running custom shell script... (`sails run cria-dados-para-teste`)'); // NAO SE ESQUECA DE INSTALAR A BIBLIOTECA COM [npm i @faker-js/faker --save-dev] const {faker} = require('@faker-js/faker') // OBSERVE QUE O OBJETO X RELACIONAL User JÁ EXISTE NATIVAMENTE NO Protótipo Sails.js let users = await User.find() let user = users[0] // OBSERVE QUE OS EXEMPLOS DE MODEL ABAIXO NÃO EXISTEM NATIVAMENTE NO SAILS! // Você deve substituir os Objetos por aqueles que você deseja carregar com dados falsos para teste // ou criar estes Model(os) com o comando [sails generate model nome-objeto] let church = await Church.create({ fullName: faker.company.name(), shortName: faker.company.buzzNoun(), email: faker.internet.email(), address: faker.location.streetAddress(), site: faker.internet.url(), phone: faker.phone.number(), linktree: `https://linktr.ee/fake-cuidado-cristao`, tipo: 'DAUGHTER' }).fetch() for (let i = 1; i < 5; i++) { await Classroom.create({ name: faker.lorem.word() }) } for (let i = 1; i < 5; i++) { await Contribution.create({ dtContribution: new Date(), value: faker.commerce.price(), propose: faker.commerce.productDescription(), userId: user.id }) } for (let i = 1; i < 5; i++) { await Usercare.create({ userId: user.id, dtContact: new Date(), record: faker.lorem.paragraph({min: 1, max: 3}) }) } await UserChurch.create({ churchId: church.id, userId: user.id, type: 'CONGREGATION', dtAssociation: new Date() }) let classrooms = await Classroom.find() classrooms.forEach(classroom=>{ UserClassroom.create({ dtAssociation: new Date(), type: 'CLASSMATE', userId: user.id }).then().catch(err=>console.log(err)) }) sails.log('Finished custom shell script... (`sails run cria-dados-para-teste`)'); } };
// ____ _____ ____ __ __ __ __ __ __ ____ ____ _____ __ ____ __ _ _ ____ __ ____ ____ __ _____ // ( ___)( _ )( _ \( \/ )( )( )( ) /__\ ( _ \(_ _)( _ ) /__\ (_ _) /__\ ( \/ ) ( _ \ /__\ ( _ \ ( _ \ /__\ ( _ ) // )__) )(_)( ) / ) ( )(__)( )(__ /( )\ ) / _)(_ )(_)( /( )\ /\_)( /( )\ ) ( )___/ /( )\ )(_) ) ) / /( )\ )(_)( // (_) (_____)(_)\_)(_/\/\_)(______)(____)(__)(__)(_)\_)(____)(_____) (__)(__)\____) (__)(__)(_/\_) (__) (__)(__)(____/ (_)\_)(__)(__)(_____) // <div id='formulario-ajax-padrao' v-cloak > // <ajax-form action="updatePassword" // :syncing.sync="syncing" // :cloud-error.sync="cloudError" // :form-data="formData" // :form-rules="formRules" // :form-errors.sync="formErrors" // @submitted="submittedForm()"> // <input class="form-control" // id="password" // name="password" // type="password" // :class="[formErrors.password ? 'is-invalid' : '']" // v-model.trim="formData.password" // placeholder="••••••••" // autocomplete="new-password" // focus-first> // <cloud-error v-if="cloudError"></cloud-error> // <ajax-button type="submit" :syncing="syncing" class="btn btn-dark">Save changes</ajax-button> // </ajax-form> // </div> parasails.registerPage('formulario-ajax-padrao', { // ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗ // ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣ // ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝ data: { // Flag de sincronismo de estado (em execucao = true/aguardando = false). syncing: false, // Atributos/Dados editaveis do formulario que nao necessitam de validacao alguma formData: { rememberMe: true, }, // Erros encontrados na validacao do formulario. // Internamente, o Sails grava {invalido = 'true' ou 'false'} para cada campo do formData. formErrors: { /* … */ }, // Regras de validacao do formulario // Atributos / Dados do Formulario e regras de validacao // Atributos definidos aqui sao copiados internamente para o Objeto formData formRules: { emailAddress: {isEmail: true, required: true}, fullName: {required: true}, password: {required: true}, confirmPassword: {required: true, sameAs: 'password'}, }, // Armazena os error retornados pelo servidor, se // e somente se, a requisicao ao servidor teve // como origem a API Cloud.js (veja arquivo assets/cloud.setup.js) // o Cloud insere neste campo somente a mensagem de erro, mas o JSON // de resposta tem muito mais informacoes e pode ser aproveitado pelo // programador. Vide `tratamento de erros Cloud.js` mais abaixo. cloudError: '', // Flag de sucesso da requisicao enviada. // Deve ser convertida por acao programada // dentro metodo invocado pelo evento 'submitted' [@submitted="submittedForm()"] // invocado pelo <ajax-form> para caso de sucesso. cloudSuccess: false, }, // ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗ // ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣ // ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝ beforeMount: function() { //… }, mounted: async function() { //… }, // ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗ // ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗ // ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝ methods: { // Este metodo e um exemplo de metodo invocado pelo evento submitted // do componente <ajax-form> submittedForm: async function() { // Tudo aqui sera processado somente se a requisicao enviada // obtiver 200 OK como resposta. this.cloudSuccess = true; }, // Cinco formas possiveis de submeter (submit) os dados para o backend: // 1) Com AJAX, informando diretamente o nome do dicionario registrado no arquivo cloud.setup.js. Ex.: 'updateBillingCard' // <ajax-form action='updateBillingCard'...></ajax-form> - os parametros (name) sao passados pelo Core Sails ao Cloud.js // 2) Com AJAX, delegando a requisicao para um Handler que acionara o Cloud.js indiretamente // <ajax-form :handle-submitting="handleSubmittingUpdateBillingCard"...> </ajax-form> // , passando-se os parametros esperados pelo metodo invocado. Ex.: handleSubmittingUpdateBillingCard: async function(argins) { var newPaymentSource = argins.newPaymentSource; await Cloud.updateBillingCard.with(newPaymentSource); }, // 3) Diretamente com a tag html <form>. Ex. <form action='api/v1/account/update-billing-card'>... </form>, // mas neste caso e preciso informar o caminho exato da url requisitada e nao sera possivel capturar os erros, // se houver. Esta opcao, embora mais simples, inviabiliza o uso do recurso AJAX. // Nos casos acima, sempre sera necessario adicionar ao formulario o CSRF (<input type='hidden' name='_csrf' value='<%=_csrf>'). // Para desativar esta exigencia, modifique o parametro csrf do arquivo 'config/security.js' para false // 4) Sem o uso de Formulários html, capturando os dados com o Vue e enviando com o recurso Cloud. Esta opção // simplifica o envio de dados e permite a captura de erros, mas tem a desvantagem de delegar o controle de // erros ao Handler, exemplos: // -- no arquivo *.ejs: <input class=`form-control` v-model=`newPaymentSource`> //observe que nao existe tag <form>, nem atributo `id`, nem `name` // -- no arquivo *.page.js (Vue 2): data: { newPaymentSource = `` }, // ... handleSubmittingUpdateBillingCard: function() { var newPaymentSource = this.newPaymentSource; if(newPaymentSource&&newPaymentSource.lenght > 3) Cloud.updateBillingCard .with(newPaymentSource) .then(result=>alert(`deu certo`)) .catch(err=>{ // Vide `tratamento de erros Cloud.js` mais abaixo. alert(`deu errado`) }) .finally(alert(`Haja o que houver`)) }, // 5) Utilizando AJAX programaticamente, exemplo: // $.post( "api/v1/account/update-billing-card", function( data ) { // $( ".result" ).html( data ); // }).done(function() { // alert( "second success" ); // }) // .fail(function() { // alert( "error" ); // }) // .always(function() { // alert( "finished" ); // }); } });
// _____ ____ _ _____ _ _ _ ____ ___ // |_ _| _ \ / \|_ _|/ \ | \ | | _ \ / _ \ // | | | |_) | / _ \ | | / _ \ | \| | | | | | | | // | | | _ < / ___ \| |/ ___ \| |\ | |_| | |_| | // |_|_|_|_\_\/_/___\_\_/_/ __\_\_| \_|____/ \___/_ _ _ ____ _ ____ // | ____| _ \| _ \ / _ \/ ___| / ___| | / _ \| | | | _ \ | / ___| // | _| | |_) | |_) | | | \___ \ | | | | | | | | | | | | | |_ | \___ \ // | |___| _ <| _ <| |_| |___) | | |___| |__| |_| | |_| | |_| | |_| |___) | // |_____|_| \_\_| \_\\___/|____/ \____|_____\___/ \___/|____(_)___/|____/ /* Cloud.js vem incorporado ao Sails.js como solucao para automacao de requisicoes HTTP. Em resumo, a API consulta o arquivo de definicao `cloud.setup.js` para identificar as rotas (paths) associadas a um determinado nome de rota Cloud. Uma vez identificado, a API faz a requisicao e controla os erros de resposta. No exemplo abaixo foi utilizada a abordagem identificada acima como 4) Uso do Vue 2 para enviar a requisição HTTP ao servidor. */ // arquivo save-guideline.js module.exports = { friendlyName: 'Salvar guideline', description: 'Uma action 2 para salvar objetos Guideline', inputs: { id: {type: `string`}, sequence: {type: `number`, required: true}, text: { type: `string`, required: true, description: `free text`} }, exits: { success: { description: `Done` }, // Esta funcao action 2 retorna um erro 402 e uma // descricao da mensagem de erro ao requisitante http sequenceAlreadyInUse: { statusCode: 409, description: 'O numero de sequencia ja esta em uso!', }, }, fn: async function (inputs) { if(inputs.id){ const guideline = await Guideline.updateOne( {id: inputs.id}, { sequence: inputs.sequence, text: inputs.text } ).intercept('E_UNIQUE', 'sequenceAlreadyInUse') // este metodo `intercept`, intercepta os erros de banco de dados // e redireciona para a funcao exit `sequenceAlreadyInUse` definida // acima. return {guideline} }else{ const guideline = await Guideline.create( { sequence: inputs.sequence, text: inputs.text } ).intercept('E_UNIQUE', 'sequenceAlreadyInUse') // este metodo `intercept`, intercepta os erros de banco de dados // e redireciona para a funcao exit `sequenceAlreadyInUse` definida // acima. return guideline } } } /* Este é o JSON de resposta enviado pelo backend sails (controllers/save-guideline.js) quando o erro for interceptado. Observe atentamente o atributo "x-exit-description" do dicionario! ==> "O numero de sequencia ja esta em uso!", */ { "name": "CloudError", "responseInfo": { "body": "Conflict", "statusCode": 409, "headers": { "cache-control": "no-cache, no-store", "connection": "keep-alive", "content-length": "8", "content-type": "text/plain; charset=utf-8", "date": "Sat, 16 Dec 2023 154843 GMT", "etag": "W/\"8-OfewgPiFJ3o3XA5wgKRYk2ZHNlU\"", "keep-alive": "timeout=5", "x-exit": "sequenceAlreadyInUse", "x-exit-description": "O numero de sequencia ja esta em uso!", "x-powered-by": "Sails <sailsjs.com>" }, "data": "Conflict", "exit": "sequenceAlreadyInUse", "code": "sequenceAlreadyInUse" }, "exit": "sequenceAlreadyInUse", "code": "sequenceAlreadyInUse" } /* Este erro sera recebido pelo componente Vue 2 que fez a requisicao e sera tratado para resposta na View do Operador. Observe que aqui capturamos o atributo o atributo "x-exit-description", mas poderiamos ter feito de uso de quaiquer outros disponiveis no JSON de resposta. */ parasails.registerPage('edit-guideline', { // ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗ // ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣ // ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝ data: { message: {severity: ``, summary: ``, details: ``}, }, // ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗ // ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣ // ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝ beforeMount: function () { //… }, mounted: async function () { //… }, // ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗ // ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗ // ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝ methods: { save: function () { Cloud .saveGuideline .with(this.guideline) .then(() => { this.message.severity = `success` this.message.summary = `Salvo com sucesso` this.message.details = `` }) .catch( err => { console.log(JSON.stringify(err)) this.message.severity = `error` this.message.summary = `Erro ao Salvar` this.message.details = err.responseInfo.headers['x-exit-description'] } ) }, } })
// ____ ____ _ _ _____ ____ _ _ _ _____ ____ // / ___| / ___| | | | ____| _ \| | | | | | ____| _ \ // \___ \| | | |_| | _| | | | | | | | | | _| | | | | // ___) | |___| _ | |___| |_| | |_| | |___| |___| |_| | // |____/ \____|_| |_|_____|____/ \___/|_____|_____|____/ // // _ _ ___ _____ ___ _____ ___ ____ _ _____ ___ ___ _ _ // | \ | |/ _ \_ _|_ _| ___|_ _/ ___| / \|_ _|_ _/ _ \| \ | | // | \| | | | || | | || |_ | | | / _ \ | | | | | | | \| | // | |\ | |_| || | | || _| | | |___ / ___ \| | | | |_| | |\ | // |_| \_|\___/ |_| |___|_| |___\____/_/ \_\_| |___\___/|_| \_| module.exports = { friendlyName: 'Send Broadcast To Session', description: 'Envia uma mensagem para a sessao do usuario, no momento agendado.', extendedDescription: ` Uma simplificacao aplicada da biblioteca (Node Schedule)[https://www.npmjs.com/package/node-schedule] em conjunto com os recursos de WebSocket nativos do (Sails.js WebSocket)[https://sailsjs.com/documentation/reference/web-sockets]. Para saber mais, visite o respectivo sitio indicado acima`, inputs: { roomName: { type: `string`, description: `Nome da Sala escolhida para o broadcast`}, sessionName: {type: `string`, description: `Nome da sessao alvo do broadcast`}, cron: { type: 'string', description: `A cronologia no padrao Linux Cron a ser aplicada no broadcast`, extendedDescription: ` * * * * * * ┬ ┬ ┬ ┬ ┬ ┬ │ │ │ │ │ │ │ │ │ │ │ └ dia da semana (0 - 7) (0 ou 7 para Domingo) │ │ │ │ └───── numero do mes (1 - 12) │ │ │ └────────── dia do mes (1 - 31) │ │ └─────────────── hora (0 - 23) │ └──────────────────── minuto (0 - 59) └───────────────────────── segundo (0 - 59, opcional) ` }, notificationText: { type: 'string', description: `Texto a ser visualizado na notificacao por push` } }, exits: { success: { description: 'Executado.', }, }, fn: async function (inputs) { const schedule = require('node-schedule'); const job = schedule.scheduleJob(inputs.cron, () => { console.log(inputs.notificationText); sails.sockets.broadcast(inputs.roomName, inputs.sessionName, {notificationText: inputs.notificationText}); // sera recebido pelo cliente WebSocket que esta ouvindo o Socket de nome [roomName] e sessao [sessionName] // ex.: io.socket.on(`sessionName`, ()=>{...}) }); } };
// _____ ___ _ _____ // | ___|_ _| | | ____| // | |_ | || | | _| // | _| | || |___| |___ // |_|___|___|_____|_____| ____ ___ _ _ _____ ____ // / ___/ _ \| \ | |_ _| _ \ / _ \| | | | | ____| _ \ // | | | | | | \| | | | | |_) | | | | | | | | _| | |_) | // | |__| |_| | |\ | | | | _ <| |_| | |___| |___| |___| _ < // \____\___/|_| \_| |_| |_| \_\\___/|_____|_____|_____|_| \_\ /** * FileController * * @description :: logica backend (Server-side) para controle de arquivos * @help :: See http://links.sailsjs.org/docs/controllers */ module.exports = { /** * `FileController.upload()` * * Envia arquivos para o servidor armazenar em disco local. */ upload: function (req, res) { // e.g. // 0 => infinito // 240000 => 4 minutos (240,000 milisegundos) // etc. // // O padrao e de 2 minutos. res.setTimeout(0); req.file('nome-do-arquivo') .upload({ // Devemos definir o tamanho maximo dos arquivos (em bytes) maxBytes: 1000000 }, function whenDone(err, uploadedFiles) { if (err) return res.serverError(err); else return res.json({ files: uploadedFiles, textParams: req.allParams() }); }); }, /** * `FileController.s3upload()` * * Envia arquivos para o servidor armazenar em no AWS S3 (Buckets). * * NOTA: * Se o arquivo a ser enviado e realmente grande, considere aumentar o * timeout da conexao TCP no servidor. */ s3upload: function (req, res) { // e.g. // 0 => infinito // 240000 => 4 minutos (240,000 milisegundos) // etc. // // O padrao e de 2 minutos. res.setTimeout(0); req.file('avatar').upload({ adapter: require('skipper-s3'), bucket: process.env.BUCKET, key: process.env.KEY, secret: process.env.SECRET }, function whenDone(err, uploadedFiles) { if (err) return res.serverError(err); else return res.json({ files: uploadedFiles, textParams: req.allParams() }); }); }, /** * `FileController.gridFS()` * * Envia arquivos para o servidor armazenar no MongoDB GridFS. * * NOTA: * Se o arquivo a ser enviado e realmente grande, considere aumentar o * timeout da conexao TCP no servidor. */ gridFS: function (req, res) { // e.g. // 0 => infinito // 240000 => 4 minutos (240,000 milisegundos) // etc. // // O padrao e de 2 minutos. res.setTimeout(0); req.file('avatar').upload({ adapter: require('skipper-gridfs'), uri: 'mongodb://[username:password@]host1[:port1][/[database[.bucket]]' }, function whenDone(err, uploadedFiles) { if (err) return res.serverError(err); else return res.json({ files: uploadedFiles, textParams: req.allParams() }); }, /** * FileController.download() * * Recebe arquivos do servidor, obtidos do armazenamento local. */ download: function (req, res) { var Path = require('path'); var fs = require('fs'); // If a relative path was provided, resolve it relative // to the cwd (which is the top-level path of this sails app) fs.createReadStream(Path.resolve(req.param('path'))) .on('error', function (err) { return res.serverError(err); }) .pipe(res); } };
// __ __ // | \/ | ___ _ __ ___ __ _ __ _ ___ _ __ ___ // | |\/| |/ _ \ '_ \/ __|/ _` |/ _` |/ _ \ '_ ` _ \ // | | | | __/ | | \__ \ (_| | (_| | __/ | | | | | // |_| |_|\___|_| |_|___/\__,_|\__, |\___|_| |_| |_| // __ __ _ _ |___/ // | \/ | ___ __| | __ _| | // | |\/| |/ _ \ / _` |/ _` | | // | | | | (_) | (_| | (_| | | // |_| |_|\___/ \__,_|\__,_|_| /** * <message> * ----------------------------------------------------------------------------- * A modal dialog pop-up. * * > Be careful adding other Vue.js lifecycle callbacks in this file! The * > finnicky combination of Vue transitions and bootstrap modal animations used * > herein work, and are very well-tested in practical applications. But any * > changes to that specific cocktail could be unpredictable, with unsavory * > consequences. * * @type {Component} * * * ----------------------------------------------------------------------------- */ parasails.registerComponent('message', { // ╔═╗╦═╗╔═╗╔═╗╔═╗ // ╠═╝╠╦╝║ ║╠═╝╚═╗ // ╩ ╩╚═╚═╝╩ ╚═╝ props: [ 'severity', 'summary', 'details' ], // ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗ // ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣ // ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝ data: function () { return {} }, // ╦ ╦╔╦╗╔╦╗╦ // ╠═╣ ║ ║║║║ // ╩ ╩ ╩ ╩ ╩╩═╝ template: ` <!-- use: <message id="msg" v-on:close="cleanMessage()" :severity="message.severity" :summary="message.summary" :details="message.details"></message> it shows message according given severity (error|warn|success|info) if summary lenght is greater than 0. These details are optional to clean message just clean message.summary value under cleanMessage() method. --> <div> <div v-if="show()"> <div class="modal fade show" tabindex="-1" style="display: block; overflow: visible; box-shadow: #5a5a5a"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title"> <div v-if="severity=='success'"> <span class="fw-bolder text-success"><em class="fa fa-check"></em> {{summary}}</span> </div> <div v-if="severity=='info'"> <span class="fw-bolder text-info"><em class="fa fa-check"></em> {{summary}}</span> </div> <div v-if="severity=='warn'"> <span class="fw-bolder text-warning"><em class="fa fa-warning"></em> {{summary}}</span> </div> <div v-if="severity=='error'"> <span class="fw-bolder text-danger"><em class="fa fa-bug"></em> {{summary}}</span> </div> </h5> <button type="button" class="btn-close" @click="$emit('close', '')" aria-label="Close"></button> </div> <div class="modal-body"> <p class="text-muted">{{details}}</p> </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" @click="$emit('close', '')"><em class="fa fa-close"></em></button> </div> </div> </div> </div> </div> </div> `, // ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗ // ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣ // ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝ beforeMount: function () { }, mounted: function () { }, // ^Note that there is no `beforeDestroy()` lifecycle callback in this // component. This is on purpose, since the timing vs. `leave()` gets tricky. // ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗ // ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗ // ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝ methods: { show: function () { return this.summary !== '' } } })
// __ _______ ____ ____ ___ ___ _ _ ___ _ _ ____ // \ \ / / ____| _ \/ ___|_ _/ _ \| \ | |_ _| \ | |/ ___| // \ \ / /| _| | |_) \___ \| | | | | \| || || \| | | _ // \ V / | |___| _ < ___) | | |_| | |\ || || |\ | |_| | // \_/ |_____|_| \_\____/___\___/|_| \_|___|_| \_|\____| module.exports = { friendlyName: 'Versioning', description: 'Automatiza o controle de versoes.', extendedDescription: ` Este script utiliza o arquivo ./VERSIONING.md para registro do historico de versoes de seu Prototipo Sails.js. Para que o Sails seja capaz de identificar corretamente a raiz do Prototipo, se faz necessario executa-lo durante a inicializacao do App (sails lift ou npm start). Por esse motivo requer que seja adicionado ao arquivo 'config/boostrap.js' uma linha de chamada da funcao 'fn' como se segue: require('/scripts/versioning.js').fn() // adicionar no inicio do corpo do modulo O Script compara a versao do Prototipo registrada no arquivo 'package.json' com a lista de versoes registradas no VERSIONING.md, se nao encontrar,obtem do dicionario os atributos version e description para criar um registro do tipo: version: Mon Nov 13 2023 10:38:37 GMT-0300 (Horário Padrão de Brasília)- description Portanto, para manter seu historico de versoes atualizado, basta alterar os valores dos atributos "version" e "description" a cada nova "release" lancada. `, fn: async function () { sails.log('Executando shell script customizado... (`sails run versioning`)'); const path = require('path'); const fs = require('fs'); const infoPath = path.resolve(sails.config.appPath, 'package.json'); const info = JSON.parse(fs.readFileSync(infoPath).toString()); const fileName = path.resolve(sails.config.appPath, 'VERSIONING.md'); fs.open(fileName, (err) => { if (err) { fs.writeFileSync(fileName, `v.0.0.0: ${new Date} - Start` + '\n'); } else { const file = fs.readFileSync(fileName).toString(); if (file.indexOf(`v.${info.version}`) !== -1) { console.log('Skipped'); return; } fs.appendFile(fileName, `v.${info.version}: ${new Date()} - ${info.description}` + '\n', (err) => { if (err) { console.error(err); } else { console.log('Recorded'); } }); } }); sails.log('Finished custom shell script... (`sails run versioning`)'); } };
# Para instalar a imagem mais atualizada... # FROM registry.ccarj.intraer/mirror/library/node:lts-alpine AS build # # Para instalar a imagem compativel com a versao de 'geracao' do app # FROM node:16.15.1 AS build FROM node:16.15.1 AS build # Definicao do diretorio de trabalho WORKDIR /usr/local/app # Transferencia do codigo fonte para a imagem COPY ./ /usr/local/app/ # Instalacao de todas as dependencias RUN npm install # Atribuicao de permissoes de execucao USER node COPY . . COPY --chown=node:node . . # Exposicao da porta 80 EXPOSE 80:80 # Execucao em ambiente de producao ENV NODE_ENV=production CMD [ "node", "./app.js" ]