feat: add api support and auth
This commit is contained in:
parent
31e4b854db
commit
a244d98806
21 changed files with 585 additions and 63 deletions
18
src/EmailService.ts
Normal file
18
src/EmailService.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import nodemailer from 'nodemailer'
|
||||
|
||||
export default class EmailService {
|
||||
static getTransporter() {
|
||||
if (process.env.SMTP_HOST == undefined || process.env.SMTP_PORT == undefined) {
|
||||
throw new Error("Invalid SMTP config")
|
||||
}
|
||||
return nodemailer.createTransport({
|
||||
host: process.env.SMTP_HOST,
|
||||
port: parseInt(process.env.SMTP_PORT),
|
||||
secure: process.env.SMTP_SECURE == 'true',
|
||||
auth: {
|
||||
user: process.env.SMTP_USERNAME,
|
||||
pass: process.env.SMTP_PASSWORD,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
51
src/IconService.ts
Normal file
51
src/IconService.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import * as faSolid from '@fortawesome/free-solid-svg-icons'
|
||||
import * as faBrands from '@fortawesome/free-brands-svg-icons'
|
||||
import * as faRegular from '@fortawesome/free-regular-svg-icons'
|
||||
import { IconDefinition } from '@fortawesome/fontawesome-common-types'
|
||||
|
||||
export interface IconInterface {
|
||||
id: string,
|
||||
width: number,
|
||||
height: number,
|
||||
path: string
|
||||
}
|
||||
|
||||
export class IconService {
|
||||
|
||||
static snakeToCamel(str: string): string {
|
||||
return str.replace(
|
||||
/([-_][a-z])/g,
|
||||
group => group.toUpperCase().replace('-', '').replace('_', '')
|
||||
)
|
||||
}
|
||||
|
||||
static getIcon(id: string): IconInterface|null {
|
||||
let components: string[] = id.split(id.indexOf('_') !== -1 ? '_' : ' ')
|
||||
if (components.length < 2) {
|
||||
return null
|
||||
}
|
||||
let iconSet: any = {}
|
||||
switch (components[0]) {
|
||||
case 'fas':
|
||||
iconSet = faSolid
|
||||
break
|
||||
case 'fab':
|
||||
iconSet = faBrands
|
||||
break
|
||||
case 'far':
|
||||
iconSet = faRegular
|
||||
break
|
||||
}
|
||||
let iconName: string = this.snakeToCamel(components[1])
|
||||
let iconVal: IconDefinition|undefined = iconSet[iconName]
|
||||
if (iconVal === undefined) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
id: components[0] + ' ' + components[1],
|
||||
width: iconVal.icon[0],
|
||||
height: iconVal.icon[1],
|
||||
path: iconVal.icon[4] as string
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/RedisService.ts
Normal file
13
src/RedisService.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import Redis from 'ioredis'
|
||||
import IORedis from 'ioredis'
|
||||
|
||||
export default class RedisService {
|
||||
|
||||
static getClient(): IORedis.Redis {
|
||||
return new IORedis(
|
||||
parseInt(process.env.REDIS_PORT === undefined ? '6379' : process.env.REDIS_PORT),
|
||||
process.env.REDIS_HOST === undefined ? "127.0.0.1" : process.env.REDIS_HOST,
|
||||
{ password: process.env.REDIS_PASSWORD }
|
||||
)
|
||||
}
|
||||
}
|
||||
64
src/app.ts
64
src/app.ts
|
|
@ -3,9 +3,15 @@ import bodyParser from 'body-parser'
|
|||
import express from 'express'
|
||||
import mongoose from 'mongoose'
|
||||
import AdminTagController from './controllers/AdminTagController'
|
||||
import AdminOrganizationController from './controllers/AdminOrganizationController'
|
||||
import DefaultController from './controllers/DefaultController'
|
||||
import MediaController from './controllers/MediaController'
|
||||
import dotenv from 'dotenv'
|
||||
import DelegateController from './controllers/DelegateController'
|
||||
import AdminAuthMiddleware from './middlewares/AdminAuthMiddleware'
|
||||
import DelegateAuthMiddleware from './middlewares/DelegateAuthMiddleware'
|
||||
import PublicController from './controllers/PublicController'
|
||||
import cors from 'cors'
|
||||
|
||||
console.log('WOOWOWO')
|
||||
|
||||
|
|
@ -32,20 +38,62 @@ let main = async () => {
|
|||
console.log('> Connected to mongodb')
|
||||
})
|
||||
|
||||
app.use(cors())
|
||||
app.use(bodyParser.json())
|
||||
|
||||
app.get('/', DefaultController.home)
|
||||
app.get('/association/:slug', DefaultController.publicOrganization)
|
||||
|
||||
app.use((err: any, req: express.Request, res: express.Response, next: express.RequestHandler) => {
|
||||
console.error(err.stack)
|
||||
res.status(res.statusCode).json({ success: false, error: err.stack })
|
||||
})
|
||||
|
||||
app.get('/', PublicController.home)
|
||||
app.get('/association/:slug', PublicController.organization)
|
||||
|
||||
app.get('/icon/:id', DefaultController.viewIcon)
|
||||
app.get('/email', DefaultController.sendEmail)
|
||||
|
||||
app.get('/api/tags', AdminTagController.getTags)
|
||||
app.put('/api/tags/:id', AdminTagController.updateTag)
|
||||
app.post('/api/tags', AdminTagController.storeTag)
|
||||
app.delete('/api/tags/:id', AdminTagController.destroyTag)
|
||||
app.use('/admin', express.Router()
|
||||
.use(AdminAuthMiddleware.handle)
|
||||
.get('/', DefaultController.success)
|
||||
.use('/tags', express.Router()
|
||||
.get('/', AdminTagController.getMany)
|
||||
.post('/', AdminTagController.store)
|
||||
.put('/:id', AdminTagController.update)
|
||||
.delete('/:id', AdminTagController.destroy)
|
||||
)
|
||||
.use('/organizations', express.Router()
|
||||
.get('/', AdminOrganizationController.getMany)
|
||||
.get('/:id', AdminOrganizationController.getOne)
|
||||
.post('', AdminOrganizationController.store)
|
||||
.put('/:id', AdminOrganizationController.update)
|
||||
.delete('/:id', AdminOrganizationController.destroy)
|
||||
.post('/:id/reset-token', AdminOrganizationController.resetToken)
|
||||
.post('/:id/approve', AdminOrganizationController.approve)
|
||||
.post('/:id/reject', AdminOrganizationController.reject)
|
||||
)
|
||||
)
|
||||
|
||||
app.use('/delegate', express.Router()
|
||||
.use(DelegateAuthMiddleware.handle)
|
||||
.get('/', DelegateController.get)
|
||||
.put('/', DelegateController.update)
|
||||
.put('/submit', DelegateController.submit)
|
||||
.delete('/', DelegateController.destroy)
|
||||
)
|
||||
|
||||
/*
|
||||
|
||||
.put('/tags/:id', AdminTagController.update)
|
||||
.put('/tags/:id', AdminTagController.destroy)
|
||||
.use('/organizations', () => express.Router()
|
||||
.get('/', AdminOrganizationController.getMany)
|
||||
)
|
||||
*/
|
||||
|
||||
|
||||
app.post('/api/upload', MediaController.uploadRoute())
|
||||
|
||||
app.listen(port, host, () => {
|
||||
app.listen(port, host, () => {
|
||||
console.log(`API listening on ${host}:${port}`)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,79 @@
|
|||
import * as express from 'express'
|
||||
import Organization from '../models/Organization'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
|
||||
export default class AdminOrganizationController {
|
||||
static getOrganizations(req: express.Request, res: express.Response) {
|
||||
static getMany(req: express.Request, res: express.Response) {
|
||||
Organization.find().then(data => {
|
||||
res.json(data)
|
||||
})
|
||||
}
|
||||
|
||||
static getOne(req: express.Request, res: express.Response) {
|
||||
Organization.findById(req.params.id).then(data => {
|
||||
res.json(data)
|
||||
})
|
||||
}
|
||||
|
||||
static store(req: express.Request, res: express.Response) {
|
||||
Organization.create({
|
||||
token: cryptoRandomString({ length: 10, type: 'distinguishable' }),
|
||||
...req.body
|
||||
}).then(data => {
|
||||
res.json({
|
||||
success: true,
|
||||
data
|
||||
})
|
||||
}).catch(err => {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
errors: err.errors
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
static update(req: express.Request, res: express.Response) {
|
||||
Organization.updateOne({ _id: req.params.id }, req.body).then(data => {
|
||||
res.json({
|
||||
success: true,
|
||||
data
|
||||
})
|
||||
}).catch(err => {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
errors: err.errors !== undefined ? err.errors : err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
static destroy(req: express.Request, res: express.Response) {
|
||||
Organization.deleteOne({ _id: req.params.id }).then(data => {
|
||||
let isSuccess = data.deletedCount !== undefined && data.deletedCount > 0
|
||||
res.status(isSuccess ? 200 : 400).json({
|
||||
success: isSuccess,
|
||||
data
|
||||
})
|
||||
}).catch(err => {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
errors: err.errors
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
static sendEmailToken() {
|
||||
|
||||
}
|
||||
|
||||
static resetToken(req: express.Request, res: express.Response) {
|
||||
|
||||
}
|
||||
|
||||
static approve(req: express.Request, res: express.Response) {
|
||||
|
||||
}
|
||||
|
||||
static reject(req: express.Request, res: express.Response) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,26 @@
|
|||
import Tag from '../models/Tag'
|
||||
import express from 'express'
|
||||
import { IconService, IconInterface } from '../IconService'
|
||||
|
||||
export default class AdminTagController {
|
||||
static getTags(_: any, res: express.Response) {
|
||||
static getMany(_: any, res: express.Response) {
|
||||
Tag.find().then(data => {
|
||||
res.json(data)
|
||||
res.json({ success: true, data })
|
||||
})
|
||||
}
|
||||
|
||||
static storeTag(req: express.Request, res: express.Response) {
|
||||
static store(req: express.Request, res: express.Response) {
|
||||
// verify the icon
|
||||
let icon: IconInterface|null = IconService.getIcon(req.body.icon)
|
||||
if (icon === null) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
errors: [
|
||||
{code: 'invalid-icon', message: 'Invalid font awesome icon code'}
|
||||
]
|
||||
})
|
||||
}
|
||||
req.body.icon = icon
|
||||
Tag.create(req.body).then(data => {
|
||||
res.json({
|
||||
success: true,
|
||||
|
|
@ -22,30 +34,35 @@ export default class AdminTagController {
|
|||
})
|
||||
}
|
||||
|
||||
static updateTag(req: express.Request, res: express.Response) {
|
||||
static update(req: express.Request, res: express.Response) {
|
||||
let icon: IconInterface|null = IconService.getIcon(req.body.icon)
|
||||
if (icon === null) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
errors: [
|
||||
{code: 'invalid-icon', message: 'Invalid font awesome icon code'}
|
||||
]
|
||||
})
|
||||
}
|
||||
req.body.icon = icon
|
||||
Tag.updateOne({ _id: req.params.id }, req.body).then(data => {
|
||||
res.json({
|
||||
success: true,
|
||||
data
|
||||
})
|
||||
}).catch(err => {
|
||||
let errors
|
||||
if (err.errors !== undefined) {
|
||||
errors = err.errors
|
||||
} else {
|
||||
errors = err
|
||||
}
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
errors
|
||||
errors: err.errors !== undefined ? err.errors : err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
static destroyTag(req: express.Request, res: express.Response) {
|
||||
static destroy(req: express.Request, res: express.Response) {
|
||||
Tag.deleteOne({ _id: req.params.id }).then(data => {
|
||||
res.json({
|
||||
success: true,
|
||||
let isSuccess = data.deletedCount !== undefined && data.deletedCount > 0
|
||||
res.status(isSuccess ? 200 : 400).json({
|
||||
success: isSuccess,
|
||||
data
|
||||
})
|
||||
}).catch(err => {
|
||||
|
|
|
|||
|
|
@ -3,45 +3,65 @@ import * as express from 'express'
|
|||
import nodemailer from 'nodemailer'
|
||||
import Mustache from 'mustache'
|
||||
import fs from 'fs'
|
||||
import { IconService, IconInterface } from '../IconService'
|
||||
import EmailService from '../EmailService'
|
||||
|
||||
export default class DefaultController {
|
||||
static home(req: express.Request, res: express.Response) {
|
||||
// Organization.create({
|
||||
// admin_name: "hello",
|
||||
// email: 'spamfree@matthieubessat.fr',
|
||||
// token: 'dqsdsqdsq',
|
||||
// validationState: "ndsqqdsd"
|
||||
// }).then(data => {
|
||||
// console.log(data)
|
||||
// res.json({
|
||||
// success: true
|
||||
// })
|
||||
// }).catch(err => {
|
||||
// res.json({
|
||||
// success: false,
|
||||
// errors: err.errors
|
||||
// })
|
||||
// })
|
||||
static success(req: express.Request, res: express.Response) {
|
||||
res.json({
|
||||
success: true
|
||||
})
|
||||
}
|
||||
|
||||
static viewIcon(req: express.Request, res: express.Response) {
|
||||
// let components: string[] = req.params.id.split('_')
|
||||
// if (components.length < 2) {
|
||||
// return res
|
||||
// .status(400)
|
||||
// .json({ success: false, errors: [{ code: 'invalid-input', message: 'Invalid Input icon' }] })
|
||||
// }
|
||||
// components = [components[0].replace(' ', ''), components[1].replace(' ', '')]
|
||||
// let iconSet: any = {}
|
||||
// console.log(components[0], components[1])
|
||||
// if (components[0] === "fas") {
|
||||
// iconSet = faSolid
|
||||
// }
|
||||
// if (components[0] === "fab") {
|
||||
// iconSet = faBrands
|
||||
// }
|
||||
// if (components[0] === "far") {
|
||||
// iconSet = faRegular
|
||||
// }
|
||||
// let iconName: string = snakeToCamel(components[1])
|
||||
// let iconVal: IconDefinition|undefined = iconSet[iconName]
|
||||
let icon: IconInterface|null = IconService.getIcon(req.params.id)
|
||||
if (icon === null) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ success: false, errors: [{ code: 'invalid-icon', message: 'The icon is invalid' }] })
|
||||
}
|
||||
res.json({ success: true, data: icon })
|
||||
// res.setHeader('Content-Type', 'text/html')
|
||||
// return res.send(Mustache.render(`
|
||||
// <svg
|
||||
// style="width: {{width}}px; height: {{height}}px;"
|
||||
// aria-hidden="true"
|
||||
// focusable="false"
|
||||
// data-prefix="fas"
|
||||
// data-icon="camera"
|
||||
// class="svg-inline--fa fa-camera fa-w-16"
|
||||
// role="img"
|
||||
// xmlns="http://www.w3.org/2000/svg"
|
||||
// viewBox="0 0 {{ width }} {{ height }}">
|
||||
// <path fill="currentColor" d="{{ path }}"></path>
|
||||
// </svg>`, {...icon}))
|
||||
}
|
||||
|
||||
static async sendEmail(req: express.Request, res: express.Response) {
|
||||
// create reusable transporter object using the default SMTP transport
|
||||
let transporter = nodemailer.createTransport({
|
||||
host: "node02.cluster.stantabcorp.net",
|
||||
port: 587,
|
||||
secure: false,
|
||||
auth: {
|
||||
user: "mail@matthieubessat.fr",
|
||||
pass: "",
|
||||
},
|
||||
});
|
||||
|
||||
transporter.sendMail({
|
||||
from: '"Matthieu Bessat" <mail@matthieubessat.fr>',
|
||||
to: "matthieu.bessat.27@gmail.com",
|
||||
EmailService.getTransporter().sendMail({
|
||||
from: '"Some Sender" <noreply@somedomain.com>',
|
||||
to: "spamfree@matthieubessat.fr",
|
||||
subject: "Hello ✔",
|
||||
text: "Hello world? Comment va tu Earum facilis libero excepturi sunt fuga eveniet autem. Illo odit quae aperiam et praesentium. Error dignissimos atque omnis. Ea iste in doloribus praesentium corrupti. Ut consequatur eius eveniet quia aut. Nam a rerum quis. Repudiandae sit nobis esse. Eaque ipsum qui enim. Expedita laudantium officia omnis maxime. Odio exercitationem recusandae quis consequatur voluptatum.",
|
||||
html: "<p><b>Hello world?</b> Comment va tu Earum facilis libero excepturi sunt fuga eveniet autem. Illo odit quae aperiam et praesentium. Error dignissimos atque omnis. Ea iste in doloribus praesentium corrupti. Ut consequatur eius eveniet quia aut. Nam a rerum quis. Repudiandae sit nobis esse. Eaque ipsum qui enim. Expedita laudantium officia omnis maxime. Odio exercitationem recusandae quis consequatur voluptatum.</p>",
|
||||
|
|
@ -55,9 +75,9 @@ export default class DefaultController {
|
|||
return res.json({ success: true })
|
||||
}
|
||||
|
||||
static async publicOrganization(req: express.Request, res: express.Response) {
|
||||
res.setHeader('Content-Type', 'text/html')
|
||||
let path: string = __dirname + '/../../templates/organization.html'
|
||||
return res.send(Mustache.render(fs.readFileSync(path).toString(), {}))
|
||||
}
|
||||
// static async publicOrganization(req: express.Request, res: express.Response) {
|
||||
// res.setHeader('Content-Type', 'text/html')
|
||||
// let path: string = __dirname + '/../../templates/organization.html'
|
||||
// return res.send(Mustache.render(fs.readFileSync(path).toString(), {}))
|
||||
// }
|
||||
}
|
||||
21
src/controllers/DelegateController.ts
Normal file
21
src/controllers/DelegateController.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import Organization from '../models/Organization'
|
||||
import * as express from 'express'
|
||||
|
||||
export default class DelegateController {
|
||||
|
||||
static get(req: express.Request, res: express.Response) {
|
||||
res.json({ success: true, data: res.locals.organization })
|
||||
}
|
||||
|
||||
static update(req: express.Request, res: express.Response) {
|
||||
res.json({ success: true })
|
||||
}
|
||||
|
||||
static submit(req: express.Request, res: express.Response) {
|
||||
res.json({ success: true })
|
||||
}
|
||||
|
||||
static destroy(req: express.Request, res: express.Response) {
|
||||
res.json({ success: true })
|
||||
}
|
||||
}
|
||||
27
src/controllers/PublicController.ts
Normal file
27
src/controllers/PublicController.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
|
||||
import RedisService from '../RedisService'
|
||||
import Organization from '../models/Organization'
|
||||
import * as express from 'express'
|
||||
import nodemailer from 'nodemailer'
|
||||
import Mustache from 'mustache'
|
||||
import fs from 'fs'
|
||||
import { IconService, IconInterface } from '../IconService'
|
||||
import IORedis from 'ioredis'
|
||||
|
||||
export default class PublicController {
|
||||
|
||||
|
||||
static async home(req: express.Request, res: express.Response) {
|
||||
let client: IORedis.Redis = RedisService.getClient()
|
||||
await client.set('hello', 'world')
|
||||
res.json({
|
||||
data: await client.get('hello')
|
||||
})
|
||||
}
|
||||
|
||||
static async organization(req: express.Request, res: express.Response) {
|
||||
res.setHeader('Content-Type', 'text/html')
|
||||
let path: string = __dirname + '/../../templates/organization.html'
|
||||
return res.send(Mustache.render(fs.readFileSync(path).toString(), {}))
|
||||
}
|
||||
}
|
||||
17
src/middlewares/AdminAuthMiddleware.ts
Normal file
17
src/middlewares/AdminAuthMiddleware.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import express from 'express'
|
||||
|
||||
export default class AdminAuthMiddleware {
|
||||
static handle(req: express.Request, res: express.Response, next: any) {
|
||||
let auth: string | undefined = req.get('Authorization')
|
||||
if (auth === undefined || auth.replace('Bearer ', '') !== process.env.ADMIN_TOKEN) {
|
||||
res
|
||||
.status(400)
|
||||
.json({
|
||||
success: false,
|
||||
errors: { code: 'invalid-auth', message: 'Invalid admin Authorization header' }
|
||||
})
|
||||
return
|
||||
}
|
||||
next()
|
||||
}
|
||||
}
|
||||
23
src/middlewares/DelegateAuthMiddleware.ts
Normal file
23
src/middlewares/DelegateAuthMiddleware.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import express from 'express'
|
||||
import Organization from '../models/Organization'
|
||||
|
||||
export default class DelegateAuthMiddleware {
|
||||
static async handle(req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
let token: string | undefined = req.get('Authorization')
|
||||
// fetch the token
|
||||
if (token !== undefined) {
|
||||
let data = await Organization.findOne({ token: token.replace('Bearer ', '') })
|
||||
if (data !== null) {
|
||||
res.locals.organization = data
|
||||
next()
|
||||
return
|
||||
}
|
||||
}
|
||||
res
|
||||
.status(400)
|
||||
.json({
|
||||
success: false,
|
||||
errors: { code: 'invalid-auth', message: 'Invalid admin Authorization header' }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,6 @@ class AllowedString extends mongoose.SchemaType {
|
|||
}
|
||||
|
||||
cast(value: any) {
|
||||
console.log(value + ' is being validated')
|
||||
if (this.allowedValues.indexOf(value) === -1) {
|
||||
let validationStr: string = 'AllowedString: ' + value + ' must be one of which: [' + this.allowedValues.join(', ') + ']'
|
||||
console.log(validationStr)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
import mongoose, { Schema } from 'mongoose'
|
||||
|
||||
const Icon = new Schema({
|
||||
id: { type: String, required: true },
|
||||
width: { type: Number, required: true },
|
||||
height: { type: Number, required: true },
|
||||
path: { type: String, required: true }
|
||||
})
|
||||
|
||||
const _Tag = new Schema({
|
||||
name: { type: String, required: true },
|
||||
icon: { type: String, required: true },
|
||||
icon: { type: Icon, required: true },
|
||||
description: { type: String }
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue