many changes idek

main
Ethan Niser 2023-09-17 20:16:01 -05:00
parent f96e9cdd0b
commit 039a4f500c
15 changed files with 273 additions and 270 deletions

Binary file not shown.

Binary file not shown.

@ -11,7 +11,8 @@
"db:seed": "bun run src/model/store/seed.ts", "db:seed": "bun run src/model/store/seed.ts",
"uno": "bunx unocss", "uno": "bunx unocss",
"uno:dev": "bunx unocss --watch", "uno:dev": "bunx unocss --watch",
"typecheck": "bunx --bun tsc" "typecheck": "bunx --bun tsc",
"cli": "bunx beth-stack foo bar"
}, },
"devDependencies": { "devDependencies": {
"@kitajs/ts-html-plugin": "^1.0.1", "@kitajs/ts-html-plugin": "^1.0.1",
@ -31,11 +32,10 @@
"@t3-oss/env-core": "^0.6.1", "@t3-oss/env-core": "^0.6.1",
"drizzle-orm": "^0.28.6", "drizzle-orm": "^0.28.6",
"drizzle-typebox": "^0.1.1", "drizzle-typebox": "^0.1.1",
"elysia": "link:elysia", "elysia": "./elysia-0.7.0-exp.0.tgz",
"elysia-autoroutes": "^0.2.2",
"lucia": "^2.6.0", "lucia": "^2.6.0",
"pino-pretty": "^10.2.0", "pino-pretty": "^10.2.0",
"zod": "^3.22.2", "zod": "^3.22.2",
"beth-jsx": "link:beth-jsx" "beth-stack": "0.0.3"
} }
} }

