Compare commits

...

2 commits

Author SHA1 Message Date
sam
a80f89d038 add frontend skeleton 2024-03-26 22:10:04 +01:00
sam
0c52ebb7bc chore: sort imports 2024-03-26 19:49:30 +01:00
25 changed files with 2114 additions and 6 deletions

6
.gitignore vendored
View file

@ -1,3 +1,9 @@
__pycache__/
.pytest_cache/
.env
node_modules
build
.svelte-kit
package
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

View file

@ -2,10 +2,10 @@ from quart import Quart, g, request
from quart_cors import cors
from quart_schema import QuartSchema, RequestSchemaValidationError
from .blueprints import members_blueprint, users_blueprint
from . import blueprints
from .db.aio import async_session
from .db.util import validate_token
from .exceptions import ExpectedError, ErrorCode
from .exceptions import ErrorCode, ExpectedError
app = Quart(__name__)
app = cors(
@ -17,7 +17,11 @@ app = cors(
)
QuartSchema(app)
for bp in (users_blueprint, members_blueprint):
for bp in (
blueprints.users_blueprint,
blueprints.members_blueprint,
blueprints.meta_blueprint,
):
app.register_blueprint(bp)

View file

@ -1,4 +1,5 @@
from .v2.members import bp as members_blueprint
from .v2.users import bp as users_blueprint
from .v2.meta import bp as meta_blueprint
__all__ = [users_blueprint, members_blueprint]
__all__ = [users_blueprint, members_blueprint, meta_blueprint]

View file

@ -0,0 +1,26 @@
from pydantic import BaseModel
from quart import Blueprint
from quart_schema import validate_response
from sqlalchemy import select
from sqlalchemy.sql import func
from foxnouns.db import User, Member
from foxnouns.db.aio import async_session
from foxnouns.settings import BASE_DOMAIN
bp = Blueprint("meta_v2", __name__)
class MetaResponse(BaseModel):
users: int
members: int
@bp.get("/api/v2/meta", host=BASE_DOMAIN)
@validate_response(MetaResponse)
async def meta():
async with async_session() as session:
user_count = await session.scalar(select(func.count()).select_from(User))
member_count = await session.scalar(select(func.count()).select_from(Member))
return MetaResponse(users=user_count, members=member_count)

View file

@ -1,6 +1,6 @@
import enum
from datetime import datetime
from typing import Any, TYPE_CHECKING
from typing import TYPE_CHECKING, Any
from sqlalchemy import BigInteger, DateTime, ForeignKey, Integer, Text
from sqlalchemy.dialects.postgresql import ARRAY, JSONB

13
frontend/.eslintignore Normal file
View file

@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

31
frontend/.eslintrc.cjs Normal file
View file

@ -0,0 +1,31 @@
/** @type { import("eslint").Linter.Config } */
module.exports = {
root: true,
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:svelte/recommended",
"prettier",
],
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
parserOptions: {
sourceType: "module",
ecmaVersion: 2020,
extraFileExtensions: [".svelte"],
},
env: {
browser: true,
es2017: true,
node: true,
},
overrides: [
{
files: ["*.svelte"],
parser: "svelte-eslint-parser",
parserOptions: {
parser: "@typescript-eslint/parser",
},
},
],
};

1
frontend/.npmrc Normal file
View file

@ -0,0 +1 @@
engine-strict=true

4
frontend/.prettierignore Normal file
View file

@ -0,0 +1,4 @@
pnpm-lock.yaml
package-lock.json
yarn.lock
.svelte-kit

6
frontend/.prettierrc Normal file
View file

@ -0,0 +1,6 @@
{
"useTabs": true,
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

38
frontend/README.md Normal file
View file

@ -0,0 +1,38 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm create svelte@latest
# create a new project in my-app
npm create svelte@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
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.

39
frontend/package.json Normal file
View file

@ -0,0 +1,39 @@
{
"name": "frontend",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check . && eslint .",
"format": "prettier --write ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@types/eslint": "^8.56.0",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.35.1",
"prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2",
"sass": "^1.72.0",
"svelte": "^4.2.7",
"svelte-check": "^3.6.0",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^5.0.3"
},
"type": "module",
"dependencies": {
"@sveltestrap/sveltestrap": "^6.2.7",
"bootstrap": "^5.3.3",
"bootstrap-icons": "^1.11.3"
}
}

