Use solid-start
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
Michael Zhang 2023-04-18 02:38:22 -05:00
parent 589a9ce2fe
commit 1550d77ca6
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
33 changed files with 3165 additions and 11259 deletions

1
.gitignore vendored
View file

@ -4,3 +4,4 @@ node_modules
.env .env
/generated /generated
/result* /result*
.solid

30
README.md Normal file
View file

@ -0,0 +1,30 @@
# SolidStart
Everything you need to build a Solid project, powered by [`solid-start`](https://start.solidjs.com);
## Creating a project
```bash
# create a new project in the current directory
npm init solid@latest
# create a new project in my-app
npm init solid@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
Solid apps are built with _adapters_, which optimise your project for deployment to different environments.
By default, `npm run build` will generate a Node app that you can run with `npm start`. To use a different adapter, add it to the `devDependencies` in `package.json` and specify in your `vite.config.js`.

View file

@ -1,87 +0,0 @@
# Index
## Docs
- [docs/book.toml](docs/book.toml)
- [docs/make-then-tera.sh](docs/make-then-tera.sh)
### Book
- [docs/book/404.html](docs/book/404.html)
- [docs/book/ayu-highlight.css](docs/book/ayu-highlight.css)
- [docs/book/book.js](docs/book/book.js)
- [docs/book/clipboard.min.js](docs/book/clipboard.min.js)
- [docs/book/elasticlunr.min.js](docs/book/elasticlunr.min.js)
- [docs/book/favicon.png](docs/book/favicon.png)
- [docs/book/favicon.svg](docs/book/favicon.svg)
- [docs/book/highlight.css](docs/book/highlight.css)
- [docs/book/highlight.js](docs/book/highlight.js)
- [docs/book/index.html](docs/book/index.html)
- [docs/book/mark.min.js](docs/book/mark.min.js)
- [docs/book/print.html](docs/book/print.html)
- [docs/book/searcher.js](docs/book/searcher.js)
- [docs/book/searchindex.js](docs/book/searchindex.js)
- [docs/book/searchindex.json](docs/book/searchindex.json)
- [docs/book/tomorrow-night.css](docs/book/tomorrow-night.css)
### Src
- [docs/src/SUMMARY](docs/src/SUMMARY.md)
### Book
#### App
- [docs/book/app/sync.html](docs/book/app/sync.html)
#### Css
- [docs/book/css/chrome.css](docs/book/css/chrome.css)
- [docs/book/css/general.css](docs/book/css/general.css)
- [docs/book/css/print.css](docs/book/css/print.css)
- [docs/book/css/variables.css](docs/book/css/variables.css)
#### Dev
- [docs/book/dev/api.html](docs/book/dev/api.html)
- [docs/book/dev/meta_json.html](docs/book/dev/meta_json.html)
#### Lib
- [docs/book/lib/api_reference.html](docs/book/lib/api_reference.html)
#### Fonts
- [docs/book/fonts/OPEN-SANS-LICENSE.txt](docs/book/fonts/OPEN-SANS-LICENSE.txt)
- [docs/book/fonts/SOURCE-CODE-PRO-LICENSE.txt](docs/book/fonts/SOURCE-CODE-PRO-LICENSE.txt)
- [docs/book/fonts/fonts.css](docs/book/fonts/fonts.css)
- [docs/book/fonts/open-sans-v17-all-charsets-300.woff2](docs/book/fonts/open-sans-v17-all-charsets-300.woff2)
- [docs/book/fonts/open-sans-v17-all-charsets-300italic.woff2](docs/book/fonts/open-sans-v17-all-charsets-300italic.woff2)
- [docs/book/fonts/open-sans-v17-all-charsets-600.woff2](docs/book/fonts/open-sans-v17-all-charsets-600.woff2)
- [docs/book/fonts/open-sans-v17-all-charsets-600italic.woff2](docs/book/fonts/open-sans-v17-all-charsets-600italic.woff2)
- [docs/book/fonts/open-sans-v17-all-charsets-700.woff2](docs/book/fonts/open-sans-v17-all-charsets-700.woff2)
- [docs/book/fonts/open-sans-v17-all-charsets-700italic.woff2](docs/book/fonts/open-sans-v17-all-charsets-700italic.woff2)
- [docs/book/fonts/open-sans-v17-all-charsets-800.woff2](docs/book/fonts/open-sans-v17-all-charsets-800.woff2)
- [docs/book/fonts/open-sans-v17-all-charsets-800italic.woff2](docs/book/fonts/open-sans-v17-all-charsets-800italic.woff2)
- [docs/book/fonts/open-sans-v17-all-charsets-italic.woff2](docs/book/fonts/open-sans-v17-all-charsets-italic.woff2)
- [docs/book/fonts/open-sans-v17-all-charsets-regular.woff2](docs/book/fonts/open-sans-v17-all-charsets-regular.woff2)
- [docs/book/fonts/source-code-pro-v11-all-charsets-500.woff2](docs/book/fonts/source-code-pro-v11-all-charsets-500.woff2)
### Src
#### App
- [docs/src/app/sync](docs/src/app/sync.md)
#### Dev
- [docs/src/dev/api](docs/src/dev/api.md)
- [docs/src/dev/meta_json](docs/src/dev/meta_json.md)
#### Lib
- [docs/src/lib/api_reference](docs/src/lib/api_reference.md)
### Book
#### FontAwesome
##### Css
- [docs/book/FontAwesome/css/font-awesome.css](docs/book/FontAwesome/css/font-awesome.css)
##### Fonts
- [docs/book/FontAwesome/fonts/FontAwesome.ttf](docs/book/FontAwesome/fonts/FontAwesome.ttf)
- [docs/book/FontAwesome/fonts/fontawesome-webfont.eot](docs/book/FontAwesome/fonts/fontawesome-webfont.eot)
- [docs/book/FontAwesome/fonts/fontawesome-webfont.svg](docs/book/FontAwesome/fonts/fontawesome-webfont.svg)
- [docs/book/FontAwesome/fonts/fontawesome-webfont.ttf](docs/book/FontAwesome/fonts/fontawesome-webfont.ttf)
- [docs/book/FontAwesome/fonts/fontawesome-webfont.woff](docs/book/FontAwesome/fonts/fontawesome-webfont.woff)
- [docs/book/FontAwesome/fonts/fontawesome-webfont.woff2](docs/book/FontAwesome/fonts/fontawesome-webfont.woff2)

View file

@ -1,7 +1,7 @@
- CLI API - CLI API
- `mznotes install` : Install a script into the database - `panorama install` : Install a script into the database
- `mznotes show` : Show the information about a particular node, by ID - `panorama show` : Show the information about a particular node, by ID
- Database of nodes + code - Database of nodes + code
@ -90,7 +90,7 @@
- Interfaces: - Interfaces:
- - `addTodo()`
- Content / blob storage - Content / blob storage
@ -137,3 +137,5 @@
- Have the frontend also shipped with the backend? - Have the frontend also shipped with the backend?
- Subset of React components to typecheck against - Subset of React components to typecheck against
- Aggregated queries and group by? Joins?

View file

@ -47,6 +47,7 @@
cargo-expand cargo-expand
cargo-flamegraph cargo-flamegraph
cargo-watch cargo-watch
sqlite
zlib zlib

13565
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,30 +1,26 @@
{ {
"name": "my-prisma-project", "name": "ouais",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": { "scripts": {
"dev": "vite" "dev": "solid-start dev",
"build": "solid-start build",
"start": "solid-start start"
}, },
"keywords": [], "type": "module",
"author": "",
"license": "ISC",
"types": "./src/api.d.ts",
"devDependencies": { "devDependencies": {
"@types/express": "^4.17.17", "solid-start-node": "^0.2.19",
"json-schema-static-docs": "^0.23.0", "typescript": "^4.9.4",
"nativescript": "^8.5.1", "vite": "^4.1.4"
"prisma": "^4.12.0",
"ts-node": "^10.9.1",
"typescript": "^5.0.2",
"vite": "^4.2.1",
"vite-plugin-solid": "^2.6.1",
"wetzel": "^0.2.3"
}, },
"dependencies": { "dependencies": {
"@prisma/client": "^4.11.0", "@prisma/client": "^4.9.0",
"express": "^4.18.2", "@solidjs/meta": "^0.28.2",
"solid-js": "^1.6.15", "@solidjs/router": "^0.8.2",
"vm2": "^3.9.14" "prisma": "^4.9.0",
"solid-js": "^1.7.2",
"solid-start": "^0.2.26",
"undici": "^5.15.1"
},
"engines": {
"node": ">=16"
} }
} }

View file

@ -4,35 +4,32 @@ CREATE TABLE "Node" (
"label" TEXT "label" TEXT
); );
-- CreateTable
CREATE TABLE "Edge" (
"id" TEXT NOT NULL PRIMARY KEY
);
-- CreateTable -- CreateTable
CREATE TABLE "Interface" ( CREATE TABLE "Interface" (
"id" TEXT NOT NULL PRIMARY KEY "id" TEXT NOT NULL PRIMARY KEY
); );
-- CreateTable -- CreateTable
CREATE TABLE "Graph" ( CREATE TABLE "Edge" (
"id" TEXT NOT NULL PRIMARY KEY,
"label" TEXT, "label" TEXT,
"appId" TEXT NOT NULL,
"appKey" TEXT NOT NULL,
"fromId" TEXT NOT NULL, "fromId" TEXT NOT NULL,
"toId" TEXT NOT NULL, "toId" TEXT NOT NULL,
CONSTRAINT "Edge_appId_fkey" FOREIGN KEY ("appId") REFERENCES "App" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
PRIMARY KEY ("fromId", "toId"), CONSTRAINT "Edge_fromId_fkey" FOREIGN KEY ("fromId") REFERENCES "Node" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "Graph_fromId_fkey" FOREIGN KEY ("fromId") REFERENCES "Node" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT "Edge_toId_fkey" FOREIGN KEY ("toId") REFERENCES "Node" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
CONSTRAINT "Graph_toId_fkey" FOREIGN KEY ("toId") REFERENCES "Node" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
); );
-- CreateTable -- CreateTable
CREATE TABLE "NodeMeta" ( CREATE TABLE "NodeMeta" (
"nodeId" TEXT NOT NULL, "nodeId" TEXT NOT NULL,
"appId" TEXT NOT NULL, "appId" TEXT NOT NULL,
"key" TEXT NOT NULL, "appKey" TEXT NOT NULL,
"value" BLOB NOT NULL, "value" BLOB NOT NULL,
PRIMARY KEY ("nodeId", "appId", "key"), PRIMARY KEY ("nodeId", "appId", "appKey"),
CONSTRAINT "NodeMeta_nodeId_fkey" FOREIGN KEY ("nodeId") REFERENCES "Node" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT "NodeMeta_nodeId_fkey" FOREIGN KEY ("nodeId") REFERENCES "Node" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "NodeMeta_appId_fkey" FOREIGN KEY ("appId") REFERENCES "App" ("id") ON DELETE RESTRICT ON UPDATE CASCADE CONSTRAINT "NodeMeta_appId_fkey" FOREIGN KEY ("appId") REFERENCES "App" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
); );
@ -59,10 +56,20 @@ CREATE TABLE "App" (
CREATE TABLE "Patterns" ( CREATE TABLE "Patterns" (
"id" TEXT NOT NULL PRIMARY KEY, "id" TEXT NOT NULL PRIMARY KEY,
"pattern" TEXT NOT NULL, "pattern" TEXT NOT NULL,
"type" TEXT NOT NULL,
"appId" TEXT NOT NULL, "appId" TEXT NOT NULL,
"functionName" TEXT NOT NULL, "functionName" TEXT NOT NULL,
CONSTRAINT "Patterns_appId_fkey" FOREIGN KEY ("appId") REFERENCES "App" ("id") ON DELETE RESTRICT ON UPDATE CASCADE CONSTRAINT "Patterns_appId_fkey" FOREIGN KEY ("appId") REFERENCES "App" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
); );
-- CreateIndex
CREATE INDEX "Edge_appId_appKey_idx" ON "Edge"("appId", "appKey");
-- CreateIndex
CREATE UNIQUE INDEX "Edge_fromId_toId_key" ON "Edge"("fromId", "toId");
-- CreateIndex
CREATE INDEX "NodeMeta_appId_appKey_idx" ON "NodeMeta"("appId", "appKey");
-- CreateIndex -- CreateIndex
CREATE UNIQUE INDEX "App_localName_key" ON "App"("localName"); CREATE UNIQUE INDEX "App_localName_key" ON "App"("localName");

View file

@ -0,0 +1,14 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_App" (
"id" TEXT NOT NULL PRIMARY KEY,
"localName" TEXT NOT NULL,
"title" TEXT NOT NULL,
"installed" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO "new_App" ("id", "installed", "localName", "title") SELECT "id", "installed", "localName", "title" FROM "App";
DROP TABLE "App";
ALTER TABLE "new_App" RENAME TO "App";
CREATE UNIQUE INDEX "App_localName_key" ON "App"("localName");
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View file

@ -76,7 +76,7 @@ model App {
localName String @unique localName String @unique
title String title String
installed DateTime installed DateTime @default(now())
patterns Patterns[] patterns Patterns[]
metaKeys NodeMeta[] metaKeys NodeMeta[]

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

View file

@ -1,24 +0,0 @@
import Todos from "./Todos";
function App() {
return (
<div>
<header>
<p>
Edit <code>src/App.jsx</code> and save to reload.
</p>
<a
href="https://github.com/solidjs/solid"
target="_blank"
rel="noopener noreferrer"
>
Learn Solid
</a>
<Todos />
</header>
</div>
);
}
export default App;

View file

@ -1,12 +0,0 @@
import { render } from "solid-js/web";
import App from "./App";
const root = document.getElementById("root");
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
throw new Error(
"Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got mispelled?"
);
}
render(() => <App />, root);

View file

@ -1 +0,0 @@
import Database from "./db";

View file

@ -1,32 +1,38 @@
import { Node, PrismaClient } from "@prisma/client"; import { Node, App, PrismaClient } from "@prisma/client";
import {
interface ICreateNodeRequest { ICreateNodeRequest,
/** IGetNodeRequest,
* A label for humans. This is not used by the database in any way, it's just IRegisterAppRequest,
* something to print when debugging. } from "./types";
*/
label: string;
/**
* A mapping from labels to node creation requests.
*/
other_nodes: Map<string, ICreateNodeRequest>;
/**
* A set of edges, using the IDs in auxiliary_nodes.
*/
edges: Set<ICreateEdgeRequest>;
}
interface ICreateEdgeRequest {
label: string;
from_node: string;
to_node: string;
metadata: unknown;
}
export default class Database { export default class Database {
public static async createNode(request: ICreateNodeRequest): Promise<Node> { private prisma: PrismaClient;
constructor() {
this.prisma = new PrismaClient();
}
public async registerApp(request: IRegisterAppRequest): Promise<App> {
// TODO: Some kind of authentication maybe?
const app = await this.prisma.app.upsert({
where: { localName: request.name },
create: { localName: request.name, title: request.name },
update: { title: request.name },
});
return app;
}
public async getNode(request: IGetNodeRequest): Promise<Node | null> {
// TODO: Apply filters
const node = this.prisma.node.findFirst();
return node;
}
public async createNode(request: ICreateNodeRequest): Promise<Node> {
// TODO: Calculate the interfaces that must be implemented // TODO: Calculate the interfaces that must be implemented
// - Use the interface application rules // - Use the interface application rules
// - Insert them into the database // - Insert them into the database
@ -41,13 +47,25 @@ export default class Database {
// TODO: Run all validations on all of the nodes // TODO: Run all validations on all of the nodes
const prisma = new PrismaClient(); return await this.prisma.$transaction(async (client) => {
const createdNode = await client.node.create({
return await prisma.$transaction(async (client) => {
const createdNode = client.node.create({
data: { label: request.label }, data: { label: request.label },
}); });
if (request.metadata_keys) {
for (const [key, value] of request.metadata_keys.entries()) {
const meta = await client.nodeMeta.create({
data: {
appKey: key.appKey,
appId: key.appId,
nodeId: createdNode.id,
value: Buffer.from(value),
},
});
console.log("Creating metadata keys", key, value, meta);
}
}
return createdNode; return createdNode;
}); });
} }

View file

@ -1,4 +0,0 @@
import {VM} from "vm2";
export default class Executor {
}

42
src/core/types.ts Normal file
View file

@ -0,0 +1,42 @@
export interface IRegisterAppRequest {
name: string;
}
export interface IMetaKey {
appId: string;
appKey: string;
}
export interface IGetNodeRequest {
node_id: string;
}
export interface ICreateNodeRequest {
/**
* A label for humans. This is not used by the database in any way, it's just
* something to print when debugging.
*/
label: string;
/**
* A mapping from labels to node creation requests.
*/
other_nodes?: Map<string, ICreateNodeRequest>;
/**
* A set of edges, using the IDs in auxiliary_nodes.
*/
edges?: Set<ICreateEdgeRequest>;
/**
* A set of metadata keys to values
*/
metadata_keys?: Map<IMetaKey, string>;
}
export interface ICreateEdgeRequest {
label: string;
from_node: string;
to_node: string;
metadata: unknown;
}

10
src/db/index.ts Normal file
View file

@ -0,0 +1,10 @@
// import { PrismaClient } from "@prisma/client";
// export const db = new PrismaClient();
import Database from "~/core/db";
export const db = new Database();
export const todosApp = await db.registerApp({
name: "todos",
});

95
src/db/session.tsx Normal file
View file

@ -0,0 +1,95 @@
import { PrismaClient } from "@prisma/client";
import { redirect } from "solid-start/server";
import { createCookieSessionStorage } from "solid-start/session";
import { db } from ".";
type LoginForm = {
username: string;
password: string;
};
export async function register({ username, password }: LoginForm) {
return db.user.create({
data: { username: username, password },
});
}
export async function login({ username, password }: LoginForm) {
const user = await db.user.findUnique({ where: { username } });
if (!user) return null;
const isCorrectPassword = password === user.password;
if (!isCorrectPassword) return null;
return user;
}
const sessionSecret = import.meta.env.SESSION_SECRET;
const storage = createCookieSessionStorage({
cookie: {
name: "RJ_session",
// secure doesn't work on localhost for Safari
// https://web.dev/when-to-use-local-https/
secure: true,
secrets: ["hello"],
sameSite: "lax",
path: "/",
maxAge: 60 * 60 * 24 * 30,
httpOnly: true,
},
});
export function getUserSession(request: Request) {
return storage.getSession(request.headers.get("Cookie"));
}
export async function getUserId(request: Request) {
const session = await getUserSession(request);
const userId = session.get("userId");
if (!userId || typeof userId !== "string") return null;
return userId;
}
export async function requireUserId(
request: Request,
redirectTo: string = new URL(request.url).pathname
) {
const session = await getUserSession(request);
const userId = session.get("userId");
if (!userId || typeof userId !== "string") {
const searchParams = new URLSearchParams([["redirectTo", redirectTo]]);
throw redirect(`/login?${searchParams}`);
}
return userId;
}
export async function getUser(db: PrismaClient, request: Request) {
const userId = await getUserId(request);
if (typeof userId !== "string") {
return null;
}
try {
const user = await db.user.findUnique({ where: { id: userId } });
return user;
} catch {
throw logout(request);
}
}
export async function logout(request: Request) {
const session = await storage.getSession(request.headers.get("Cookie"));
return redirect("/login", {
headers: {
"Set-Cookie": await storage.destroySession(session),
},
});
}
export async function createUserSession(userId: string, redirectTo: string) {
const session = await storage.getSession();
session.set("userId", userId);
return redirect(redirectTo, {
headers: {
"Set-Cookie": await storage.commitSession(session),
},
});
}

15
src/db/useUser.tsx Normal file
View file

@ -0,0 +1,15 @@
import { PrismaClient } from "@prisma/client";
import { createServerData$, redirect } from "solid-start/server";
import { getUser } from "./session";
export const useUser = () =>
createServerData$(async (_, { request }) => {
const db = new PrismaClient();
const user = await getUser(db, request);
if (!user) {
throw redirect("/login");
}
return user;
});

3
src/entry-client.tsx Normal file
View file

@ -0,0 +1,3 @@
import { mount, StartClient } from "solid-start/entry-client";
mount(() => <StartClient />, document);

9
src/entry-server.tsx Normal file
View file

@ -0,0 +1,9 @@
import {
StartServer,
createHandler,
renderAsync,
} from "solid-start/entry-server";
export default createHandler(
renderAsync((event) => <StartServer event={event} />)
);

4
src/root.css Normal file
View file

@ -0,0 +1,4 @@
body {
font-family: Gordita, Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans",
"Helvetica Neue", sans-serif;
}

36
src/root.tsx Normal file
View file

@ -0,0 +1,36 @@
// @refresh reload
import { Suspense } from "solid-js";
import {
Body,
ErrorBoundary,
FileRoutes,
Head,
Html,
Meta,
Routes,
Scripts,
Title,
} from "solid-start";
import "./root.css";
export default function Root() {
return (
<Html lang="en">
<Head>
<Title>SolidStart - With Auth</Title>
<Meta charset="utf-8" />
<Meta name="viewport" content="width=device-width, initial-scale=1" />
</Head>
<Body>
<ErrorBoundary>
<Suspense fallback={<div>Loading</div>}>
<Routes>
<FileRoutes />
</Routes>
</Suspense>
</ErrorBoundary>
<Scripts />
</Body>
</Html>
);
}

7
src/routes/[...404].tsx Normal file
View file

@ -0,0 +1,7 @@
export default function NotFound() {
return (
<main class="full-width">
<h1>Page Not Found</h1>
</main>
);
}

28
src/routes/index.tsx Normal file
View file

@ -0,0 +1,28 @@
import { refetchRouteData, useRouteData } from "solid-start";
import { createServerAction$ } from "solid-start/server";
import { logout } from "~/db/session";
import { useUser } from "../db/useUser";
export function routeData() {
return useUser();
}
export default function Home() {
const user = useRouteData<typeof routeData>();
const [, { Form }] = createServerAction$((f: FormData, { request }) =>
logout(request)
);
return (
<main class="full-width">
<h1>Hello {user()?.username}</h1>
<h3>Message board</h3>
<button onClick={() => refetchRouteData()}>Refresh</button>
<Form>
<button name="logout" type="submit">
Logout
</button>
</Form>
</main>
);
}

136
src/routes/login.tsx Normal file
View file

@ -0,0 +1,136 @@
import { Show } from "solid-js";
import { useParams, useRouteData } from "solid-start";
import { FormError } from "solid-start/data";
import {
createServerAction$,
createServerData$,
redirect,
} from "solid-start/server";
import { db } from "~/db";
import { createUserSession, getUser, login, register } from "~/db/session";
function validateUsername(username: unknown) {
if (typeof username !== "string" || username.length < 3) {
return `Usernames must be at least 3 characters long`;
}
}
function validatePassword(password: unknown) {
if (typeof password !== "string" || password.length < 6) {
return `Passwords must be at least 6 characters long`;
}
}
export function routeData() {
return createServerData$(async (_, { request }) => {
if (await getUser(db, request)) {
throw redirect("/");
}
return {};
});
}
export default function Login() {
const data = useRouteData<typeof routeData>();
const params = useParams();
const [loggingIn, { Form }] = createServerAction$(async (form: FormData) => {
const loginType = form.get("loginType");
const username = form.get("username");
const password = form.get("password");
const redirectTo = form.get("redirectTo") || "/";
if (
typeof loginType !== "string" ||
typeof username !== "string" ||
typeof password !== "string" ||
typeof redirectTo !== "string"
) {
throw new FormError(`Form not submitted correctly.`);
}
const fields = { loginType, username, password };
const fieldErrors = {
username: validateUsername(username),
password: validatePassword(password),
};
if (Object.values(fieldErrors).some(Boolean)) {
throw new FormError("Fields invalid", { fieldErrors, fields });
}
switch (loginType) {
case "login": {
const user = await login({ username, password });
if (!user) {
throw new FormError(`Username/Password combination is incorrect`, {
fields,
});
}
return createUserSession(`${user.id}`, redirectTo);
}
case "register": {
const userExists = await db.user.findUnique({ where: { username } });
if (userExists) {
throw new FormError(`User with username ${username} already exists`, {
fields,
});
}
const user = await register({ username, password });
if (!user) {
throw new FormError(
`Something went wrong trying to create a new user.`,
{
fields,
}
);
}
return createUserSession(`${user.id}`, redirectTo);
}
default: {
throw new FormError(`Login type invalid`, { fields });
}
}
});
return (
<main>
<h1>Login</h1>
<Form>
<input
type="hidden"
name="redirectTo"
value={params.redirectTo ?? "/"}
/>
<fieldset>
<legend>Login or Register?</legend>
<label>
<input type="radio" name="loginType" value="login" checked={true} />{" "}
Login
</label>
<label>
<input type="radio" name="loginType" value="register" /> Register
</label>
</fieldset>
<div>
<label for="username-input">Username</label>
<input name="username" placeholder="kody" />
</div>
<Show when={loggingIn.error?.fieldErrors?.username}>
<p role="alert">{loggingIn.error.fieldErrors.username}</p>
</Show>
<div>
<label for="password-input">Password</label>
<input name="password" type="password" placeholder="twixrox" />
</div>
<Show when={loggingIn.error?.fieldErrors?.password}>
<p role="alert">{loggingIn.error.fieldErrors.password}</p>
</Show>
<Show when={loggingIn.error}>
<p role="alert" id="error-message">
{loggingIn.error.message}
</p>
</Show>
<button type="submit">{data() ? "Login" : ""}</button>
</Form>
</main>
);
}

View file

@ -1,21 +1,54 @@
import { For, batch, createEffect, createSignal } from "solid-js"; import { For, batch, createEffect, createSignal } from "solid-js";
import { SetStoreFunction, Store, createStore } from "solid-js/store"; import { SetStoreFunction, Store, createStore } from "solid-js/store";
import { createServerAction$ } from "solid-start/server";
type TodoItem = { title: string; done: boolean }; import { db, todosApp } from "~/db";
const Todos = () => { interface TodoItem {
title: string;
done: boolean;
committed: boolean;
}
export default function Todos() {
const [newTitle, setTitle] = createSignal(""); const [newTitle, setTitle] = createSignal("");
const [todos, setTodos] = createLocalStore<TodoItem[]>("todos", []); const [todos, setTodos] = createTodoStore<TodoItem[]>("todos", []);
const [adding, add] = createServerAction$(
async ({ title }: { title: string }) => {
const metadata_keys = new Map();
const keyOf = (appKey: string) => ({ appId: todosApp.id, appKey });
metadata_keys.set(keyOf("title"), title);
const node = await db.createNode({
label: `todo-${title}`,
metadata_keys,
});
console.log("Created node", node);
return node;
}
);
const addTodo = (e: SubmitEvent) => { const addTodo = (e: SubmitEvent) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation();
const idx = todos.length;
const title = newTitle();
batch(() => { batch(() => {
setTodos(todos.length, { setTodos(idx, {
title: newTitle(), title,
done: false, done: false,
committed: false,
}); });
setTitle(""); setTitle("");
}); });
add({ title });
console.log("Adding", adding);
}; };
return ( return (
@ -30,6 +63,7 @@ const Todos = () => {
/> />
<button>+</button> <button>+</button>
</form> </form>
Todos:
<For each={todos}> <For each={todos}>
{(todo, i) => ( {(todo, i) => (
<div> <div>
@ -51,22 +85,27 @@ const Todos = () => {
</For> </For>
</> </>
); );
}; }
export default Todos; function createTodoStore<T extends object>(
export function createLocalStore<T extends object>(
name: string, name: string,
init: T init: T
): [Store<T>, SetStoreFunction<T>] { ): [Store<T>, SetStoreFunction<T>] {
const localState = localStorage.getItem(name); // const localState = localStorage.getItem(name);
const [state, setState] = createStore<T>( const [state, setState] = createStore<T>(
localState ? JSON.parse(localState) : init init
// localState ? JSON.parse(localState) : init
); );
createEffect(() => localStorage.setItem(name, JSON.stringify(state)));
createEffect(() => {
console.log("Calling local storage");
// localStorage.setItem(name, JSON.stringify(state));
});
return [state, setState]; return [state, setState];
} }
export function removeIndex<T>(array: readonly T[], index: number): T[] { function removeIndex<T>(array: readonly T[], index: number): T[] {
return [...array.slice(0, index), ...array.slice(index + 1)]; return [...array.slice(0, index), ...array.slice(index + 1)];
} }

View file

@ -1,28 +0,0 @@
import express from "express";
import Database from "../core/db";
const app = express();
app.get("/", (req, res) => {
res.send("Welcome to the Dinosaur API!");
});
// Create a node
app.post("/api/node", async (req, res) => {
const node = await Database.createNode({
label: "hellosu",
other_nodes: new Map(),
edges: new Set(),
});
console.log("node", node);
res.json({ success: true });
});
import todos from "./todos";
app.use("/api/todos", todos);
console.log("Listening.");
app.listen(8605, "0.0.0.0");

View file

@ -1,8 +0,0 @@
import {Router} from "express";
const router = Router();
router.get("/", async (req, res) => {
});
export default router;

View file

@ -1,20 +1,17 @@
{ {
"compilerOptions": { "compilerOptions": {
"strict": true, "allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"target": "ESNext", "target": "ESNext",
"module": "ESNext", "module": "ESNext",
"moduleResolution": "node", "moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "solid-js", "jsxImportSource": "solid-js",
"types": ["vite/client"], "jsx": "preserve",
"noEmit": true, "strict": true,
"isolatedModules": true, "types": ["solid-start/env"],
"baseUrl": "./",
"baseUrl": ".",
"paths": { "paths": {
"@panorama": ["src/*"] "~/*": ["./src/*"]
} }
} }
} }

View file

@ -1,12 +0,0 @@
import { defineConfig } from "vite";
import solidPlugin from "vite-plugin-solid";
export default defineConfig({
plugins: [solidPlugin()],
server: {
port: 3000,
},
build: {
target: "esnext",
},
});

7
vite.config.ts Normal file
View file

@ -0,0 +1,7 @@
import solid from "solid-start/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [solid()],
ssr: { external: ["@prisma/client"] },
});