Add go links and minor refactoring
This commit is contained in:
parent
22c2b9f665
commit
3142d0a4be
@ -8,7 +8,6 @@ COPY lib/ /app
|
||||
|
||||
RUN rm -f /app/.env
|
||||
|
||||
COPY .env.docker /app/.env
|
||||
COPY package.json /app
|
||||
COPY pnpm-lock.yaml /app
|
||||
|
||||
|
12
ex
12
ex
@ -1,4 +1,4 @@
|
||||
#!/bin/bash -e
|
||||
#!/bin/bash
|
||||
|
||||
ENV_NODE="$(which node)"
|
||||
ENV_PNPM="$(which pnpm)"
|
||||
@ -134,7 +134,13 @@ if [ ! -d "./node_modules" ]; then
|
||||
printf "\033[32m✓\033[39m Looks like you're all set up! Run this command again to access the Extollo CLI.\n"
|
||||
else
|
||||
start_spinner "Building your app..."
|
||||
"$ENV_PNPM" run build > /dev/null
|
||||
stop_spinner 0
|
||||
BUILD_OUTPUT="$($ENV_PNPM run build 2>&1)"
|
||||
BUILD_EC=$?
|
||||
stop_spinner $BUILD_EC
|
||||
if [ $BUILD_EC -ne 0 ]; then
|
||||
printf "\033[31m✘\033[39m Uh, oh! Looks like your application failed to build. (exit: $BUILD_EC)"
|
||||
echo "$BUILD_OUTPUT"
|
||||
exit $BUILD_EC
|
||||
fi
|
||||
"$ENV_NODE" --experimental-repl-await ./lib/cli.js $@
|
||||
fi
|
||||
|
@ -8,8 +8,9 @@
|
||||
"lib": "lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@extollo/lib": "^0.9.21",
|
||||
"@extollo/lib": "^0.9.28",
|
||||
"copyfiles": "^2.4.1",
|
||||
"feed": "^4.2.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-expose-internals": "^4.5.4",
|
||||
"ts-patch": "^2.0.1",
|
||||
|
@ -2,8 +2,9 @@ lockfileVersion: 5.3
|
||||
|
||||
specifiers:
|
||||
'@extollo/cc': ^0.6.0
|
||||
'@extollo/lib': ^0.9.21
|
||||
'@extollo/lib': ^0.9.28
|
||||
copyfiles: ^2.4.1
|
||||
feed: ^4.2.2
|
||||
rimraf: ^3.0.2
|
||||
ts-expose-internals: ^4.5.4
|
||||
ts-patch: ^2.0.1
|
||||
@ -12,8 +13,9 @@ specifiers:
|
||||
zod: ^3.11.6
|
||||
|
||||
dependencies:
|
||||
'@extollo/lib': 0.9.21
|
||||
'@extollo/lib': 0.9.28
|
||||
copyfiles: 2.4.1
|
||||
feed: 4.2.2
|
||||
rimraf: 3.0.2
|
||||
ts-expose-internals: 4.5.4
|
||||
ts-patch: 2.0.1_typescript@4.3.2
|
||||
@ -110,8 +112,8 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@extollo/lib/0.9.21:
|
||||
resolution: {integrity: sha512-JKgUQWS9/lEho9ardayvZO8pkE4ZoPs4xuo6KAQDqp7wq4PjGjrGBKeHX7ViHeaFm/XelskaG9eu9Ox9RvirFQ==}
|
||||
/@extollo/lib/0.9.28:
|
||||
resolution: {integrity: sha512-lMI2FWOTKbRsqJrOAvZofzfz0DaNwWecYsDX98XkNbktaUjhaGc7xr1OVxscxUC/Edapyj/p02MLdsUoIsARxA==}
|
||||
dependencies:
|
||||
'@atao60/fse-cli': 0.1.7
|
||||
'@extollo/ui': 0.1.0_@types+node@14.18.12
|
||||
@ -987,6 +989,13 @@ packages:
|
||||
dependencies:
|
||||
reusify: 1.0.4
|
||||
|
||||
/feed/4.2.2:
|
||||
resolution: {integrity: sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
dependencies:
|
||||
xml-js: 1.6.11
|
||||
dev: false
|
||||
|
||||
/fetch-blob/3.1.5:
|
||||
resolution: {integrity: sha512-N64ZpKqoLejlrwkIAnb9iLSA3Vx/kjgzpcDhygcqJ2KKjky8nCgUQ+dzXtbrLaWZGZNmNfQTsiQ0weZ1svglHg==}
|
||||
engines: {node: ^12.20 || >= 14.13}
|
||||
@ -2001,6 +2010,10 @@ packages:
|
||||
/safer-buffer/2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
|
||||
/sax/1.2.4:
|
||||
resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==}
|
||||
dev: false
|
||||
|
||||
/semver/5.7.1:
|
||||
resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==}
|
||||
hasBin: true
|
||||
@ -2577,6 +2590,13 @@ packages:
|
||||
/wrappy/1.0.2:
|
||||
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
|
||||
|
||||
/xml-js/1.6.11:
|
||||
resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
sax: 1.2.4
|
||||
dev: false
|
||||
|
||||
/xtend/4.0.2:
|
||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||
engines: {node: '>=0.4'}
|
||||
|
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
|
||||
|
Loading…
Reference in New Issue
Block a user