Basics coming together...
This commit is contained in:
parent
1099ea1785
commit
c97c3f7b69
21
components/Note.tsx
Normal file
21
components/Note.tsx
Normal 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>
|
||||
);
|
||||
}
|
20
db/mod.ts
20
db/mod.ts
|
@ -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],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
15
types.ts
15
types.ts
|
@ -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>;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue