Basics coming together...

This commit is contained in:
Daniel Flanagan 2022-10-08 02:53:13 -05:00
parent 1099ea1785
commit c97c3f7b69
Signed by: lytedev
GPG key ID: 5B2020A0F9921EF4
7 changed files with 66 additions and 46 deletions

21
components/Note.tsx Normal file
View file

@ -0,0 +1,21 @@
import { type Note } from "@/types.ts";
export function NoteItem(
{ id, createdAt, content, userId, userDisplayName, userUsername }: Note,
) {
return (
<div class="my-4" key={id}>
<span>
<a href={`/note/${id}`} class="text-blue-500">Note {id}</a>
{" created by "}
{!userId
? "Anonymous"
: <a href={`/user/${userId}`}>{userDisplayName || userUsername}</a>}
{` at ${createdAt.toLocaleString()}`}
</span>
<blockquote class="mt-2 pl-4 border-l(solid 4)">
<pre>{content}</pre>
</blockquote>
</div>
);
}

View file

@ -53,13 +53,15 @@ export async function queryObject<T>(
sql: string,
args?: QueryArguments,
): Promise<QueryObjectResult<T> | null> {
return await dbOp(async (connection) =>
await connection.queryObject<T>({
return await dbOp(async (connection) => {
const result = await connection.queryObject<T>({
camelcase: true,
text: sql.trim(),
args,
})
);
});
console.debug(result);
return result;
});
}
export async function queryArray<T extends []>(
@ -76,8 +78,8 @@ export async function queryArray<T extends []>(
export async function listNotes(): Promise<Note[] | null> {
return someRows(
await queryObject<Note>(
"select * from note order by created_at desc",
await queryObject<Note & User>(
'select u.username as user_username, u.display_name as user_display_name, n.* from note n left join "user" u on u.id = n.user_id order by n.created_at desc',
),
);
}
@ -98,12 +100,12 @@ export async function getNote(
type Ungenerated<T> = Omit<T, keyof Identifiable | keyof Timestamped>;
export async function createNote(
{ content }: Ungenerated<Note>,
{ content, userId }: Ungenerated<Note>,
): Promise<Note | null> {
return singleRow(
await queryObject<Note>(
"insert into note (content) values ($1) returning *",
[content],
"insert into note (content, user_id) values ($1, $2) returning *",
[content, userId],
),
);
}

View file

@ -1,28 +1,24 @@
import { MiddlewareHandlerContext } from "$fresh/server.ts";
import { deleteCookie, getCookies } from "$std/http/cookie.ts";
import { getUserFromNonExpiredLoginToken } from "@/db/mod.ts";
interface State {
data: string;
}
import { type ContextState } from "@/types.ts";
export async function handler(
request: Request,
ctx: MiddlewareHandlerContext<State>,
ctx: MiddlewareHandlerContext<ContextState>,
) {
ctx.state.data = "";
let hasBadAuthCookie = false;
const { lsauth } = getCookies(request.headers);
console.log("lsauth cookie:", lsauth);
if (lsauth) {
const user = await getUserFromNonExpiredLoginToken(lsauth);
if (!user) hasBadAuthCookie = true;
else {ctx.state.data += "user:" + JSON.stringify({
id: user.id,
username: user.username,
displayName: user.displayName || user.username,
}) +
"\n";}
else {
ctx.state.user = user;
delete ctx.state.user.createdAt;
delete ctx.state.user.updatedAt;
delete ctx.state.user.passwordDigest;
}
}
const resp = await ctx.next();

View file

@ -1,17 +1,19 @@
import { HandlerContext, Handlers, PageProps } from "$fresh/server.ts";
import { Handlers, PageProps } from "$fresh/server.ts";
import { Page } from "@/components/Page.tsx";
// import { getToken, getUser } from "@/db/mod.ts";
// import * as base64 from "$std/encoding/base64.ts";
import { getCookies } from "$std/http/cookie.ts";
import { type User } from "@/types.ts";
import { type ContextState } from "@/types.ts";
export const handler: Handlers<unknown> = {
async GET(request: Request, context) {
return await context.render(context.state.data);
export const handler: Handlers<unknown, ContextState> = {
async GET(_request: Request, context) {
const user: Partial<ContextState["user"]> = context.state.user;
delete user.passwordDigest;
return await context.render(context.state.user);
},
};
export default function Dashboard({ data }: PageProps) {
console.log(data);
if (data) {
return You(data);
} else {
@ -23,7 +25,7 @@ function You(data: unknown) {
return (
<Page>
<p>
You are <pre>{data}</pre>.
You are <pre>{JSON.stringify(data)}</pre>.
</p>
</Page>
);

View file

@ -2,6 +2,7 @@ import { Handlers, PageProps } from "$fresh/server.ts";
import { listNotes } from "@/db/mod.ts";
import { Page } from "@/components/Page.tsx";
import { type Note } from "@/types.ts";
import { NoteItem } from "@/components/Note.tsx";
export const handler: Handlers<Note[]> = {
async GET(_request, context) {
@ -19,17 +20,7 @@ export default function NotesPage({ data: notes }: PageProps<Note[]>) {
</textarea>
<input class="mt-2" type="submit" value="Post" />
</form>
{notes.map(({ id, content, createdAt }) => (
<div class="my-4" key={id}>
<span>
<a href={`/note/${id}`} class="text-blue-500">Note {id}</a>{" "}
created at {createdAt.toLocaleString()}
</span>
<blockquote class="mt-2 pl-4 border-l(solid 4)">
<pre>{content}</pre>
</blockquote>
</div>
))}
{notes.map(NoteItem)}
</Page>
);
}

View file

@ -1,13 +1,14 @@
import { Handlers, PageProps } from "$fresh/server.ts";
import { createNote } from "@/db/mod.ts";
import { Page } from "@/components/Page.tsx";
import { type Note } from "@/types.ts";
import { type ContextState, type Note } from "@/types.ts";
export const handler: Handlers<Note> = {
export const handler: Handlers<Note, ContextState> = {
async POST(request, context) {
const userId = context.state.user ? context.state.user.id : null;
const content = (await request.formData()).get("content");
if (!content) throw "no content provided";
const note = await createNote({ content: content.toString() });
const note = await createNote({ userId, content: content.toString() });
if (!note) throw "no note created";
return await context.render(note);
},

View file

@ -12,10 +12,6 @@ export interface Updated {
export type Timestamped = Created & Updated;
export interface Note extends Identifiable, Timestamped {
content: string;
}
export interface Team extends Identifiable, Timestamped {
displayName: string;
}
@ -26,6 +22,13 @@ export interface User extends Identifiable, Timestamped {
displayName?: string;
}
export interface Note extends Identifiable, Timestamped {
content: string;
userId: User["id"] | null;
userUsername?: User["username"];
userDisplayName?: User["displayName"];
}
type IdentifierFor<T extends Identifiable> = T["id"];
export interface Token extends Created {
@ -37,3 +40,7 @@ export interface Token extends Created {
/** 32 bytes base64-encoded */
export type TokenDigest = string;
export interface ContextState {
user?: Omit<User, "passwordDigest" | keyof Timestamped> & Partial<User>;
}