Ethan Niser 2023-09-21 18:12:21 -05:00
parent f1c5467009
commit 22bff4dc17
7 changed files with 84 additions and 26 deletions

Binary file not shown.

@ -37,11 +37,12 @@
"@elysiajs/static": "^0.6.0", "@elysiajs/static": "^0.6.0",
"@elysiajs/swagger": "^0.6.2", "@elysiajs/swagger": "^0.6.2",
"@iconify-json/logos": "^1.1.37", "@iconify-json/logos": "^1.1.37",
"@iconify-json/lucide": "^1.1.128",
"@libsql/client": "0.3.5-pre.9", "@libsql/client": "0.3.5-pre.9",
"@lucia-auth/adapter-sqlite": "^2.0.0", "@lucia-auth/adapter-sqlite": "^2.0.0",
"@t3-oss/env-core": "^0.6.1", "@t3-oss/env-core": "^0.6.1",
"@tlscipher/holt": "1.1.0", "@tlscipher/holt": "1.1.0",
"beth-stack": "0.0.24", "beth-stack": "0.0.27",
"drizzle-orm": "^0.28.6", "drizzle-orm": "^0.28.6",
"drizzle-typebox": "^0.1.1", "drizzle-typebox": "^0.1.1",
"elysia": "0.7.4", "elysia": "0.7.4",

