Add go links and minor refactoring

This commit is contained in:
2022-04-05 10:47:15 -05:00
parent 22c2b9f665
commit 3142d0a4be
15 changed files with 238 additions and 65 deletions

View File

@@ -0,0 +1,8 @@
import {env, RedisOptions} from '@extollo/lib'
export default {
connection: {
port: env('REDIS_PORT', 6379),
host: env('REDIS_HOST', '127.0.0.1'),
} as RedisOptions
}

View File

@@ -0,0 +1,16 @@
import {Controller, view, Injectable, Collection} from '@extollo/lib'
import {FeedPost} from '../../models/FeedPost.model'
/**
* Feed Controller
* ------------------------------------
* Backend for routes related to my post feed.
*/
@Injectable()
export class Feed extends Controller {
public async feed(feedPosts: Collection<FeedPost>) {
return view('feed', {
feedPosts: feedPosts.toArray(),
})
}
}

View File

@@ -0,0 +1,22 @@
import {Controller, view, Inject, Injectable, HTTPStatus, http, redirect} from '@extollo/lib'
import {GoLink} from '../../models/GoLink.model'
/**
* GoLinks Controller
*/
@Injectable()
export class GoLinks extends Controller {
async launch() {
const short = this.request.safe('short').string()
const link = await GoLink.query<GoLink>()
.where('active', '=', true)
.where('short', '=', short)
.first()
if ( !link ) {
return http(HTTPStatus.http404, 'Invalid or expired link.')
}
return redirect(link.url)
}
}

View File

@@ -13,9 +13,8 @@ export class Home extends Controller {
@Inject()
protected readonly routing!: Routing
public async welcome() {
public async welcome(feedPosts: Collection<FeedPost>) {
const workItems = await this.getWorkItems()
const feedPosts = await this.getFeedPosts()
return view('welcome', {
feedPosts: feedPosts.toArray(),
@@ -30,13 +29,6 @@ export class Home extends Controller {
})
}
public async feed() {
const feedPosts = await this.getFeedPosts(true)
return view('feed', {
feedPosts: feedPosts.toArray(),
})
}
public technical() {
const isOptOut = this.request.cookies.has(this.config.get('app.analytics.optOutCookie'))
@@ -73,21 +65,6 @@ export class Home extends Controller {
})
}
protected async getFeedPosts(all = false): Promise<Collection<FeedPost>> {
const query = FeedPost.query<FeedPost>()
.orderByDescending('posted_at')
if ( !all ) {
query.limit(6)
}
if ( !this.security.hasUser() ) {
query.where('visible', '=', true)
}
return query.get().collect()
}
protected async getWorkItems(): Promise<Collection<WorkItem>> {
const query = WorkItem.query<WorkItem>()
.orderByDescending('start_date')

View File

@@ -4,23 +4,14 @@ import {Snippet} from '../../models/Snippet.model'
/**
* Snippets Controller
* ------------------------------------
* Put some description here.
* Backend for routes that deal with code snippets.
*/
@Injectable()
export class Snippets extends Controller {
@Inject()
protected readonly logging!: Logging
public async viewSnippet() {
const slug = this.request.safe('slug').string()
const snippet = await Snippet.query<Snippet>()
.where('slug', '=', slug)
.first()
if ( !snippet ) {
return http(HTTPStatus.http404, 'Snippet URL is invalid.')
}
public async viewSnippet(snippet: Snippet) {
const needsAccessKey = snippet?.accessKey && snippet.accessKey !== this.request.input('accessKey')
const needsConfirm = snippet?.singleAccessOnly && !this.request.input('confirmSingleAccess')

View File

@@ -0,0 +1,28 @@
import {Injectable, ParameterMiddleware, Collection, SecurityContext, Either, ResponseObject, right, Inject} from '@extollo/lib'
import {FeedPost} from '../../../models/FeedPost.model'
/**
* LoadFeedPosts Middleware
* --------------------------------------------
* Load feed posts visible to the user as a handler parameter.
*/
@Injectable()
export class LoadFeedPosts extends ParameterMiddleware<Collection<FeedPost>, [] | [{ all: boolean }]> {
@Inject()
protected readonly security!: SecurityContext
async handle({ all = false } = {}): Promise<Either<ResponseObject, Collection<FeedPost>>> {
const query = FeedPost.query<FeedPost>()
.orderByDescending('posted_at')
if ( !all ) {
query.limit(6)
}
if ( !this.security.hasUser() ) {
query.where('visible', '=', true)
}
return right(await query.get().collect())
}
}

View File

@@ -0,0 +1,40 @@
import {
Either,
http,
HTTPStatus,
Inject,
Injectable,
left,
ParameterMiddleware,
ResponseObject, right,
SecurityContext,
} from '@extollo/lib'
import {Snippet} from '../../../models/Snippet.model'
/**
* LoadSnippet Middleware
* --------------------------------------------
* Look up the Snippet instance from the request parameters.
*/
@Injectable()
export class LoadSnippet extends ParameterMiddleware<Snippet> {
@Inject()
protected readonly security!: SecurityContext
async handle(): Promise<Either<ResponseObject, Snippet>> {
const slug = String(this.request.input('slug') || '')
if ( !slug ) {
return left(http(HTTPStatus.http404))
}
const snippet = await Snippet.query<Snippet>()
.where('slug', '=', slug)
.first()
if ( !snippet || snippet.usersOnly && !this.security.hasUser() ) {
return left(http(HTTPStatus.http404))
}
return right(snippet)
}
}

View File

@@ -2,10 +2,15 @@ import {Route, SessionAuthMiddleware} from '@extollo/lib'
import {Home} from '../controllers/Home.controller'
import {PageView} from '../middlewares/PageView.middleware'
import {Snippets} from '../controllers/Snippets.controller'
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'
Route
.group('/', () => {
Route.get('/')
.parameterMiddleware(LoadFeedPosts)
.calls<Home>(Home, home => home.welcome)
.alias('home')
@@ -21,16 +26,16 @@ Route
.alias('opt-out')
Route.get('/feed')
.calls<Home>(Home, home => home.feed)
.parameterMiddleware(LoadFeedPosts, {all: true})
.calls<Feed>(Feed, feed => feed.feed)
.alias('feed')
Route.get('/snippet/:slug')
.parameterMiddleware(LoadSnippet)
.calls<Snippets>(Snippets, snippets => snippets.viewSnippet)
Route.get('/test')
.handledBy(() => {
return 'Hello, World!'
})
Route.any('/go/:short')
.calls<GoLinks>(GoLinks, go => go.launch)
})
.pre(SessionAuthMiddleware)
.pre(PageView)

View File

@@ -0,0 +1,49 @@
import {DatabaseService, FieldType, Inject, Injectable, Migration} from '@extollo/lib'
/**
* CreateGolinksTableMigration
* ----------------------------------
* Create Table to store URL redirections
*/
@Injectable()
export default class CreateGolinksTableMigration 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('go_links')
table.primaryKey('go_link_id').required()
table.column('active')
.type(FieldType.bool)
.default(true)
table.column('short')
.type(FieldType.varchar)
.required()
.unique()
table.column('url')
.type(FieldType.text)
.required()
await schema.commit(table)
}
/**
* Undo the migration.
*/
async down(): Promise<void> {
const schema = this.db.get().schema()
const table = await schema.table('go_links')
table.dropIfExists()
await schema.commit(table)
}
}

View File

@@ -0,0 +1,24 @@
import {Field, FieldType, Injectable, Model} from '@extollo/lib'
/**
* GoLink Model
* -----------------------------------
* A shortened URL.
*/
@Injectable()
export class GoLink extends Model<GoLink> {
protected static table = 'go_links'
protected static key = 'go_link_id'
@Field(FieldType.serial, 'go_link_id')
protected id!: number
@Field(FieldType.bool)
public active = true
@Field(FieldType.varchar)
public short!: string
@Field(FieldType.text)
public url!: string
}

View File

@@ -64,19 +64,6 @@ block content
p.text !{item.body}
.bottom
.stamp <a href="#{named('feed')}##{item.feedPostId}" class="feed-edit-button">permalink</a>&nbsp;&nbsp;|&nbsp;&nbsp;#{item.postedAt.toLocaleString()}
// each item in feed_items
// .feed-item
// div.feed-category(id='feedPostTag_' + item.id)
// .tag #{item.tag}
// span
// if item.draft
// p.text (draft)
// p.text !{item.text}
// .bottom
// if user && can_access.feed_delete && can_access.feed_edit
// .stamp <a href="/dash/c/form/FeedPost?id=#{item.id}" class="feed-edit-button" target="_blank">edit</a> | <a href="#recent" class="feed-delete-button" postid="#{item.id}">delete</a>
// .stamp <a href="#{app.url}feed##{item.id}" class="feed-edit-button">permalink</a>&nbsp;&nbsp;|&nbsp;&nbsp;#{item.date.toLocaleString()}
if feed_overflow
.row.mt-4
.col-12.text-center
a.button(href="/feed") view more
.row.mt-4
.col-12.text-center
a.button(href="/feed") view all