Update to latest @extollo/lib & add contact API
This commit is contained in:
@@ -10,7 +10,7 @@ import {
|
||||
file,
|
||||
Application,
|
||||
make,
|
||||
Valid, Logging,
|
||||
Valid, Logging, api,
|
||||
} from '@extollo/lib'
|
||||
import {WorkItem} from '../../models/WorkItem.model'
|
||||
import {FeedPost} from '../../models/FeedPost.model'
|
||||
@@ -111,13 +111,21 @@ export class Home extends Controller {
|
||||
.read()
|
||||
}
|
||||
|
||||
async contact(data: Valid<ContactForm>) {
|
||||
// If the request has an "e-mail" field, then this was likely filled out by a spam
|
||||
// bot, as this field is hidden on the form. So, reject it.
|
||||
if ( this.request.input('e-mail') ) {
|
||||
data.name = `SPAM: ${data.name}` // for testing, just alter the name
|
||||
}
|
||||
async contact(data: ContactForm) {
|
||||
await this.sendContact(data)
|
||||
return view('message', {
|
||||
title: 'Message Sent',
|
||||
message: 'Your message has been sent. Thanks! I\'ll be in touch soon.',
|
||||
buttonAction: this.routing.getNamedPath('home').toRemote,
|
||||
})
|
||||
}
|
||||
|
||||
async contactApi(data: ContactForm) {
|
||||
await this.sendContact(data)
|
||||
return api.one({})
|
||||
}
|
||||
|
||||
protected async sendContact(data: ContactForm) {
|
||||
const submission = make<ContactSubmission>(ContactSubmission)
|
||||
submission.name = data.name
|
||||
submission.email = data.email
|
||||
@@ -135,11 +143,6 @@ export class Home extends Controller {
|
||||
data.message,
|
||||
].join('\n'),
|
||||
}).then(x => this.logging.debug(x))
|
||||
|
||||
return view('message', {
|
||||
title: 'Message Sent',
|
||||
message: 'Your message has been sent. Thanks! I\'ll be in touch soon.',
|
||||
buttonAction: this.routing.getNamedPath('home').toRemote,
|
||||
})
|
||||
.catch(e => this.logging.error(e))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
Maybe,
|
||||
QueryRow,
|
||||
} from '@extollo/lib'
|
||||
import {FieldDefinition, FieldType, ResourceAction, ResourceConfiguration} from '../../../cobalt'
|
||||
import {FieldDefinition, FieldType, ResourceAction, ResourceConfiguration, SelectOptions} from '../../../cobalt'
|
||||
|
||||
const parser = require('any-date-parser')
|
||||
|
||||
@@ -197,7 +197,7 @@ export class ResourceAPI extends Controller {
|
||||
}
|
||||
return cast
|
||||
} else if ( type === FieldType.select && hasOwnProperty(fieldDef, 'options') ) {
|
||||
const options = collect(fieldDef.options)
|
||||
const options = collect(fieldDef.options as SelectOptions)
|
||||
if ( options.pluck('value').includes(value) ) {
|
||||
return value
|
||||
}
|
||||
|
||||
23
src/app/http/middlewares/RateLimit.middleware.ts
Normal file
23
src/app/http/middlewares/RateLimit.middleware.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import {Cache, http, HTTPStatus, Inject, Injectable, Middleware} from '@extollo/lib'
|
||||
|
||||
/**
|
||||
* RateLimit Middleware
|
||||
* --------------------------------------------
|
||||
* Limits a route to one request / 30 seconds / IP address.
|
||||
*/
|
||||
@Injectable()
|
||||
export class RateLimit extends Middleware {
|
||||
@Inject()
|
||||
protected readonly cache!: Cache
|
||||
|
||||
public async apply() {
|
||||
const slug = `extollo__rate_limit__${this.request.path}__${this.request.address.address}`
|
||||
if ( await this.cache.has(slug) ) {
|
||||
return http(HTTPStatus.TOO_MANY_REQUESTS)
|
||||
}
|
||||
|
||||
const date = new Date()
|
||||
date.setSeconds(date.getSeconds() + 30) // one request / 30 seconds
|
||||
await this.cache.put(slug, slug, date)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,14 @@
|
||||
import {ParameterMiddleware, Injectable, Either, ResponseObject, Validator, Valid, right} from '@extollo/lib'
|
||||
import {
|
||||
Either, hasOwnProperty,
|
||||
http,
|
||||
HTTPStatus,
|
||||
Inject,
|
||||
Injectable,
|
||||
left, Logging,
|
||||
ParameterMiddleware,
|
||||
ResponseObject,
|
||||
right,
|
||||
} from '@extollo/lib'
|
||||
import {ContactForm} from '../../../types/ContactForm.type'
|
||||
|
||||
/**
|
||||
@@ -7,9 +17,20 @@ import {ContactForm} from '../../../types/ContactForm.type'
|
||||
* Parse the contact form data and validate it. Provide the fields as middleware.
|
||||
*/
|
||||
@Injectable()
|
||||
export class ValidContactForm extends ParameterMiddleware<Valid<ContactForm>> {
|
||||
async handle(): Promise<Either<ResponseObject, Valid<ContactForm>>> {
|
||||
const validator = new Validator<ContactForm>()
|
||||
return right(validator.parse(this.request.input()))
|
||||
export class ValidContactForm extends ParameterMiddleware<ContactForm> {
|
||||
@Inject()
|
||||
protected readonly logging!: Logging
|
||||
|
||||
async handle(): Promise<Either<ResponseObject, ContactForm>> {
|
||||
const allInput = this.request.input()
|
||||
if ( typeof allInput !== 'object' || allInput === null || !hasOwnProperty(allInput, 'e-mail') || allInput['e-mail'] ) {
|
||||
return left(http(HTTPStatus.UNAUTHORIZED))
|
||||
}
|
||||
|
||||
return right({
|
||||
email: this.request.safe('email').present().string(),
|
||||
name: this.request.safe('name').present().string(),
|
||||
message: this.request.safe('message').present().string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {Feed} from '../controllers/Feed.controller'
|
||||
import {LoadSnippet} from '../middlewares/parameters/LoadSnippet.middleware'
|
||||
import {LoadFeedPosts} from '../middlewares/parameters/LoadFeedPosts.middleware'
|
||||
import {ValidContactForm} from '../middlewares/parameters/ValidContactForm.middleware'
|
||||
import {RateLimit} from '../middlewares/RateLimit.middleware'
|
||||
|
||||
Route
|
||||
.group('/', () => {
|
||||
@@ -19,10 +20,17 @@ Route
|
||||
.handledBy(() => redirect('/blog/'))
|
||||
|
||||
Route.post('/contact')
|
||||
.pre(RateLimit)
|
||||
.parameterMiddleware(ValidContactForm)
|
||||
.calls<Home>(Home, home => home.contact)
|
||||
.alias('contact')
|
||||
|
||||
Route.post('/api/contact')
|
||||
.pre(RateLimit)
|
||||
.parameterMiddleware(ValidContactForm)
|
||||
.calls<Home>(Home, home => home.contactApi)
|
||||
.alias('contact-api')
|
||||
|
||||
Route.get('/humans.txt')
|
||||
.calls<Home>(Home, home => home.humans)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Application, CommandLineApplication} from '@extollo/lib'
|
||||
import {Application, CommandLineApplication, Foreground} from '@extollo/lib'
|
||||
import {Units} from './Units.extollo'
|
||||
|
||||
/*
|
||||
@@ -22,12 +22,7 @@ export function cli(): Application {
|
||||
const app = Application.getApplication()
|
||||
app.forceStartupMessage = false
|
||||
|
||||
const units = [...Units]
|
||||
|
||||
units.reverse()
|
||||
CommandLineApplication.setReplacement(units[0])
|
||||
units[0] = CommandLineApplication
|
||||
units.reverse()
|
||||
const units = [...Units, CommandLineApplication]
|
||||
|
||||
app.scaffold(__dirname, units)
|
||||
return app
|
||||
@@ -38,6 +33,10 @@ export function cli(): Application {
|
||||
*/
|
||||
export function app(): Application {
|
||||
const app = Application.getApplication()
|
||||
app.scaffold(__dirname, Units)
|
||||
|
||||
const units = [...Units]
|
||||
units.push(Foreground)
|
||||
|
||||
app.scaffold(__dirname, units)
|
||||
return app
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user