Implement contact form backend

This commit is contained in:
2022-04-05 14:24:36 -05:00
parent e643bf6df0
commit e3c3b93818
12 changed files with 399 additions and 11 deletions

View File

@@ -1,9 +1,19 @@
import {ORMUser, Singleton, Unit} from '@extollo/lib'
import {Config, Inject, ORMUser, Singleton, Unit} from '@extollo/lib'
import {User} from './models/User.model'
import {Gotify} from 'gotify'
@Singleton()
export class AppUnit extends Unit {
@Inject()
protected readonly config!: Config
async up(): Promise<void> {
this.container().registerStaticOverride(ORMUser, User)
const gotify = new Gotify({
server: this.config.safe('gotify.server').string(),
})
this.container().registerSingletonInstance(Gotify, gotify)
}
}

View File

@@ -0,0 +1,6 @@
import { env } from '@extollo/lib'
export default {
server: env('GOTIFY_SERVER'),
app: env('GOTIFY_TOKEN'),
}

View File

@@ -1,6 +1,22 @@
import {Controller, view, Injectable, SecurityContext, Inject, Collection, Config, Routing, file, Application, plaintext} from '@extollo/lib'
import {
Controller,
view,
Injectable,
SecurityContext,
Inject,
Collection,
Config,
Routing,
file,
Application,
make,
Valid,
} from '@extollo/lib'
import {WorkItem} from '../../models/WorkItem.model'
import {FeedPost} from '../../models/FeedPost.model'
import {ContactForm} from '../../types/ContactForm.type'
import {ContactSubmission} from '../../models/ContactSubmission.model'
import {Gotify} from 'gotify'
@Injectable()
export class Home extends Controller {
@@ -13,6 +29,9 @@ export class Home extends Controller {
@Inject()
protected readonly routing!: Routing
@Inject()
protected readonly gotify!: Gotify
public async welcome(feedPosts: Collection<FeedPost>) {
const workItems = await this.getWorkItems()
@@ -88,4 +107,29 @@ export class Home extends Controller {
.appPath('resources', 'assets', 'humans.txt')
.read()
}
async contact(data: Valid<ContactForm>) {
const submission = make<ContactSubmission>(ContactSubmission)
submission.name = data.name
submission.email = data.email
submission.message = data.message
await submission.save()
this.gotify.send({
app: this.config.get('gotify.app'),
title: `Contact form submission from ${data.name}`,
message: [
`From: ${data.name}`,
`E-mail: ${data.email}`,
'Message:',
data.message,
].join('\n'),
})
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,
})
}
}

View File

@@ -0,0 +1,15 @@
import {ParameterMiddleware, Injectable, Either, ResponseObject, Validator, Valid, right} from '@extollo/lib'
import {ContactForm} from '../../../types/ContactForm.type'
/**
* ContactForm Middleware
* --------------------------------------------
* 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()))
}
}

View File

@@ -6,6 +6,7 @@ import {GoLinks} from '../controllers/GoLinks.controller'
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'
Route
.group('/', () => {
@@ -14,6 +15,11 @@ Route
.calls<Home>(Home, home => home.welcome)
.alias('home')
Route.post('/contact')
.parameterMiddleware(ValidContactForm)
.calls<Home>(Home, home => home.contact)
.alias('contact')
Route.get('/humans.txt')
.calls<Home>(Home, home => home.humans)

View File

@@ -0,0 +1,51 @@
import {Injectable, Migration, Inject, DatabaseService, FieldType, raw} from '@extollo/lib'
/**
* CreateContactSubmissionsTableMigration
* ----------------------------------
* Put some description here.
*/
@Injectable()
export default class CreateContactSubmissionsTableMigration extends Migration {
@Inject()
protected readonly db!: DatabaseService
/**
* Apply the migration.
*/
async up(): Promise<void> {
const schema = this.db.get().schema()
const table = await schema.table('contact_submissions')
table.primaryKey('contact_submission_id').required()
table.column('email')
.type(FieldType.varchar)
.required()
table.column('name')
.type(FieldType.varchar)
.required()
table.column('message')
.type(FieldType.text)
table.column('sent_at')
.type(FieldType.timestamp)
.default(raw('NOW()'))
await schema.commit(table)
}
/**
* Undo the migration.
*/
async down(): Promise<void> {
const schema = this.db.get().schema()
const table = await schema.table('contact_submissions')
table.dropIfExists()
await schema.commit(table)
}
}

View File

@@ -0,0 +1,27 @@
import {Field, FieldType, Injectable, Model} from '@extollo/lib'
/**
* ContactSubmission Model
* -----------------------------------
* A message submitted via the contact form on my website.
*/
@Injectable()
export class ContactSubmission extends Model<ContactSubmission> {
protected static table = 'contact_submissions'
protected static key = 'contact_submission_id'
@Field(FieldType.serial, 'contact_submission_id')
protected id?: number
@Field(FieldType.varchar)
public email!: string
@Field(FieldType.varchar)
public name!: string
@Field(FieldType.text)
public message!: string
@Field(FieldType.timestamp, 'sent_at')
public sentAt = new Date()
}

View File

@@ -40,13 +40,11 @@ block content
p I'd love to hear from you if you have questions or inquiries related to me or my projects. You can get in touch by text, e-mail, or using this form. I also occasionally share thoughts on my <a href="/blog">blog</a>.
p <b>E-mail:</b> <a href="mailto:shout@garrettmills.dev">shout@garrettmills.dev</a>
.form
form#contact-form
form#contact-form(method='post' action=named('contact'))
.form-group
input#contactEmail.form-control(type='email' name='email' placeholder='E-Mail Address' required)
.form-group
input#contactFirst.form-control(name='first' placeholder='First Name' required)
.form-group
input#contactLast.form-control(name='last' placeholder='Last Name' required)
input#contactFirst.form-control(name='name' placeholder='Name' required)
.form-group
textarea.form-control#contactMessage(name='message' placeholder='Message' required rows=6)
.form-group

View File

@@ -0,0 +1,12 @@
/** A contact form submission. */
export interface ContactForm {
/** @email */
email: string
/** The submitter's name */
name: string
/** The body of the contact form submission. */
message: string
}