This commit is contained in:
parent
589a9ce2fe
commit
1550d77ca6
33 changed files with 3165 additions and 11259 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,3 +4,4 @@ node_modules
|
|||
.env
|
||||
/generated
|
||||
/result*
|
||||
.solid
|
||||
|
|
30
README.md
Normal file
30
README.md
Normal 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`.
|
|
@ -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)
|
|
@ -1,7 +1,7 @@
|
|||
- CLI API
|
||||
|
||||
- `mznotes install` : Install a script into the database
|
||||
- `mznotes show` : Show the information about a particular node, by ID
|
||||
- `panorama install` : Install a script into the database
|
||||
- `panorama show` : Show the information about a particular node, by ID
|
||||
|
||||
- Database of nodes + code
|
||||
|
||||
|
@ -90,7 +90,7 @@
|
|||
|
||||
- Interfaces:
|
||||
|
||||
-
|
||||
- `addTodo()`
|
||||
|
||||
- Content / blob storage
|
||||
|
||||
|
@ -137,3 +137,5 @@
|
|||
- Have the frontend also shipped with the backend?
|
||||
|
||||
- Subset of React components to typecheck against
|
||||
|
||||
- Aggregated queries and group by? Joins?
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
cargo-expand
|
||||
cargo-flamegraph
|
||||
cargo-watch
|
||||
sqlite
|
||||
|
||||
zlib
|
||||
|
||||
|
|
13565
package-lock.json
generated
13565
package-lock.json
generated
File diff suppressed because it is too large
Load diff
40
package.json
40
package.json
|
@ -1,30 +1,26 @@
|
|||
{
|
||||
"name": "my-prisma-project",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"name": "ouais",
|
||||
"scripts": {
|
||||
"dev": "vite"
|
||||
"dev": "solid-start dev",
|
||||
"build": "solid-start build",
|
||||
"start": "solid-start start"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"types": "./src/api.d.ts",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.17",
|
||||
"json-schema-static-docs": "^0.23.0",
|
||||
"nativescript": "^8.5.1",
|
||||
"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"
|
||||
"solid-start-node": "^0.2.19",
|
||||
"typescript": "^4.9.4",
|
||||
"vite": "^4.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^4.11.0",
|
||||
"express": "^4.18.2",
|
||||
"solid-js": "^1.6.15",
|
||||
"vm2": "^3.9.14"
|
||||
"@prisma/client": "^4.9.0",
|
||||
"@solidjs/meta": "^0.28.2",
|
||||
"@solidjs/router": "^0.8.2",
|
||||
"prisma": "^4.9.0",
|
||||
"solid-js": "^1.7.2",
|
||||
"solid-start": "^0.2.26",
|
||||
"undici": "^5.15.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
}
|
|
@ -4,35 +4,32 @@ CREATE TABLE "Node" (
|
|||
"label" TEXT
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Edge" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Interface" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Graph" (
|
||||
CREATE TABLE "Edge" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"label" TEXT,
|
||||
"appId" TEXT NOT NULL,
|
||||
"appKey" TEXT NOT NULL,
|
||||
"fromId" TEXT NOT NULL,
|
||||
"toId" TEXT NOT NULL,
|
||||
|
||||
PRIMARY KEY ("fromId", "toId"),
|
||||
CONSTRAINT "Graph_fromId_fkey" FOREIGN KEY ("fromId") 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
|
||||
CONSTRAINT "Edge_appId_fkey" FOREIGN KEY ("appId") REFERENCES "App" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
CONSTRAINT "Edge_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
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "NodeMeta" (
|
||||
"nodeId" TEXT NOT NULL,
|
||||
"appId" TEXT NOT NULL,
|
||||
"key" TEXT NOT NULL,
|
||||
"appKey" TEXT 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_appId_fkey" FOREIGN KEY ("appId") REFERENCES "App" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
@ -59,10 +56,20 @@ CREATE TABLE "App" (
|
|||
CREATE TABLE "Patterns" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"pattern" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"appId" TEXT NOT NULL,
|
||||
"functionName" TEXT NOT NULL,
|
||||
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
|
||||
CREATE UNIQUE INDEX "App_localName_key" ON "App"("localName");
|
|
@ -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;
|
|
@ -76,7 +76,7 @@ model App {
|
|||
localName String @unique
|
||||
title String
|
||||
|
||||
installed DateTime
|
||||
installed DateTime @default(now())
|
||||
|
||||
patterns Patterns[]
|
||||
metaKeys NodeMeta[]
|
||||
|
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 664 B |
|
@ -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;
|
|
@ -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);
|
|
@ -1 +0,0 @@
|
|||
import Database from "./db";
|
|
@ -1,32 +1,38 @@
|
|||
import { Node, PrismaClient } from "@prisma/client";
|
||||
|
||||
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>;
|
||||
}
|
||||
|
||||
interface ICreateEdgeRequest {
|
||||
label: string;
|
||||
from_node: string;
|
||||
to_node: string;
|
||||
metadata: unknown;
|
||||
}
|
||||
import { Node, App, PrismaClient } from "@prisma/client";
|
||||
import {
|
||||
ICreateNodeRequest,
|
||||
IGetNodeRequest,
|
||||
IRegisterAppRequest,
|
||||
} from "./types";
|
||||
|
||||
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
|
||||
// - Use the interface application rules
|
||||
// - Insert them into the database
|
||||
|
@ -41,13 +47,25 @@ export default class Database {
|
|||
|
||||
// TODO: Run all validations on all of the nodes
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
return await prisma.$transaction(async (client) => {
|
||||
const createdNode = client.node.create({
|
||||
return await this.prisma.$transaction(async (client) => {
|
||||
const createdNode = await client.node.create({
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
import {VM} from "vm2";
|
||||
|
||||
export default class Executor {
|
||||
}
|
42
src/core/types.ts
Normal file
42
src/core/types.ts
Normal 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
10
src/db/index.ts
Normal 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
95
src/db/session.tsx
Normal 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
15
src/db/useUser.tsx
Normal 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
3
src/entry-client.tsx
Normal file
|
@ -0,0 +1,3 @@
|
|||
import { mount, StartClient } from "solid-start/entry-client";
|
||||
|
||||
mount(() => <StartClient />, document);
|
9
src/entry-server.tsx
Normal file
9
src/entry-server.tsx
Normal 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
4
src/root.css
Normal 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
36
src/root.tsx
Normal 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
7
src/routes/[...404].tsx
Normal 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
28
src/routes/index.tsx
Normal 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
136
src/routes/login.tsx
Normal 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>
|
||||
);
|
||||
}
|
|
@ -1,21 +1,54 @@
|
|||
import { For, batch, createEffect, createSignal } from "solid-js";
|
||||
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 [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) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const idx = todos.length;
|
||||
const title = newTitle();
|
||||
|
||||
batch(() => {
|
||||
setTodos(todos.length, {
|
||||
title: newTitle(),
|
||||
setTodos(idx, {
|
||||
title,
|
||||
done: false,
|
||||
committed: false,
|
||||
});
|
||||
|
||||
setTitle("");
|
||||
});
|
||||
|
||||
add({ title });
|
||||
console.log("Adding", adding);
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -30,6 +63,7 @@ const Todos = () => {
|
|||
/>
|
||||
<button>+</button>
|
||||
</form>
|
||||
Todos:
|
||||
<For each={todos}>
|
||||
{(todo, i) => (
|
||||
<div>
|
||||
|
@ -51,22 +85,27 @@ const Todos = () => {
|
|||
</For>
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default Todos;
|
||||
|
||||
export function createLocalStore<T extends object>(
|
||||
function createTodoStore<T extends object>(
|
||||
name: string,
|
||||
init: T
|
||||
): [Store<T>, SetStoreFunction<T>] {
|
||||
const localState = localStorage.getItem(name);
|
||||
// const localState = localStorage.getItem(name);
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
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)];
|
||||
}
|
|
@ -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");
|
|
@ -1,8 +0,0 @@
|
|||
import {Router} from "express";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get("/", async (req, res) => {
|
||||
});
|
||||
|
||||
export default router;
|
|
@ -1,20 +1,17 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"jsx": "preserve",
|
||||
"jsxImportSource": "solid-js",
|
||||
"types": ["vite/client"],
|
||||
"noEmit": true,
|
||||
"isolatedModules": true,
|
||||
|
||||
"baseUrl": ".",
|
||||
"jsx": "preserve",
|
||||
"strict": true,
|
||||
"types": ["solid-start/env"],
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@panorama": ["src/*"]
|
||||
"~/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
7
vite.config.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import solid from "solid-start/vite";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [solid()],
|
||||
ssr: { external: ["@prisma/client"] },
|
||||
});
|
Loading…
Reference in a new issue