Add go links and minor refactoring
This commit is contained in:
8
src/app/configs/redis.config.ts
Normal file
8
src/app/configs/redis.config.ts
Normal 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
|
||||
}
|
||||
16
src/app/http/controllers/Feed.controller.ts
Normal file
16
src/app/http/controllers/Feed.controller.ts
Normal 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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
22
src/app/http/controllers/GoLinks.controller.ts
Normal file
22
src/app/http/controllers/GoLinks.controller.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
24
src/app/models/GoLink.model.ts
Normal file
24
src/app/models/GoLink.model.ts
Normal 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
|
||||
}
|
||||
@@ -64,19 +64,6 @@ block content
|
||||
p.text !{item.body}
|
||||
.bottom
|
||||
.stamp <a href="#{named('feed')}##{item.feedPostId}" class="feed-edit-button">permalink</a> | #{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> | #{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
|
||||
|
||||
Reference in New Issue
Block a user