@ -13,19 +13,19 @@ function dispatch() {
const app = new Elysia() const app = new Elysia()
.ws("/ws", { .ws("/ws", {
open(ws) { open(ws) {
console.log("open"); // console.log("open");
wsConnections.add(ws); wsConnections.add(ws);
}, },
close(ws) { close(ws) {
console.log("close"); // console.log("close");
wsConnections.delete(ws); wsConnections.delete(ws);
}, },
message(ws, message) { message(ws, message) {
console.log("message", message); // console.log("message", message);
}, },
}) })
.get("/restart", () => { .get("/restart", () => {
console.log("recieved restart"); // console.log("recieved restart");
dispatch(); dispatch();
}) })
.listen(3001); .listen(3001);

@ -1,4 +1,4 @@
import { type PropsWithChildren } from "beth-jsx"; import { type PropsWithChildren } from "beth-stack/jsx";
import { config } from "../config"; import { config } from "../config";
export const BaseHtml = ({ children }: PropsWithChildren) => ( export const BaseHtml = ({ children }: PropsWithChildren) => (

@ -43,7 +43,7 @@ export function TodoForm() {
_="on submit target.reset()" _="on submit target.reset()"
> >
<select name="content" class="border border-black"> <select name="content" class="border border-black">
<option value="" disabled="true" selected="true"> <option value="" disabled={true} selected="true">
Select a Todo Select a Todo
</option> </option>
<option value="beth">Learn the BETH stack</option> <option value="beth">Learn the BETH stack</option>

@ -3,8 +3,8 @@ import { Elysia } from "elysia";
// import pretty from "pino-pretty"; // import pretty from "pino-pretty";
import { config } from "../config"; import { config } from "../config";
import { client, db } from "../db"; import { client, db } from "../db";
import "beth-jsx/register"; import "beth-stack/jsx/register";
import "beth-jsx/htmx"; import { bethStack } from "beth-stack/elysia";
import { auth } from "../auth"; import { auth } from "../auth";
// import { cron } from "@elysiajs/cron"; // import { cron } from "@elysiajs/cron";
@ -14,11 +14,13 @@ import { auth } from "../auth";
export const ctx = new Elysia({ export const ctx = new Elysia({
name: "@app/ctx", name: "@app/ctx",
cookie: {
secrets: config.env.COOKIE_SECRET,
sign: "session",
},
}) })
.use(
bethStack({
log: true,
returnStaleWhileRevalidate: false,
})
)
// .use( // .use(
// logger({ // logger({
// level: config.env.LOG_LEVEL, // level: config.env.LOG_LEVEL,
@ -42,14 +44,7 @@ export const ctx = new Elysia({
// ) // )
.decorate("db", db) .decorate("db", db)
.decorate("config", config) .decorate("config", config)
.decorate("auth", auth) .decorate("auth", auth);
.decorate(
"html",
(html: string) =>
new Response(html, {
headers: { "Content-Type": "text/html; charset=utf8" },
})
);
// .onStart(({ log }) => log.info("Server starting")) // .onStart(({ log }) => log.info("Server starting"))
// .onStop(({ log }) => log.info("Server stopping")) // .onStop(({ log }) => log.info("Server stopping"))
// .onRequest(({ log, request }) => { // .onRequest(({ log, request }) => {

@ -1,9 +1,8 @@
import Elysia from "elysia"; import Elysia from "elysia";
import { todosController } from "./todos"; import { todosController } from "./todos";
import { authController } from "./auth"; // import { authController } from "./auth";
export const api = new Elysia({ export const api = new Elysia({
prefix: "/api", prefix: "/api",
}) }).use(todosController);
.use(todosController) // .use(authController);
.use(authController);

@ -1,129 +1,128 @@
import { Elysia, t } from "elysia"; // import { Elysia, t } from "elysia";
import { ctx } from "../context"; // import { ctx } from "../context";
import { set } from "zod"; // import { set } from "zod";
import { LuciaError } from "lucia"; // import { LuciaError } from "lucia";
class DuplicateEmailError extends Error { // class DuplicateEmailError extends Error {
constructor() { // constructor() {
super("Duplicate email"); // super("Duplicate email");
} // }
} // }
export const authController = new Elysia({ // export const authController = new Elysia({
prefix: "/auth", // prefix: "/auth",
}) // })
.use(ctx) // .use(ctx)
.post( // .post(
"/signup", // "/signup",
async ({ body: { email, password }, auth, set, cookie }) => { // async ({ body: { email, password }, auth, set }) => {
const user = await auth // const user = await auth
.createUser({ // .createUser({
key: { // key: {
providerId: "email", // auth method // providerId: "email", // auth method
providerUserId: email.toLowerCase(), // unique id when using "email" auth method // providerUserId: email.toLowerCase(), // unique id when using "email" auth method
password, // hashed by Lucia // password, // hashed by Lucia
}, // },
attributes: { // attributes: {
email, // email,
}, // },
}) // })
.catch((err) => { // .catch((err) => {
if (err.code === "SQLITE_CONSTRAINT") { // if (err.code === "SQLITE_CONSTRAINT") {
throw new DuplicateEmailError(); // throw new DuplicateEmailError();
} else { // } else {
throw err; // throw err;
} // }
}); // });
const session = await auth.createSession({ // const session = await auth.createSession({
userId: user.userId, // userId: user.userId,
attributes: {}, // attributes: {},
}); // });
const sessionCookie = auth.createSessionCookie(session); // const sessionCookie = auth.createSessionCookie(session);
// cookie.session?.set(sessionCookie); // // cookie.session?.set(sessionCookie);
set.headers["Set-Cookie"] = sessionCookie.serialize(); // set.headers["Set-Cookie"] = sessionCookie.serialize();
set.headers["HX-Location"] = "/profile"; // set.headers["HX-Location"] = "/profile";
}, // },
{ // {
body: t.Object({ // body: t.Object({
email: t.String({ // email: t.String({
minLength: 5, // minLength: 5,
maxLength: 30, // maxLength: 30,
}), // }),
password: t.String({ // password: t.String({
minLength: 6, // minLength: 6,
maxLength: 255, // maxLength: 255,
}), // }),
}), // }),
error({ code, error, set }) { // error({ code, error, set }) {
if (code === "VALIDATION") { // if (code === "VALIDATION") {
console.log("sign up validation error"); // console.log("sign up validation error");
console.log(error); // console.log(error);
set.status = 400; // set.status = 400;
return "Invalid email or password"; // return "Invalid email or password";
} else if (error instanceof DuplicateEmailError) { // } else if (error instanceof DuplicateEmailError) {
console.log("sign up duplicate email error"); // console.log("sign up duplicate email error");
console.log(error); // console.log(error);
set.status = 400; // set.status = 400;
return "Email already exists"; // return "Email already exists";
} else { // } else {
console.log("sign up error"); // console.log("sign up error");
console.log(error); // console.log(error);
set.status = 500; // set.status = 500;
return "Internal server error"; // return "Internal server error";
} // }
}, // },
} // }
) // )
.post( // .post(
"/signin", // "/signin",
async ({ body: { email, password }, auth, set, cookie }) => { // async ({ body: { email, password }, auth, set }) => {
const user = await auth.useKey("email", email.toLowerCase(), password); // const user = await auth.useKey("email", email.toLowerCase(), password);
const session = await auth.createSession({ // const session = await auth.createSession({
userId: user.userId, // userId: user.userId,
attributes: {}, // attributes: {},
}); // });
const sessionCookie = auth.createSessionCookie(session); // const sessionCookie = auth.createSessionCookie(session);
// cookie.sesion?.set(sessionCookie); // set.headers["Set-Cookie"] = sessionCookie.serialize();
set.headers["Set-Cookie"] = sessionCookie.serialize(); // set.headers["HX-Location"] = "/profile";
set.headers["HX-Location"] = "/profile"; // },
}, // {
{ // body: t.Object({
body: t.Object({ // email: t.String({
email: t.String({ // minLength: 5,
minLength: 5, // maxLength: 30,
maxLength: 30, // }),
}), // password: t.String({
password: t.String({ // minLength: 6,
minLength: 6, // maxLength: 255,
maxLength: 255, // }),
}), // }),
}), // error({ code, error, set }) {
error({ code, error, set }) { // if (code === "VALIDATION") {
if (code === "VALIDATION") { // console.log("sign up validation error");
console.log("sign up validation error"); // console.log(error);
console.log(error); // set.status = 400;
set.status = 400; // return "Invalid email or password";
return "Invalid email or password"; // } else if (
} else if ( // error instanceof LuciaError &&
error instanceof LuciaError && // (error.message === "AUTH_INVALID_KEY_ID" ||
(error.message === "AUTH_INVALID_KEY_ID" || // error.message === "AUTH_INVALID_PASSWORD")
error.message === "AUTH_INVALID_PASSWORD") // ) {
) { // console.log("sign in invalid email or password error");
console.log("sign in invalid email or password error"); // console.log(error);
console.log(error); // set.status = 400;
set.status = 400; // return "Invalid email or password";
return "Invalid email or password"; // } else {
} else { // console.log("sign up error");
console.log("sign up error"); // console.log(error);
console.log(error); // set.status = 500;
set.status = 500; // return "Internal server error";
return "Internal server error"; // }
} // },
}, // }
} // );
);

@ -1,6 +1,8 @@
import Elysia from "elysia"; import Elysia from "elysia";
import { signup } from "./signup"; // import { signup } from "./signup";
import { profile } from "./profile"; import { profile } from "./profile";
import { signin } from "./signin"; // import { signin } from "./signin";
export const authGroup = new Elysia().use(signup).use(signin).use(profile); export const authGroup = new Elysia()
// .use(signup).use(signin)
.use(profile);

@ -8,7 +8,7 @@ export const profile = new Elysia()
const authRequest = auth.handleRequest(request); const authRequest = auth.handleRequest(request);
const session = await authRequest.validate(); const session = await authRequest.validate();
return html( return html(() =>
session ? <div>Hello {session.user.email}</div> : <div>Not logged in</div> session ? <div>Hello {session.user.email}</div> : <div>Not logged in</div>
); );
}); });

@ -1,54 +1,54 @@
import Elysia from "elysia"; // import Elysia from "elysia";
import { BaseHtml } from "../../components/base"; // import { BaseHtml } from "../../components/base";
import { ctx } from "../../context"; // import { ctx } from "../../context";
export const signin = new Elysia().use(ctx).get("/signin", ({ html }) => // export const signin = new Elysia().use(ctx).get("/signin", ({ html }) =>
html( // html(
<BaseHtml> // <BaseHtml>
<div class="flex w-full h-screen bg-gray-200 justify-center items-center"> // <div class="flex w-full h-screen bg-gray-200 justify-center items-center">
<form // <form
hx-post="/api/auth/signin" // hx-post="/api/auth/signin"
hx-swap="afterend" // hx-swap="afterend"
class="bg-white p-8 rounded-lg shadow-md w-96" // class="bg-white p-8 rounded-lg shadow-md w-96"
> // >
<div class="mb-4"> // <div class="mb-4">
<label // <label
for="email" // for="email"
class="block text-sm font-medium text-gray-600 mb-2" // class="block text-sm font-medium text-gray-600 mb-2"
> // >
Email // Email
</label> // </label>
<input // <input
type="text" // type="text"
name="email" // name="email"
id="email" // id="email"
placeholder="Enter your email" // placeholder="Enter your email"
class="w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:border-transparent" // class="w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:border-transparent"
/> // />
</div> // </div>
<div class="mb-4"> // <div class="mb-4">
<label // <label
for="password" // for="password"
class="block text-sm font-medium text-gray-600 mb-2" // class="block text-sm font-medium text-gray-600 mb-2"
> // >
Password // Password
</label> // </label>
<input // <input
type="password" // type="password"
name="password" // name="password"
id="password" // id="password"
placeholder="Enter your password" // placeholder="Enter your password"
class="w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:border-transparent" // class="w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:border-transparent"
/> // />
</div> // </div>
<button // <button
type="submit" // type="submit"
class="w-full bg-indigo-600 text-white p-2 rounded-md hover:bg-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:ring-opacity-50" // class="w-full bg-indigo-600 text-white p-2 rounded-md hover:bg-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:ring-opacity-50"
> // >
Sign In // Sign In
</button> // </button>
</form> // </form>
</div> // </div>
</BaseHtml> // </BaseHtml>
) // )
); // );

@ -1,54 +1,54 @@
import Elysia from "elysia"; // import Elysia from "elysia";
import { BaseHtml } from "../../components/base"; // import { BaseHtml } from "../../components/base";
import { ctx } from "../../context"; // import { ctx } from "../../context";
export const signup = new Elysia().use(ctx).get("/signup", ({ html }) => // export const signup = new Elysia().use(ctx).get("/signup", ({ html }) =>
html( // html(
<BaseHtml> // <BaseHtml>
<div class="flex w-full h-screen bg-gray-200 justify-center items-center"> // <div class="flex w-full h-screen bg-gray-200 justify-center items-center">
<form // <form
hx-post="/api/auth/signup" // hx-post="/api/auth/signup"
hx-swap="afterend" // hx-swap="afterend"
class="bg-white p-8 rounded-lg shadow-md w-96" // class="bg-white p-8 rounded-lg shadow-md w-96"
> // >
<div class="mb-4"> // <div class="mb-4">
<label // <label
for="email" // for="email"
class="block text-sm font-medium text-gray-600 mb-2" // class="block text-sm font-medium text-gray-600 mb-2"
> // >
Email // Email
</label> // </label>
<input // <input
type="text" // type="text"
name="email" // name="email"
id="email" // id="email"
placeholder="Enter your email" // placeholder="Enter your email"
class="w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:border-transparent" // class="w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:border-transparent"
/> // />
</div> // </div>
<div class="mb-4"> // <div class="mb-4">
<label // <label
for="password" // for="password"
class="block text-sm font-medium text-gray-600 mb-2" // class="block text-sm font-medium text-gray-600 mb-2"
> // >
Password // Password
</label> // </label>
<input // <input
type="password" // type="password"
name="password" // name="password"
id="password" // id="password"
placeholder="Enter your password" // placeholder="Enter your password"
class="w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:border-transparent" // class="w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:border-transparent"
/> // />
</div> // </div>
<button // <button
type="submit" // type="submit"
class="w-full bg-indigo-600 text-white p-2 rounded-md hover:bg-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:ring-opacity-50" // class="w-full bg-indigo-600 text-white p-2 rounded-md hover:bg-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:ring-opacity-50"
> // >
Sign Up // Sign Up
</button> // </button>
</form> // </form>
</div> // </div>
</BaseHtml> // </BaseHtml>
) // )
); // );

@ -1,8 +1,8 @@
import Elysia from "elysia"; import { Elysia } from "elysia";
import { BaseHtml } from "../components/base"; import { BaseHtml } from "../components/base";
import { ctx } from "../context"; import { ctx } from "../context";
import { Suspense, renderToStream, renderToString } from "beth-jsx"; import { Suspense, renderToStream, renderToString } from "beth-stack/jsx";
import { persistedCache, revalidateTag } from "beth-jsx"; import { persistedCache, revalidateTag } from "beth-stack/cache";
const start = Date.now(); const start = Date.now();
@ -24,18 +24,19 @@ export const index = new Elysia()
}); });
} }
}) })
.get("/test", async () => { .get("/test", async ({ html }) => {
const time = await cachedGetTime(); const time = await cachedGetTime();
return renderToString(() => <p>{time}</p>); return html(() => <p>{time}</p>);
}) })
.get("/", async ({ set }) => { .get("/", async ({ html }) => {
return renderToString(() => ( return html(() => (
<BaseHtml> <BaseHtml>
<h1>cache revalidates on two second interval</h1> <h1>cache revalidates every two seconds</h1>
<button hx-get="/test" hx-target="#foo" hx-swap="beforeend"> <button hx-get="/test" hx-target="#foo" hx-swap="beforeend">
click me to get time since start (cached) click me to get time since start (cached)
</button> </button>
<br /> <br />
<div>hot reload</div>
<br /> <br />
<button <button
hx-get="/test" hx-get="/test"

@ -9,7 +9,6 @@
"noEmit": true, "noEmit": true,
"composite": true, "composite": true,
"strict": true, "strict": true,
// "noUncheckedIndexedAccess": true,
"downlevelIteration": true, "downlevelIteration": true,
"skipLibCheck": true, "skipLibCheck": true,
"jsx": "react", "jsx": "react",
@ -21,6 +20,14 @@
"types": [ "types": [
"bun-types" // add Bun global "bun-types" // add Bun global
], ],
"plugins": [{ "name": "@kitajs/ts-html-plugin" }] // non bun init
"plugins": [{ "name": "@kitajs/ts-html-plugin" }],
"noUncheckedIndexedAccess": true,
// "noUnusedLocals": true,
// "noUnusedParameters": true,
"exactOptionalPropertyTypes": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true
// "noImplicitReturns": true
} }
} }