13
frontend/src/app.d.ts vendored Normal file
View file

@ -0,0 +1,13 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

12
frontend/src/app.html Normal file
View file

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View file

@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.

View file

@ -0,0 +1,63 @@
export type FetchOptions = {
fetchFn?: typeof fetch;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data?: any;
version?: number;
extraHeaders?: Record<string, string>;
};
/**
* Fetch a path from the API and parse the response.
* To make sure the request is authenticated in load functions,
* pass `fetch` from the request object into opts.
*
* @param method The HTTP method, i.e. GET, POST, PATCH
* @param path The path to request, minus the leading `/api/v2`
* @param opts Extra options for this request
* @returns T
* @throws APIError
*/
export default async function request<T>(
method: string,
path: string,
opts: FetchOptions = {},
): Promise<T> {
const { data, version, extraHeaders } = opts;
const fetchFn = opts.fetchFn ?? fetch;
const resp = await fetchFn(`/api/v${version ?? 2}${path}`, {
method,
body: data ? JSON.stringify(data) : undefined,
headers: { ...extraHeaders, "Content-Type": "application/json" },
});
if (resp.status < 200 || resp.status >= 400) throw await resp.json();
return (await resp.json()) as T;
}
/**
* Fetch a path from the API and discard the response.
* To make sure the request is authenticated in load functions,
* pass `fetch` from the request object into opts.
*
* @param method The HTTP method, i.e. GET, POST, PATCH
* @param path The path to request, minus the leading `/api/v2`
* @param opts Extra options for this request
* @throws APIError
*/
export async function fastRequest(
method: string,
path: string,
opts: FetchOptions = {},
): Promise<void> {
const { data, version, extraHeaders } = opts;
const fetchFn = opts.fetchFn ?? fetch;
const resp = await fetchFn(`/api/v2${version ?? 2}${path}`, {
method,
body: data ? JSON.stringify(data) : undefined,
headers: { ...extraHeaders, "Content-Type": "application/json" },
});
if (resp.status < 200 || resp.status >= 400) throw await resp.json();
}

View file

@ -0,0 +1,12 @@
import request from "$lib/request";
export async function load({ fetch, cookies }) {
const meta = await request("GET", "/meta", { fetchFn: fetch });
let user;
if (cookies.get("pronounscc-token")) {
user = await request("GET", "/users/@me", { fetchFn: fetch });
}
return { meta, user };
}

View file

@ -0,0 +1,11 @@
<script lang="ts">
import "bootstrap/scss/bootstrap.scss";
import "bootstrap-icons/font/bootstrap-icons.scss";
import type { LayoutData } from "./$types";
export let data: LayoutData
</script>
{JSON.stringify(data.meta)}
<slot />

View file

@ -0,0 +1,2 @@
<h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>

BIN
frontend/static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

22
frontend/svelte.config.js Normal file
View file

@ -0,0 +1,22 @@
import adapter from "@sveltejs/adapter-auto";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
import * as child_process from "node:child_process";
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter(),
version: {
name: child_process.execSync("git describe --tags --long --always").toString().trim(),
},
},
};
export default config;

19
frontend/tsconfig.json Normal file
View file

@ -0,0 +1,19 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
// except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

23
frontend/vite.config.ts Normal file
View file

@ -0,0 +1,23 @@
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [sveltekit()],
server: {
host: "127.0.0.1",
proxy: {
"/api": {
target: "http://localhost:5000",
changeOrigin: true,
},
"/media": {
target: "http://localhost:9000",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/media/, "/pronouns.cc"),
},
},
},
ssr: {
noExternal: ["@popperjs/core"],
},
});

1760
frontend/yarn.lock Normal file

File diff suppressed because it is too large Load diff

View file

@ -53,6 +53,7 @@ cmd = "alembic upgrade head"
test = "pytest"
lint = "ruff check"
format = "ruff format"
"check-imports" = "ruff check --select I"
"sort-imports" = "ruff check --select I --fix"
[tool.pytest.ini_options]