diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..c9d85f5 --- /dev/null +++ b/.env.sample @@ -0,0 +1 @@ +DEPLOY_MODE="development" diff --git a/.gitignore b/.gitignore index 506e4c3..7021adb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ # deps node_modules/ +.env diff --git a/Dockerfile b/Dockerfile index d5c6b18..1d4a349 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,9 +4,9 @@ WORKDIR /app COPY bun.lockb . COPY package.json . +COPY tsconfig.json . RUN bun install --frozen-lockfile -RUN mkdir src posts COPY ./src ./src COPY ./posts ./posts diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..2acbbbe --- /dev/null +++ b/build.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +docker build --pull -t dsh-blog . --no-cache diff --git a/posts/fullstack-hono.md b/posts/fullstack-hono.md index 2e27c81..b52c4b8 100644 --- a/posts/fullstack-hono.md +++ b/posts/fullstack-hono.md @@ -11,7 +11,7 @@ slug: fullstack-hono draft: true --- -# Context and My Blog +## Context and My Blog I like my blog. I don't write nearly as much as I should for it but at the endof the day I like the three small posts that I have put into it. However, I never really liked how I had it set up. I have no real qualms with the SSG I chose ([11ty](https://www.11ty.dev/)) or the web server to host the HTML (NGiNX) but I never really felt like it was _mine_. @@ -27,7 +27,7 @@ But that made no sense once I _actually_ thought it. What advantage does having _Note:_ I am going to talk a lot about JavaScript and TypeScript, and I want to make it very clear that I am talking about running JavaScript on the server. I feel like when people talk about JavaScript, there is an assumption we are talking about building frontend SPAs and not high-IO server applications (for good reasons, honestly). However, in my career and experience, the bulk of JavaScript I have written runs on Node.js. Anyways, after such a long introduction, here is my blog post about what I learned building a very basic blog with Hono and Bun. -# Organizing Hono applications +## Organizing Hono applications I make no effort to hide the fact that I come from the MVC Web Framework world. I have spent a lot of time in my career thinking of web applications in the terms of Models, Views, and Controllers. This has made a lot of sense to me over the years, and I posit that it's still a great framework for organizing your code. However when it comes to JavaScript, it feels verbose to create classes full of methods to handle requests and responses. I think this verbosity comes from how JavaScript code is actually organized for Node.js (Deno, Bun, etc). @@ -37,7 +37,7 @@ So with my understanding that a Class in JavaScript should be a collection of me I like to call this pattern Handler, Service, Presentation. This pattern is nearly identical to MVC and you can immediately see the analogues to the original acronym. I don't think there is a clear advantage of using these words in particular, other than it can hopefully erode some of the _web-brainrot_ on how we organize our we applications. -## An Example of a Service +### An Example of a Service Let's think of a Blog. This blog. What services do we have in the code for this blog? Right now, it is solely the `PostService`. This is a class that is given a list of `Post` types, and creates an internal Map of that with the `slug` as a key. From there we can do things like, get all the posts, get the latest post, get a post by slugs, get un-published posts, etc. @@ -45,7 +45,7 @@ Within the `PostService` module is two helper functions. One of these is an `asy Another good way to think of a service, is a Repository class. Think of a class that should handle querying data to and from a database. If you need another example, image an HTTP Client for a specific HTTP API. Think of something that provides data to something else. I guess that's how I'd describe it. -## What Handlers Are +### What Handlers Are Handlers should be thought of as callback functions for particular requests and responses. The _handle_ the request and response. In the world of Hono, we get to decide what Middleware is type'd into the Application and can be accessed within a handler. This allows us to bootstrap our middleware elsewhere and be assured it will be there when it runs. @@ -95,7 +95,7 @@ _`src/handlers/posts.tsx`_ Now I haven't done this yet - but if I need to test the `handleSinglePost` function, I can properly mock the `Context` object with the right `PostService` class. -## How Presentation Works +### How Presentation Works Like I mentioned earlier, one of the selling points of using Hono for the framework was its suppose of rendering JSX with `jsxRenderer` middleware. Its trivial to set up, but you have to remember to re-save your files as `tsx` and `jsx` if you want to use it. I have not spent a lot of time writing JSX in my life but once I got some of the basics it became super easy to understand. I can understand why people like React, honestly. @@ -151,12 +151,12 @@ app.get( ); ``` -# Development +## Development If the environment variable `ENVIRONMENT` is set and the value is `DEVELOPMENT` then we can easily grab all the Posts, including Drafts, from the `PostService`. -# Deployment +## Deployment We can simply depoy the application within a Docker container into Dokku. diff --git a/src/config.ts b/src/config.ts index d8809fc..8d62b5f 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,4 +1,3 @@ export const POST_PATH: string = __dirname + '/../posts'; export const STATIC_PATH: string = __dirname + '/assets'; export const POST_ROUTE_PREFIX: string = '/posts' -export const SQLITE_DATABASE_FILE: string = __dirname + '/../db/blog.sqlite'; diff --git a/src/services/post-file.ts b/src/services/post-file.ts index 71d0136..a6077e0 100644 --- a/src/services/post-file.ts +++ b/src/services/post-file.ts @@ -61,6 +61,16 @@ export async function createPostService(path: string): Promise { return new PostService(posts); } +export function sortPostsByDate(posts: Post[], desc: boolean = true): Post[] { + return posts.sort((p1: Post, p2: Post) => { + if (desc) { + return p2.meta.date.getTime() - p1.meta.date.getTime(); + } + + return p1.meta.date.getTime() - p2.meta.date.getTime(); + }); +} + export class PostService { private posts: Map; @@ -73,28 +83,19 @@ export class PostService { } public getAllPosts(): Post[] { - return Array.from(this.posts.values()); + return this.getPostsSortedByDate(); } public getPostsSortedByDate(desc: boolean = true): Post[] { - return Array.from(this.posts.values()) - .sort((p1: Post, p2: Post) => { - if (desc) { - return p2.meta.date.getTime() - p1.meta.date.getTime(); - } - - return p1.meta.date.getTime() - p2.meta.date.getTime(); - }); + return sortPostsByDate(Array.from(this.getPosts().values()), desc); } - public getPublishedPosts(desc: boolean = true): Post[] { - return this.getPostsSortedByDate(desc) - .filter((p: Post) => p.meta.draft == false); + public getPublishedPosts(): Post[] { + return this.getAllPosts().filter((p: Post) => p.meta.draft == false); } - public getDraftPosts(desc: boolean = true): Post[] { - return this.getPostsSortedByDate(desc) - .filter((p: Post) => p.meta.draft == true); + public getDraftPosts(): Post[] { + return this.getAllPosts().filter((p: Post) => p.meta.draft == true); } public getPost(slug: string): Post {