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,
|
sql: string,
|
||||||
args?: QueryArguments,
|
args?: QueryArguments,
|
||||||
): Promise<QueryObjectResult<T> | null> {
|
): Promise<QueryObjectResult<T> | null> {
|
||||||
return await dbOp(async (connection) =>
|
return await dbOp(async (connection) => {
|
||||||
await connection.queryObject<T>({
|
const result = await connection.queryObject<T>({
|
||||||
camelcase: true,
|
camelcase: true,
|
||||||
text: sql.trim(),
|
text: sql.trim(),
|
||||||
args,
|
args,
|
||||||
})
|
});
|
||||||
);
|
console.debug(result);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function queryArray<T extends []>(
|
export async function queryArray<T extends []>(
|
||||||
|
@ -76,8 +78,8 @@ export async function queryArray<T extends []>(
|
||||||
|
|
||||||
export async function listNotes(): Promise<Note[] | null> {
|
export async function listNotes(): Promise<Note[] | null> {
|
||||||
return someRows(
|
return someRows(
|
||||||
await queryObject<Note>(
|
await queryObject<Note & User>(
|
||||||
"select * from note order by created_at desc",
|
'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>;
|
type Ungenerated<T> = Omit<T, keyof Identifiable | keyof Timestamped>;
|
||||||
|
|
||||||
export async function createNote(
|
export async function createNote(
|
||||||
{ content }: Ungenerated<Note>,
|
{ content, userId }: Ungenerated<Note>,
|
||||||
): Promise<Note | null> {
|
): Promise<Note | null> {
|
||||||
return singleRow(
|
return singleRow(
|
||||||
await queryObject<Note>(
|
await queryObject<Note>(
|
||||||
"insert into note (content) values ($1) returning *",
|
"insert into note (content, user_id) values ($1, $2) returning *",
|
||||||
[content],
|
[content, userId],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,24 @@
|
||||||
import { MiddlewareHandlerContext } from "$fresh/server.ts";
|
import { MiddlewareHandlerContext } from "$fresh/server.ts";
|
||||||
import { deleteCookie, getCookies } from "$std/http/cookie.ts";
|
import { deleteCookie, getCookies } from "$std/http/cookie.ts";
|
||||||
import { getUserFromNonExpiredLoginToken } from "@/db/mod.ts";
|
import { getUserFromNonExpiredLoginToken } from "@/db/mod.ts";
|
||||||
|
import { type ContextState } from "@/types.ts";
|
||||||
interface State {
|
|
||||||
data: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handler(
|
export async function handler(
|
||||||
request: Request,
|
request: Request,
|
||||||
ctx: MiddlewareHandlerContext<State>,
|
ctx: MiddlewareHandlerContext<ContextState>,
|
||||||
) {
|
) {
|
||||||
ctx.state.data = "";
|
|
||||||
let hasBadAuthCookie = false;
|
let hasBadAuthCookie = false;
|
||||||
const { lsauth } = getCookies(request.headers);
|
const { lsauth } = getCookies(request.headers);
|
||||||
console.log("lsauth cookie:", lsauth);
|
console.log("lsauth cookie:", lsauth);
|
||||||
if (lsauth) {
|
if (lsauth) {
|
||||||
const user = await getUserFromNonExpiredLoginToken(lsauth);
|
const user = await getUserFromNonExpiredLoginToken(lsauth);
|
||||||
if (!user) hasBadAuthCookie = true;
|
if (!user) hasBadAuthCookie = true;
|
||||||
else {ctx.state.data += "user:" + JSON.stringify({
|
else {
|
||||||
id: user.id,
|
ctx.state.user = user;
|
||||||
username: user.username,
|
delete ctx.state.user.createdAt;
|
||||||
displayName: user.displayName || user.username,
|
delete ctx.state.user.updatedAt;
|
||||||
}) +
|
delete ctx.state.user.passwordDigest;
|
||||||
"\n";}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const resp = await ctx.next();
|
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 { Page } from "@/components/Page.tsx";
|
||||||
// import { getToken, getUser } from "@/db/mod.ts";
|
// import { getToken, getUser } from "@/db/mod.ts";
|
||||||
// import * as base64 from "$std/encoding/base64.ts";
|
// import * as base64 from "$std/encoding/base64.ts";
|
||||||
import { getCookies } from "$std/http/cookie.ts";
|
import { type ContextState } from "@/types.ts";
|
||||||
import { type User } from "@/types.ts";
|
|
||||||
|
|
||||||
export const handler: Handlers<unknown> = {
|
export const handler: Handlers<unknown, ContextState> = {
|
||||||
async GET(request: Request, context) {
|
async GET(_request: Request, context) {
|
||||||
return await context.render(context.state.data);
|
const user: Partial<ContextState["user"]> = context.state.user;
|
||||||
|
delete user.passwordDigest;
|
||||||
|
return await context.render(context.state.user);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Dashboard({ data }: PageProps) {
|
export default function Dashboard({ data }: PageProps) {
|
||||||
|
console.log(data);
|
||||||
if (data) {
|
if (data) {
|
||||||
return You(data);
|
return You(data);
|
||||||
} else {
|
} else {
|
||||||
|
@ -23,7 +25,7 @@ function You(data: unknown) {
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<p>
|
<p>
|
||||||
You are <pre>{data}</pre>.
|
You are <pre>{JSON.stringify(data)}</pre>.
|
||||||
</p>
|
</p>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { Handlers, PageProps } from "$fresh/server.ts";
|
||||||
import { listNotes } from "@/db/mod.ts";
|
import { listNotes } from "@/db/mod.ts";
|
||||||
import { Page } from "@/components/Page.tsx";
|
import { Page } from "@/components/Page.tsx";
|
||||||
import { type Note } from "@/types.ts";
|
import { type Note } from "@/types.ts";
|
||||||
|
import { NoteItem } from "@/components/Note.tsx";
|
||||||
|
|
||||||
export const handler: Handlers<Note[]> = {
|
export const handler: Handlers<Note[]> = {
|
||||||
async GET(_request, context) {
|
async GET(_request, context) {
|
||||||
|
@ -19,17 +20,7 @@ export default function NotesPage({ data: notes }: PageProps<Note[]>) {
|
||||||
</textarea>
|
</textarea>
|
||||||
<input class="mt-2" type="submit" value="Post" />
|
<input class="mt-2" type="submit" value="Post" />
|
||||||
</form>
|
</form>
|
||||||
{notes.map(({ id, content, createdAt }) => (
|
{notes.map(NoteItem)}
|
||||||
<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>
|
|
||||||
))}
|
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import { Handlers, PageProps } from "$fresh/server.ts";
|
import { Handlers, PageProps } from "$fresh/server.ts";
|
||||||
import { createNote } from "@/db/mod.ts";
|
import { createNote } from "@/db/mod.ts";
|
||||||
import { Page } from "@/components/Page.tsx";
|
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) {
|
async POST(request, context) {
|
||||||
|
const userId = context.state.user ? context.state.user.id : null;
|
||||||
const content = (await request.formData()).get("content");
|
const content = (await request.formData()).get("content");
|
||||||
if (!content) throw "no content provided";
|
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";
|
if (!note) throw "no note created";
|
||||||
return await context.render(note);
|
return await context.render(note);
|
||||||
},
|
},
|
||||||
|
|
15
types.ts
15
types.ts
|
@ -12,10 +12,6 @@ export interface Updated {
|
||||||
|
|
||||||
export type Timestamped = Created & Updated;
|
export type Timestamped = Created & Updated;
|
||||||
|
|
||||||
export interface Note extends Identifiable, Timestamped {
|
|
||||||
content: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Team extends Identifiable, Timestamped {
|
export interface Team extends Identifiable, Timestamped {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
}
|
}
|
||||||
|
@ -26,6 +22,13 @@ export interface User extends Identifiable, Timestamped {
|
||||||
displayName?: string;
|
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"];
|
type IdentifierFor<T extends Identifiable> = T["id"];
|
||||||
|
|
||||||
export interface Token extends Created {
|
export interface Token extends Created {
|
||||||
|
@ -37,3 +40,7 @@ export interface Token extends Created {
|
||||||
|
|
||||||
/** 32 bytes base64-encoded */
|
/** 32 bytes base64-encoded */
|
||||||
export type TokenDigest = string;
|
export type TokenDigest = string;
|
||||||
|
|
||||||
|
export interface ContextState {
|
||||||
|
user?: Omit<User, "passwordDigest" | keyof Timestamped> & Partial<User>;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue