From ebd9f7ae36924fbcd0e148f2585a44ca6afc4c54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sof=C3=ADa=20Aritz?= Date: Fri, 8 Dec 2023 18:43:56 +0100 Subject: [PATCH] aybWeb (again) --- .editorconfig | 7 + .gitignore | 27 + .prettierrc | 7 + .vscode/extensions.json | 3 + README.md | 47 + index.html | 12 + package.json | 33 + postcss.config.js | 6 + public/.gitkeep | 0 src/App.svelte | 40 + src/app.css | 37 + src/lib/api/index.ts | 103 ++ src/lib/api/types.ts | 41 + src/lib/auth/stores.ts | 57 + src/lib/components/Header.svelte | 18 + src/lib/components/Redirect.svelte | 7 + src/lib/components/common/Button.svelte | 8 + src/lib/components/common/Input.svelte | 18 + src/lib/components/common/index.ts | 2 + .../components/database/DatabaseCard.svelte | 74 + .../database/DatabasePagesHeader.svelte | 27 + .../database/DatabaseQueryResult.svelte | 24 + src/main.ts | 9 + src/routes/Confirm.svelte | 80 ++ src/routes/DatabaseOverview.svelte | 32 + src/routes/DatabaseQuery.svelte | 79 ++ src/routes/Home.svelte | 25 + src/routes/Login.svelte | 75 + src/vite-env.d.ts | 2 + svelte.config.js | 7 + tailwind.config.js | 77 ++ tsconfig.json | 20 + tsconfig.node.json | 9 + vite.config.ts | 7 + yarn.lock | 1227 +++++++++++++++++ 35 files changed, 2247 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .prettierrc create mode 100644 .vscode/extensions.json create mode 100644 README.md create mode 100644 index.html create mode 100644 package.json create mode 100644 postcss.config.js create mode 100644 public/.gitkeep create mode 100644 src/App.svelte create mode 100644 src/app.css create mode 100644 src/lib/api/index.ts create mode 100644 src/lib/api/types.ts create mode 100644 src/lib/auth/stores.ts create mode 100644 src/lib/components/Header.svelte create mode 100644 src/lib/components/Redirect.svelte create mode 100644 src/lib/components/common/Button.svelte create mode 100644 src/lib/components/common/Input.svelte create mode 100644 src/lib/components/common/index.ts create mode 100644 src/lib/components/database/DatabaseCard.svelte create mode 100644 src/lib/components/database/DatabasePagesHeader.svelte create mode 100644 src/lib/components/database/DatabaseQueryResult.svelte create mode 100644 src/main.ts create mode 100644 src/routes/Confirm.svelte create mode 100644 src/routes/DatabaseOverview.svelte create mode 100644 src/routes/DatabaseQuery.svelte create mode 100644 src/routes/Home.svelte create mode 100644 src/routes/Login.svelte create mode 100644 src/vite-env.d.ts create mode 100644 svelte.config.js create mode 100644 tailwind.config.js create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts create mode 100644 yarn.lock diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..abfc200 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +root = true + +[*] +indent_style = tab +tab_width = 4 +end_of_line = lf +insert_final_newline = true \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4abe73e --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Environment variables +.env diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..300076e --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], + "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }], + "useTabs": true, + "semi": false, + "printWidth": 100 +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..28d1e67 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["svelte.svelte-vscode"] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..ec70c52 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# Svelte + TS + Vite + +This template should help get you started developing with Svelte and TypeScript in Vite. + +## Recommended IDE Setup + +[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). + +## Need an official Svelte framework? + +Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more. + +## Technical considerations + +**Why use this over SvelteKit?** + +- It brings its own routing solution which might not be preferable for some users. +- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app. + +This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project. + +Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate. + +**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?** + +Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information. + +**Why include `.vscode/extensions.json`?** + +Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project. + +**Why enable `allowJs` in the TS template?** + +While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant. + +**Why is HMR not preserving my local component state?** + +HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr). + +If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR. + +```ts +// store.ts +// An extremely simple external store +import { writable } from "svelte/store" +export default writable(0) +``` diff --git a/index.html b/index.html new file mode 100644 index 0000000..3b94d91 --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + aybWeb + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..5cb2186 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "ayb-web", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.json", + "prettify": "yarn prettier . --write" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^3.0.0", + "@tsconfig/svelte": "^5.0.2", + "autoprefixer": "^10.4.16", + "felte": "^1.2.12", + "postcss": "^8.4.32", + "prettier": "^3.1.0", + "prettier-plugin-svelte": "^3.1.2", + "prettier-plugin-tailwindcss": "^0.5.9", + "svelte": "^4.2.3", + "svelte-check": "^3.6.0", + "svelte-routing": "sofiaritz/svelte-routing-exports", + "tailwindcss": "^3.3.6", + "tslib": "^2.6.2", + "typescript": "^5.2.2", + "vite": "^5.0.0" + }, + "dependencies": { + "pattern.css": "^1.0.0" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..1a52624 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/public/.gitkeep b/public/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/App.svelte b/src/App.svelte new file mode 100644 index 0000000..679f1d6 --- /dev/null +++ b/src/App.svelte @@ -0,0 +1,40 @@ + + + +
+
+
+ {#if $loggedIn} + + + + + + + + + + + + + {:else} + + + + + + + {/if} +
+
+ diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..a8b439c --- /dev/null +++ b/src/app.css @@ -0,0 +1,37 @@ +@font-face { + font-family: "JetBrains Mono"; + src: url("https://cdn.sofiaritz.com/fonts/ofl/jetbrainsmono/JetBrainsMono-Italic[wght].ttf"); + font-style: italic; + font-weight: 300; +} + +@font-face { + font-family: "JetBrains Mono"; + src: url("https://cdn.sofiaritz.com/fonts/ofl/jetbrainsmono/JetBrainsMono[wght].ttf"); + font-style: normal; + font-weight: 300 400; +} + +@font-face { + font-family: "Rubik"; + src: url("https://cdn.sofiaritz.com/fonts/ofl/rubik/Rubik[wght].ttf"); + font-style: normal; + font-weight: 400 500; +} + +@font-face { + font-family: "Rubik"; + src: url("https://cdn.sofiaritz.com/fonts/ofl/rubik/Rubik-Italic[wght].ttf"); + font-style: italic; + font-weight: 400 500; +} + +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer components { + a { + @apply text-blue-700 underline hover:text-blue-800; + } +} diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts new file mode 100644 index 0000000..f9d7f25 --- /dev/null +++ b/src/lib/api/index.ts @@ -0,0 +1,103 @@ +import type { + DatabaseCreation, + DatabaseQuery, + EntityInfo, + Response, + UserInstanceData, + UserToken, +} from "./types" +import { DBType } from "./types" + +async function request( + endpoint: string, + auth: UserInstanceData, + { + headers: desiredHeaders = undefined, + text = undefined, + json = undefined, + method = undefined, + }: { + headers?: HeadersInit + text?: string + json?: any + method?: string + } = {}, +): Promise> { + let url = new URL(auth.endpoint) + url.pathname = endpoint + + let headers: any = { + ...desiredHeaders, + } + if (auth.token != null && auth.token !== "undefined") { + headers["Authorization"] = `Bearer ${auth.token}` + } + + let body + if (text != null && json != null) { + console.error( + `Both text and json are set in a request to ${auth.endpoint}. JSON will be used, this behaviour may change at any time!`, + ) + body = JSON.stringify(json) + } else if (text != null) { + body = text + } else if (json != null) { + body = JSON.stringify(json) + } + + let res = await fetch(url, { + headers, + body, + method, + }) + + return res.json() +} + +export function unwrapResponse(response: Response): T { + if (isError(response)) { + throw response + } else { + return response as T + } +} + +// @ts-ignore +export const isError = (response: Response): boolean => response?.["message"] != null + +export async function login(entity: string, auth: UserInstanceData) { + await request("/v1/log_in", auth, { + headers: { + entity: entity, + }, + method: "POST", + }) +} + +export async function confirm(token: string, auth: UserInstanceData) { + return request("/v1/confirm", auth, { + headers: { + "authentication-token": token, + }, + method: "POST", + }) +} + +export async function createDatabase(slug: string, databaseType: DBType, auth: UserInstanceData) { + return request(`/v1/${slug}/create`, auth, { + headers: { + "db-type": databaseType, + }, + }) +} + +export async function entityInfo(entity: string, auth: UserInstanceData) { + return request(`/v1/entity/${entity}`, auth) +} + +export async function queryDatabase(slug: string, query: string, auth: UserInstanceData) { + return request(`/v1/${slug}/query`, auth, { + text: query, + method: "POST", + }) +} diff --git a/src/lib/api/types.ts b/src/lib/api/types.ts new file mode 100644 index 0000000..5fc72cf --- /dev/null +++ b/src/lib/api/types.ts @@ -0,0 +1,41 @@ +export type Response = Error | T + +export type UserInstanceData = { + endpoint: string + entity?: string + token?: string +} + +export enum DBType { + SQLite = "sqlite", + DuckDB = "duckdb", +} + +export type Error = { + message: string +} + +export type DatabaseInfo = { + slug: string + database_type: string +} + +export type EntityInfo = { + slug: string + databases: DatabaseInfo[] +} + +export type UserToken = { + token: string +} + +export type DatabaseCreation = { + entity: string + database: string + database_type: DBType +} + +export type DatabaseQuery = { + fields: string[] + rows: string[][] +} diff --git a/src/lib/auth/stores.ts b/src/lib/auth/stores.ts new file mode 100644 index 0000000..b6f5a56 --- /dev/null +++ b/src/lib/auth/stores.ts @@ -0,0 +1,57 @@ +import { writable } from "svelte/store" +import type { EntityInfo, UserInstanceData } from "../api/types" +import { entityInfo, isError } from "../api" + +const CREDENTIAL_STORAGE_KEY = "v1/auth.token" + +export function parseCredentials(credentials: string): UserInstanceData { + let url = new URL(credentials) + return { + endpoint: url.origin, + entity: url.username && url.password ? url.username : undefined, + token: url.username && url.password ? url.password : undefined, + } +} + +/// This store contains the _raw_ credentials. Use `userInstanceData` for usable data. +export const credentials = writable(localStorage.getItem(CREDENTIAL_STORAGE_KEY)) +/// This store is guaranteed to contain `entity` and `token` +export const userInstanceData = writable() +export const userInfo = writable() +export const loggedIn = writable(false) + +credentials.subscribe(async (v) => { + const resetStores = () => { + loggedIn.set(false) + userInfo.set(undefined) + userInstanceData.set(undefined) + localStorage.removeItem(CREDENTIAL_STORAGE_KEY) + } + + if ( + v == null || + v.length === 0 || + parseCredentials(v).entity == null || + parseCredentials(v).token == null + ) { + resetStores() + return + } + + let instanceData = parseCredentials(v) + + let data = await entityInfo(instanceData.entity!, instanceData) + if (isError(data)) { + console.error("Failed to retrieve entity info") + // TODO(sofiaritz): Improve error handling. Even though this case should *never* happen, we should be ready. + alert("Failed to retrieve entity info. Try again later") + resetStores() + return + } + + data = data as EntityInfo // We have verified that this is not an error above. + userInstanceData.set(instanceData) + userInfo.set(data) + loggedIn.set(true) + localStorage.setItem(CREDENTIAL_STORAGE_KEY, v) +}) diff --git a/src/lib/components/Header.svelte b/src/lib/components/Header.svelte new file mode 100644 index 0000000..a13ccbd --- /dev/null +++ b/src/lib/components/Header.svelte @@ -0,0 +1,18 @@ + + +
+

+ aybWeb +

+ {#if $loggedIn} + Logged in as {$userInfo.slug} + {:else} + Login + {/if} +
diff --git a/src/lib/components/Redirect.svelte b/src/lib/components/Redirect.svelte new file mode 100644 index 0000000..fd6da25 --- /dev/null +++ b/src/lib/components/Redirect.svelte @@ -0,0 +1,7 @@ + diff --git a/src/lib/components/common/Button.svelte b/src/lib/components/common/Button.svelte new file mode 100644 index 0000000..dd73bd6 --- /dev/null +++ b/src/lib/components/common/Button.svelte @@ -0,0 +1,8 @@ + + + diff --git a/src/lib/components/common/Input.svelte b/src/lib/components/common/Input.svelte new file mode 100644 index 0000000..9903ceb --- /dev/null +++ b/src/lib/components/common/Input.svelte @@ -0,0 +1,18 @@ + + + diff --git a/src/lib/components/common/index.ts b/src/lib/components/common/index.ts new file mode 100644 index 0000000..2faa772 --- /dev/null +++ b/src/lib/components/common/index.ts @@ -0,0 +1,2 @@ +export { default as Button } from "./Button.svelte" +export { default as Input } from "./Input.svelte" diff --git a/src/lib/components/database/DatabaseCard.svelte b/src/lib/components/database/DatabaseCard.svelte new file mode 100644 index 0000000..7a4354c --- /dev/null +++ b/src/lib/components/database/DatabaseCard.svelte @@ -0,0 +1,74 @@ + + + + {#await background(slug)} +
+ {:then result} +
+ {/await} + +
+ {slug.split(".")[0]} +
+ Database type: {displayType(type)} + diff --git a/src/lib/components/database/DatabasePagesHeader.svelte b/src/lib/components/database/DatabasePagesHeader.svelte new file mode 100644 index 0000000..a7b1ded --- /dev/null +++ b/src/lib/components/database/DatabasePagesHeader.svelte @@ -0,0 +1,27 @@ + + +
+ {#if selected === "overview"} + Overview + {:else} + Overview + {/if} + + {#if selected === "query"} + Query + {:else} + Query + {/if} + + {#if selected === "settings"} + Settings + {:else} + Settings + {/if} +
diff --git a/src/lib/components/database/DatabaseQueryResult.svelte b/src/lib/components/database/DatabaseQueryResult.svelte new file mode 100644 index 0000000..f725fe6 --- /dev/null +++ b/src/lib/components/database/DatabaseQueryResult.svelte @@ -0,0 +1,24 @@ + + + + + + {#each data.fields as field} + + {/each} + + + + {#each data.rows as row} + + {#each row as value} + + {/each} + + {/each} + +
{field}
{value}
diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..398abf3 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,9 @@ +import "./app.css" +import "pattern.css/dist/pattern.min.css" +import App from "./App.svelte" + +const app = new App({ + target: document.getElementById("app"), +}) + +export default app diff --git a/src/routes/Confirm.svelte b/src/routes/Confirm.svelte new file mode 100644 index 0000000..e411acc --- /dev/null +++ b/src/routes/Confirm.svelte @@ -0,0 +1,80 @@ + + +
+

Confirmation

+

We are almost there...

+
+ + + + +
+
diff --git a/src/routes/DatabaseOverview.svelte b/src/routes/DatabaseOverview.svelte new file mode 100644 index 0000000..9e7b8e3 --- /dev/null +++ b/src/routes/DatabaseOverview.svelte @@ -0,0 +1,32 @@ + + +
+ +
+

Congratulations, you've got your database set-up!

+
+
+

Documentation

+ Check out the link below for more information about ayb + ayb.host/docs +
+
+

Getting help

+ Contact the instance owner for support + ayb.host/support +
+
+
+
+

Usage limits

+ You are not subject to usage limits right now + ayb.host/limits +
+
+
+
diff --git a/src/routes/DatabaseQuery.svelte b/src/routes/DatabaseQuery.svelte new file mode 100644 index 0000000..9ddf69b --- /dev/null +++ b/src/routes/DatabaseQuery.svelte @@ -0,0 +1,79 @@ + + +
+ +
+

Start querying your database

+
+ + +
+
+ {#if bigMode === true} +
+