Added start of application.

This commit is contained in:
Andrew Lalis 2023-08-16 10:37:39 -04:00
parent 06fcbaabac
commit 57d92d95a4
21 changed files with 2785 additions and 0 deletions

57
design/icon.svg Normal file
View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="48"
height="48"
viewBox="0 0 12.7 12.7"
version="1.1"
id="svg1"
inkscape:version="1.3 (1:1.3+202307231459+0e150ed6c4)"
sodipodi:docname="icon.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050"
inkscape:document-units="mm"
inkscape:zoom="11.80202"
inkscape:cx="25.885399"
inkscape:cy="28.384971"
inkscape:window-width="1920"
inkscape:window-height="1025"
inkscape:window-x="1080"
inkscape:window-y="470"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1">
<rect
x="7.5093697"
y="5.3729877"
width="36.984449"
height="35.672986"
id="rect1" />
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<text
xml:space="preserve"
transform="matrix(0.74693217,0,0,0.74693217,-4.7766061,-3.799875)"
id="text1"
style="font-size:13.3333px;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;white-space:pre;shape-inside:url(#rect1);fill:#ffffff;stroke-width:0.56676275;stroke-linecap:square;fill-opacity:1;stroke:#7bf8ff;stroke-opacity:1;stroke-dasharray:none"><tspan
x="7.5097656"
y="17.806245"
id="tspan2">LL</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

16
litelist-api/.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
.dub
docs.json
__dummy.html
docs/
/litelist-api
litelist-api.so
litelist-api.dylib
litelist-api.dll
litelist-api.a
litelist-api.lib
litelist-api-test-*
*.exe
*.pdb
*.o
*.obj
*.lst

18
litelist-api/dub.json Normal file
View File

@ -0,0 +1,18 @@
{
"authors": [
"Andrew Lalis"
],
"copyright": "Copyright © 2023, Andrew Lalis",
"dependencies": {
"d2sqlite3": "~>1.0.0",
"handy-httpd": "~>7.9.3",
"jwt": "~>0.4.0",
"slf4d": "~>2.4.2"
},
"description": "API for the litelist application.",
"license": "MIT",
"name": "litelist-api",
"subConfigurations": {
"d2sqlite3": "all-included"
}
}

View File

@ -0,0 +1,11 @@
{
"fileVersion": 1,
"versions": {
"d2sqlite3": "1.0.0",
"handy-httpd": "7.9.3",
"httparsed": "1.2.1",
"jwt": "0.4.0",
"slf4d": "2.4.2",
"streams": "3.5.0"
}
}

40
litelist-api/source/app.d Normal file
View File

@ -0,0 +1,40 @@
import handy_httpd;
import slf4d;
import slf4d.default_provider;
void main() {
auto provider = new shared DefaultProvider(true, Levels.INFO);
configureLoggingProvider(provider);
HttpServer server = initServer();
server.start();
}
private HttpServer initServer() {
import handy_httpd.handlers.path_delegating_handler;
import handy_httpd.handlers.filtered_handler;
import auth;
ServerConfig config = ServerConfig.defaultValues();
config.enableWebSockets = false;
config.workerPoolSize = 3;
config.port = 8080;
config.connectionQueueSize = 10;
config.defaultHeaders["Access-Control-Allow-Origin"] = "*";
auto mainHandler = new PathDelegatingHandler();
mainHandler.addMapping(Method.GET, "/status", (ref HttpRequestContext ctx) {
ctx.response.writeBodyString("online");
});
mainHandler.addMapping(Method.POST, "/login", &handleLogin);
// Authenticated endpoints are protected by the TokenFilter.
auto authEndpoints = new PathDelegatingHandler();
auto authHandler = new FilteredRequestHandler(
authEndpoints,
[new TokenFilter]
);
return new HttpServer(mainHandler, config);
}

View File