@ -2,13 +2,8 @@ import { liveReloadScript } from "beth-stack/dev";
import { type PropsWithChildren } from "beth-stack/jsx"; import { type PropsWithChildren } from "beth-stack/jsx";
import { config } from "../config"; import { config } from "../config";
const safeScript = const safeScript =
config.env.NODE_ENV === "development" config.env.NODE_ENV === "development" ? liveReloadScript() : "";
? liveReloadScript({
url: "https://upgraded-orbit-qw9457vrwv39669-3001.app.github.dev/ws",
})
: "";
export const BaseHtml = ({ children }: PropsWithChildren) => ( export const BaseHtml = ({ children }: PropsWithChildren) => (
<html> <html>

@ -20,16 +20,14 @@ export function TweetCard({
<p class="text-gray-700" safe> <p class="text-gray-700" safe>
{content} {content}
</p> </p>
<span class="text-sm text-gray-500"> <span class="text-sm text-gray-500">{createdAt.toLocaleString()}</span>
{createdAt.toLocaleString()}
</span>
</div> </div>
); );
} }
export async function TweetList() { export async function InitialTweetList() {
const tweetData = await db.query.tweets.findMany({ const tweetData = await db.query.tweets.findMany({
limit: 10, limit: 5,
orderBy: (tweets, { desc }) => [desc(tweets.createdAt)], orderBy: (tweets, { desc }) => [desc(tweets.createdAt)],
with: { with: {
author: { author: {
@ -40,12 +38,55 @@ export async function TweetList() {
}, },
}); });
const lastTweetTime = tweetData[tweetData.length - 1]?.createdAt;
return (
<>
<div class="space-y-4" id="tweetList">
{tweetData.map((tweet) => (
<TweetCard {...tweet} />
))}
<div
hx-get={`/api/tweets?after=${lastTweetTime?.toISOString()}`}
hx-swap="beforeend"
hx-target="#tweetList"
hx-trigger="revealed"
/>
</div>
</>
);
}
export async function AdditionalTweetList({ after }: { after: Date }) {
const tweetData = await db.query.tweets.findMany({
where: (tweets, { lt }) => lt(tweets.createdAt, after),
limit: 5,
orderBy: (tweets, { desc }) => [desc(tweets.createdAt)],
with: {
author: {
columns: {
handle: true,
},
},
},
});
const lastTweetTime = tweetData[tweetData.length - 1]?.createdAt;
return ( return (
<div class="space-y-4" id="tweetList"> <>
{tweetData.map((tweet) => ( {tweetData.map((tweet) => (
<TweetCard {...tweet} /> <TweetCard {...tweet} />
))} ))}
</div> {lastTweetTime && (
<div
hx-get={`/api/tweets?after=${lastTweetTime.toISOString()}`}
hx-swap="beforeend"
hx-target="#tweetList"
hx-trigger="revealed"
/>
)}
</>
); );
} }
@ -53,7 +94,12 @@ export function TweetCreationForm() {
return ( return (
<div class="rounded-lg border p-4 shadow-md"> <div class="rounded-lg border p-4 shadow-md">
<h2 class="mb-4 text-xl font-bold">Create a new Tweet</h2> <h2 class="mb-4 text-xl font-bold">Create a new Tweet</h2>
<form hx-post="/api/tweets" hx-swap="afterbegin" hx-target="#tweetList" _="on submit target.reset()"> <form
hx-post="/api/tweets"
hx-swap="afterbegin"
hx-target="#tweetList"
_="on submit target.reset()"
>
<label class="mb-2 block text-sm font-bold" for="content"> <label class="mb-2 block text-sm font-bold" for="content">
Tweet: Tweet:
</label> </label>

@ -1,6 +1,6 @@
import { Elysia, t } from "elysia"; import { Elysia, t } from "elysia";
import { authed } from "../auth/middleware"; import { authed } from "../auth/middleware";
import { TweetCard } from "../components/tweets"; import { AdditionalTweetList, TweetCard } from "../components/tweets";
import { ctx } from "../context"; import { ctx } from "../context";
import { tweets } from "../db/schema/tweets"; import { tweets } from "../db/schema/tweets";
@ -14,6 +14,21 @@ export const tweetsController = new Elysia({
return { session }; return { session };
}) })
.get(
"/",
async ({ query: { after } }) => {
const date = new Date(after);
return <AdditionalTweetList after={date} />;
},
{
query: t.Object({
after: t.String({
format: "date-time",
}),
}),
},
)
.post( .post(
"/", "/",
async ({ session, db, body, set, log }) => { async ({ session, db, body, set, log }) => {

@ -1,7 +1,8 @@
import { Suspense } from "beth-stack/jsx";
import { Elysia } from "elysia"; import { Elysia } from "elysia";
import { authed } from "../auth/middleware"; import { authed } from "../auth/middleware";
import { BaseHtml } from "../components/base"; import { BaseHtml } from "../components/base";
import { TweetCreationForm, TweetList } from "../components/tweets"; import { InitialTweetList, TweetCreationForm } from "../components/tweets";
import { ctx } from "../context"; import { ctx } from "../context";
export const index = new Elysia() export const index = new Elysia()
@ -12,8 +13,8 @@ export const index = new Elysia()
return { session }; return { session };
}) })
.get("/", async ({ html, session, db }) => { .get("/", async ({ htmlStream, session, db }) => {
return html(() => ( return htmlStream(() => (
<BaseHtml> <BaseHtml>
<div class="flex flex-col items-center py-3"> <div class="flex flex-col items-center py-3">
{session ? ( {session ? (
@ -37,7 +38,7 @@ export const index = new Elysia()
Sign In Sign In
</a> </a>
)} )}
<TweetList /> <InitialTweetList />
</div> </div>
</BaseHtml> </BaseHtml>
)); ));

@ -46,11 +46,11 @@ type PatchRoutes = RoutesByType<Schema, "patch">;
declare namespace JSX { declare namespace JSX {
interface HtmlTag extends Htmx.Attributes { interface HtmlTag extends Htmx.Attributes {
["hx-get"]?: StartsWithApi<GetRoutes>; ["hx-get"]?: StartsWithApi<GetRoutes> | (string & {});
["hx-post"]?: StartsWithApi<PostRoutes>; ["hx-post"]?: StartsWithApi<PostRoutes> | (string & {});
["hx-put"]?: StartsWithApi<PutRoutes>; ["hx-put"]?: StartsWithApi<PutRoutes> | (string & {});
["hx-delete"]?: StartsWithApi<DeleteRoutes>; ["hx-delete"]?: StartsWithApi<DeleteRoutes> | (string & {});
["hx-patch"]?: StartsWithApi<PatchRoutes>; ["hx-patch"]?: StartsWithApi<PatchRoutes> | (string & {});
_?: string; _?: string;
} }
} }