diff --git a/package.json b/package.json index a295b3f..e35ba5f 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "lib": "lib" }, "dependencies": { - "@extollo/lib": "^0.9.28", + "@extollo/lib": "^0.9.31", "copyfiles": "^2.4.1", "feed": "^4.2.2", "rimraf": "^3.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d0c5809..537bd94 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,7 +2,7 @@ lockfileVersion: 5.3 specifiers: '@extollo/cc': ^0.6.0 - '@extollo/lib': ^0.9.28 + '@extollo/lib': ^0.9.31 copyfiles: ^2.4.1 feed: ^4.2.2 rimraf: ^3.0.2 @@ -13,7 +13,7 @@ specifiers: zod: ^3.11.6 dependencies: - '@extollo/lib': 0.9.28 + '@extollo/lib': 0.9.31 copyfiles: 2.4.1 feed: 4.2.2 rimraf: 3.0.2 @@ -112,8 +112,8 @@ packages: - supports-color dev: true - /@extollo/lib/0.9.28: - resolution: {integrity: sha512-lMI2FWOTKbRsqJrOAvZofzfz0DaNwWecYsDX98XkNbktaUjhaGc7xr1OVxscxUC/Edapyj/p02MLdsUoIsARxA==} + /@extollo/lib/0.9.31: + resolution: {integrity: sha512-sYtqWTL+hmkPZ44fwWdRsHK1081m98547r9snFm8i+ou13Npw3i4RBf8k+uFRUCayMM4PKVWqgo0bZJLvO4W1g==} dependencies: '@atao60/fse-cli': 0.1.7 '@extollo/ui': 0.1.0_@types+node@14.18.12 diff --git a/src/app/http/controllers/Feed.controller.ts b/src/app/http/controllers/Feed.controller.ts index 03b0e50..95f0459 100644 --- a/src/app/http/controllers/Feed.controller.ts +++ b/src/app/http/controllers/Feed.controller.ts @@ -1,5 +1,6 @@ -import {Controller, view, Injectable, Collection} from '@extollo/lib' +import {Controller, view, Injectable, Collection, Inject, Routing, plaintext} from '@extollo/lib' import {FeedPost} from '../../models/FeedPost.model' +import * as RSSFeed from 'feed' /** * Feed Controller @@ -8,9 +9,72 @@ import {FeedPost} from '../../models/FeedPost.model' */ @Injectable() export class Feed extends Controller { + @Inject() + protected readonly routing!: Routing + public async feed(feedPosts: Collection) { return view('feed', { feedPosts: feedPosts.toArray(), }) } + + public async rss(feedPosts: Collection) { + const feed = this.getFeed(feedPosts) + return plaintext(feed.rss2()).contentType('application/rss+xml; charset=UTF-8') + } + + public async atom(feedPosts: Collection) { + const feed = this.getFeed(feedPosts) + return plaintext(feed.atom1()).contentType('application/atom+xml; charset=UTF-8') + } + + public async json(feedPosts: Collection) { + const feed = this.getFeed(feedPosts) + return plaintext(feed.json1()).contentType('application/feed+json; charset=UTF-8') + } + + protected getFeed(feedPosts: Collection): RSSFeed.Feed { + const feed = new RSSFeed.Feed({ + title: 'Garrett Mills', + description: 'A sporadic collection of my thoughts, blog posts, and project updates', + id: `${this.routing.getAppUrl()}#about`, + link: this.routing.getNamedPath('feed').toRemote, + language: 'en', + image: this.routing.getAssetPath('favicon', 'apple-touch-icon.png').toRemote, + favicon: this.routing.getAssetPath('favicon', 'favicon.ico').toRemote, + copyright: `Copyright (c) ${(new Date).getFullYear()} Garrett Mills. See website for licensing details.`, + updated: feedPosts.whereMax('postedAt').first()?.postedAt, + generator: '', + feedLinks: { + json: this.routing.getNamedPath('feed.json').toRemote, + atom: this.routing.getNamedPath('feed.atom').toRemote, + rss: this.routing.getNamedPath('feed.rss').toRemote, + }, + author: { + name: 'Garrett Mills', + email: 'shout@garrettmills.dev', + link: 'https://garrettmills.dev/#about', + }, + }) + + feed.addCategory('Technology') + feed.addCategory('Software Development') + + feedPosts.each(post => { + feed.addItem({ + title: post.tag, // FIXME better title generation + date: post.postedAt, + id: `${this.routing.getNamedPath('feed')}#${post.feedPostId}`, + link: `${this.routing.getNamedPath('feed')}#${post.feedPostId}`, + content: post.body, + author: [{ + name: 'Garrett Mills', + email: 'shout@garrettmills.dev', + link: 'https://garrettmills.dev/#about', + }], + }) + }) + + return feed + } } diff --git a/src/app/http/controllers/Home.controller.ts b/src/app/http/controllers/Home.controller.ts index 3ce1e24..44c7f7d 100644 --- a/src/app/http/controllers/Home.controller.ts +++ b/src/app/http/controllers/Home.controller.ts @@ -1,4 +1,4 @@ -import {Controller, view, Injectable, SecurityContext, Inject, Collection, Config, Routing, file, Application} from '@extollo/lib' +import {Controller, view, Injectable, SecurityContext, Inject, Collection, Config, Routing, file, Application, plaintext} from '@extollo/lib' import {WorkItem} from '../../models/WorkItem.model' import {FeedPost} from '../../models/FeedPost.model' @@ -82,4 +82,10 @@ export class Home extends Controller { .appPath('resources', 'assets', 'favicon', 'favicon.ico') ) } + + humans() { + return Application.getApplication() + .appPath('resources', 'assets', 'humans.txt') + .read() + } } diff --git a/src/app/http/routes/app.routes.ts b/src/app/http/routes/app.routes.ts index 740c8e2..db03456 100644 --- a/src/app/http/routes/app.routes.ts +++ b/src/app/http/routes/app.routes.ts @@ -14,6 +14,9 @@ Route .calls(Home, home => home.welcome) .alias('home') + Route.get('/humans.txt') + .calls(Home, home => home.humans) + Route.get('/technical') .calls(Home, home => home.technical) @@ -25,11 +28,6 @@ Route .calls(Home, home => home.optOut) .alias('opt-out') - Route.get('/feed') - .parameterMiddleware(LoadFeedPosts, {all: true}) - .calls(Feed, feed => feed.feed) - .alias('feed') - Route.get('/snippet/:slug') .parameterMiddleware(LoadSnippet) .calls(Snippets, snippets => snippets.viewSnippet) @@ -39,6 +37,28 @@ Route Route.get('/favicon.ico') .calls(Home, home => home.favicon) + + Route.group('feed', () => { + Route.get('/') + .parameterMiddleware(LoadFeedPosts, {all: true}) + .calls(Feed, feed => feed.feed) + .alias('feed') + + Route.get('/rss.xml') + .parameterMiddleware(LoadFeedPosts, {all: true}) + .calls(Feed, feed => feed.rss) + .alias('feed.rss') + + Route.get('/atom.xml') + .parameterMiddleware(LoadFeedPosts, {all: true}) + .calls(Feed, feed => feed.atom) + .alias('feed.atom') + + Route.get('/json.json') + .parameterMiddleware(LoadFeedPosts, {all: true}) + .calls(Feed, feed => feed.json) + .alias('feed.json') + }) }) .pre(SessionAuthMiddleware) .pre(PageView) diff --git a/src/app/resources/assets/humans.txt b/src/app/resources/assets/humans.txt new file mode 100644 index 0000000..66f3185 --- /dev/null +++ b/src/app/resources/assets/humans.txt @@ -0,0 +1,16 @@ +/* PEOPLE */ + Garrett Mills - Developer/Speaker/Designer + E-Mail: shout@garrettmills.dev + Twitter: @glmdev + From: Lawrence, Kansas, USA (Rock Chalk!) + +/* SITE */ + Updated: 2022-04-05 + Language: English + Doctype: HTML5 + This site was built with Extollo, a free & libre application framework. + Learn more at: https://extollo.garrettmills.dev/ + Copyright (C) 2022 Garrett Mills. All Rights Reserved. + +/* OTHER */ +Just a little something for the humans scraping the web... -GM diff --git a/src/app/resources/views/technical.pug b/src/app/resources/views/technical.pug index 2a04e9a..13575e0 100644 --- a/src/app/resources/views/technical.pug +++ b/src/app/resources/views/technical.pug @@ -10,6 +10,12 @@ block content .fira-p p This page contains a smattering of technical information that I think some people might find interesting, but not enough people for it to be included on the main page. + h3 Source Code & Licensing + a(href='https://creativecommons.org/licenses/by-nc-sa/4.0/' target='_blank' style='margin-top: 15px') + img(src=asset('cc-by-nc-sa.png')) + p This website, its source code, and its contents are licensed under the terms of the Creative Commons BY-NC-SA 4.0 license. Learn more here. + p The source code for this site is available openly under the terms of the aforementioned license. You can view it here. + h3 Framework p This site is built with Extollo, my free & libre application framework. You can learn more about Extollo here. diff --git a/src/app/resources/views/template_raj.pug b/src/app/resources/views/template_raj.pug index e92e0d3..55ccb26 100644 --- a/src/app/resources/views/template_raj.pug +++ b/src/app/resources/views/template_raj.pug @@ -15,9 +15,10 @@ head block style link(rel='stylesheet' href=asset('main.css')) - link(rel='author' href='humans.txt') + link(rel='author' href='/humans.txt') link(rel="alternate" href="/feed/atom.xml" title="Garrett Mills - Posts & Updates" type="application/atom+xml") link(rel="alternate" href="/feed/rss.xml" title="Garrett Mills - Posts & Updates" type="application/rss+xml") + link(rel="alternate" href="/feed/json.json" title="Garrett Mills - Posts & Updates" type="application/feed+json") link(rel='apple-touch-icon' sizes='180x180' href=asset('favicon/apple-touch-icon.png')) link(rel='manifest' href=asset('favicon/site.webmanifest')) @@ -34,8 +35,8 @@ body img(src=asset('ffox.png') width="75" style="margin-top: -1px") .by-line garrettmills .copy#tagline(title="my, aren't you curious...") copyright © #{(new Date()).getFullYear()} garrett mills - .copyright - a(href='https://creativecommons.org/licenses/by-nc-sa/4.0/' target='_blank') + .copyright(style='display: flex; justify-content: right') + a(style='display: flex; padding-top: 10px' href='https://creativecommons.org/licenses/by-nc-sa/4.0/' target='_blank') img(src=asset('cc-by-nc-sa-small.png') title='This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License') if false div.auth-container