@ -0,0 +1,72 @@
module auth;
import handy_httpd;
import handy_httpd.handlers.filtered_handler;
import std.json;
void handleLogin(ref HttpRequestContext ctx) {
JSONValue resp = JSONValue(string[string].init);
resp.object["token"] = "authtoken";
ctx.response.writeBodyString(resp.toString(), "application/json");
}
struct User {
string username;
string email;
string passwordHash;
}
struct AuthContext {
string token;
User user;
}
class AuthContextHolder {
private static AuthContextHolder instance;
static getInstance() {
if (!instance) instance = new AuthContextHolder();
return instance;
}
static reset() {
auto i = getInstance();
i.authenticated = false;
i.context = AuthContext.init;
}
static setContext(string token, User user) {
auto i = getInstance();
i.authenticated = true;
i.context = AuthContext(token, user);
}
private bool authenticated;
private AuthContext context;
}
class TokenFilter : HttpRequestFilter {
immutable HEADER_NAME = "Authorization";
void apply(ref HttpRequestContext ctx, FilterChain filterChain) {
AuthContextHolder.reset();
if (!ctx.request.hasHeader(HEADER_NAME)) {
ctx.response.setStatus(HttpStatus.UNAUTHORIZED);
ctx.response.writeBodyString("Missing Authorization header.");
return;
}
string authHeader = ctx.request.getHeader(HEADER_NAME);
if (authHeader.length < 7 || authHeader[0 .. 7] != "Bearer ") {
ctx.response.setStatus(HttpStatus.UNAUTHORIZED);
ctx.response.writeBodyString("Invalid bearer token authorization header.");
return;
}
string rawToken = authHeader[7 .. $];
// TODO: Validate token and fetch user.
User user = User("bleh", "bleh@example.com", "faef9834rfe");
AuthContextHolder.setContext(rawToken, user);
filterChain.doFilter(ctx);
}
}

28
litelist-app/.gitignore vendored Normal file
View File

@ -0,0 +1,28 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

40
litelist-app/README.md Normal file
View File

@ -0,0 +1,40 @@
# litelist-app
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## Type Support for `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
1. Disable the built-in TypeScript Extension
1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette
2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Type-Check, Compile and Minify for Production
```sh
npm run build
```

1
litelist-app/env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

13
litelist-app/index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

2321
litelist-app/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

27
litelist-app/package.json Normal file
View File

@ -0,0 +1,27 @@
{
"name": "litelist-app",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "run-p type-check build-only",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false"
},
"dependencies": {
"pinia": "^2.1.4",
"vue": "^3.3.4",
"vue-router": "^4.2.4"
},
"devDependencies": {
"@tsconfig/node18": "^18.2.0",
"@types/node": "^18.17.0",
"@vitejs/plugin-vue": "^4.2.3",
"@vue/tsconfig": "^0.4.0",
"npm-run-all": "^4.1.5",
"typescript": "~5.1.6",
"vite": "^4.4.6",
"vue-tsc": "^1.8.6"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

11
litelist-app/src/App.vue Normal file
View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router'
</script>
<template>
<RouterView />
</template>
<style scoped>
</style>

12
litelist-app/src/main.ts Normal file
View File

@ -0,0 +1,12 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')

View File

@ -0,0 +1,15 @@
import { createRouter, createWebHistory } from 'vue-router'
import LoginView from "@/views/LoginView.vue";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/login',
name: 'login',
component: LoginView
}
]
})
export default router

View File

@ -0,0 +1,48 @@
<script setup lang="ts">
import {ref} from "vue";
const loginModel = ref({
username: "",
password: ""
})
function resetLogin() {
loginModel.value.username = ""
loginModel.value.password = ""
}
async function doLogin() {
try {
const response = await fetch(
"http://localhost:8080/login",
{
method: "POST",
mode: "no-cors",
body: JSON.stringify(loginModel.value)
}
)
console.log(response.json())
} catch (error: AxiosError) {
console.error(error)
}
}
</script>
<template>
<form @submit.prevent="doLogin" @reset="resetLogin">
<h1>LiteList</h1>
<label>
Username:
<input type="text" name="username" required v-model="loginModel.username"/>
</label>
<label>
Password:
<input type="password" name="password" required v-model="loginModel.password"/>
</label>
<button type="submit">Submit</button>
</form>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,12 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}

View File

@ -0,0 +1,11 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
}

View File

@ -0,0 +1,16 @@
{
"extends": "@tsconfig/node18/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*"
],
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}

View File

@ -0,0 +1,16 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})