add a bunch of frontend stuff

This commit is contained in:
sam 2023-09-03 04:11:56 +02:00
parent 2586161abd
commit bc85b7c340
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
30 changed files with 1459 additions and 136 deletions

View file

@ -0,0 +1,5 @@
{
"useTabs": true,
"trailingComma": "all",
"singleQuote": false
}

View file

@ -0,0 +1,19 @@
:root {
--dark-background: #2B303A;
--light-background: #F5F5F5;
--dark-text: #FFFFFF;
--light-text: #000000;
--dark-primary: #087E8B;
--light-primary: #67AEB5;
--dark-secondary: #7C7F85;
--light-secondary: #A4A6AA;
--dark-danger: #D64933;
--light-danger: #FF5A5F;
--dark-success: #4DA167;
--light-success: #77B68B;
}

26
assets/scss/style.scss Normal file
View file

@ -0,0 +1,26 @@
@use "node_modules/normalize.css/normalize";
@use "_variables";
body {
font-family: sans-serif;
background-color: var(--light-background);
color: var(--light-text);
&:dark {
background-color: var(--dark-background);
color: var(--dark-text);
}
}
.auth {
width: 100%;
@media (min-width: 640px) {
max-width: 520px;
}
margin: 1rem auto;
p {
display: flex;
flex-direction: column;
}
}

View file

@ -30,7 +30,10 @@ func run(c *cli.Context) error {
return errors.Wrap(err, "creating postgres database")
}
a := app.NewApp(cfg, db)
a, err := app.NewApp(cfg, db)
if err != nil {
return errors.Wrap(err, "creating app")
}
log.Debug().Msg("Creating account")
acct, err := a.Account().CreateLocal(c.Context, "testington", "no@mercury.example", []byte("password"))

View file

@ -39,7 +39,10 @@ func run(c *cli.Context) error {
return errors.Wrap(err, "creating postgres database")
}
a := app.NewApp(cfg, db)
a, err := app.NewApp(cfg, db)
if err != nil {
return errors.Wrap(err, "creating app")
}
log.Debug().Msg("Mounting routes")
web.Routes(a)

View file

@ -1,13 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Svelte + TS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Svelte + TS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View file

@ -1,22 +1,26 @@
{
"name": "frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.4.2",
"@tsconfig/svelte": "^5.0.0",
"prettier": "^3.0.3",
"svelte": "^4.0.5",
"svelte-check": "^3.4.6",
"tslib": "^2.6.0",
"typescript": "^5.0.2",
"vite": "^4.4.5"
}
"name": "frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.4.2",
"@tsconfig/svelte": "^5.0.0",
"prettier": "^3.0.3",
"sass": "^1.66.1",
"svelte": "^4.0.5",
"svelte-check": "^3.4.6",
"tslib": "^2.6.0",
"typescript": "^5.0.2",
"vite": "^4.4.5"
},
"dependencies": {
"normalize.css": "^8.0.1"
}
}

View file

@ -1,5 +1,10 @@
lockfileVersion: "6.0"
dependencies:
normalize.css:
specifier: ^8.0.1
version: 8.0.1
devDependencies:
"@sveltejs/vite-plugin-svelte":
specifier: ^2.4.2
@ -10,12 +15,15 @@ devDependencies:
prettier:
specifier: ^3.0.3
version: 3.0.3
sass:
specifier: ^1.66.1
version: 1.66.1
svelte:
specifier: ^4.0.5
version: 4.0.5
svelte-check:
specifier: ^3.4.6
version: 3.4.6(svelte@4.0.5)
version: 3.4.6(sass@1.66.1)(svelte@4.0.5)
tslib:
specifier: ^2.6.0
version: 2.6.0
@ -24,7 +32,7 @@ devDependencies:
version: 5.0.2
vite:
specifier: ^4.4.5
version: 4.4.5
version: 4.4.5(sass@1.66.1)
packages:
/@ampproject/remapping@2.2.1:
@ -391,7 +399,7 @@ packages:
"@sveltejs/vite-plugin-svelte": 2.4.2(svelte@4.0.5)(vite@4.4.5)
debug: 4.3.4
svelte: 4.0.5
vite: 4.4.5
vite: 4.4.5(sass@1.66.1)
transitivePeerDependencies:
- supports-color
dev: true
@ -413,7 +421,7 @@ packages:
magic-string: 0.30.3
svelte: 4.0.5
svelte-hmr: 0.15.3(svelte@4.0.5)
vite: 4.4.5
vite: 4.4.5(sass@1.66.1)
vitefu: 0.2.4(vite@4.4.5)
transitivePeerDependencies:
- supports-color
@ -747,6 +755,13 @@ packages:
}
dev: true
/immutable@4.3.4:
resolution:
{
integrity: sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==,
}
dev: true
/import-fresh@3.3.0:
resolution:
{
@ -947,6 +962,13 @@ packages:
engines: { node: ">=0.10.0" }
dev: true
/normalize.css@8.0.1:
resolution:
{
integrity: sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==,
}
dev: false
/once@1.4.0:
resolution:
{
@ -1106,6 +1128,19 @@ packages:
rimraf: 2.7.1
dev: true
/sass@1.66.1:
resolution:
{
integrity: sha512-50c+zTsZOJVgFfTgwwEzkjA3/QACgdNsKueWPyAR0mRINIvLAStVQBbPg14iuqEQ74NPDbXzJARJ/O4SI1zftA==,
}
engines: { node: ">=14.0.0" }
hasBin: true
dependencies:
chokidar: 3.5.3
immutable: 4.3.4
source-map-js: 1.0.2
dev: true
/sorcery@0.11.0:
resolution:
{
@ -1137,7 +1172,7 @@ packages:
min-indent: 1.0.1
dev: true
/svelte-check@3.4.6(svelte@4.0.5):
/svelte-check@3.4.6(sass@1.66.1)(svelte@4.0.5):
resolution:
{
integrity: sha512-OBlY8866Zh1zHQTkBMPS6psPi7o2umTUyj6JWm4SacnIHXpWFm658pG32m3dKvKFL49V4ntAkfFHKo4ztH07og==,
@ -1153,7 +1188,7 @@ packages:
picocolors: 1.0.0
sade: 1.8.1
svelte: 4.0.5
svelte-preprocess: 5.0.4(svelte@4.0.5)(typescript@5.2.2)
svelte-preprocess: 5.0.4(sass@1.66.1)(svelte@4.0.5)(typescript@5.2.2)
typescript: 5.2.2
transitivePeerDependencies:
- "@babel/core"
@ -1179,7 +1214,7 @@ packages:
svelte: 4.0.5
dev: true
/svelte-preprocess@5.0.4(svelte@4.0.5)(typescript@5.2.2):
/svelte-preprocess@5.0.4(sass@1.66.1)(svelte@4.0.5)(typescript@5.2.2):
resolution:
{
integrity: sha512-ABia2QegosxOGsVlsSBJvoWeXy1wUKSfF7SWJdTjLAbx/Y3SrVevvvbFNQqrSJw89+lNSsM58SipmZJ5SRi5iw==,
@ -1223,6 +1258,7 @@ packages:
"@types/pug": 2.0.6
detect-indent: 6.1.0
magic-string: 0.27.0
sass: 1.66.1
sorcery: 0.11.0
strip-indent: 3.0.0
svelte: 4.0.5
@ -1286,7 +1322,7 @@ packages:
hasBin: true
dev: true
/vite@4.4.5:
/vite@4.4.5(sass@1.66.1):
resolution:
{
integrity: sha512-4m5kEtAWHYr0O1Fu7rZp64CfO1PsRGZlD3TAB32UmQlpd7qg15VF7ROqGN5CyqN7HFuwr7ICNM2+fDWRqFEKaA==,
@ -1320,6 +1356,7 @@ packages:
esbuild: 0.18.20
postcss: 8.4.29
rollup: 3.28.1
sass: 1.66.1
optionalDependencies:
fsevents: 2.3.3
dev: true
@ -1335,7 +1372,7 @@ packages:
vite:
optional: true
dependencies:
vite: 4.4.5
vite: 4.4.5(sass@1.66.1)
dev: true
/wrappy@1.0.2:

View file

@ -1,80 +1,80 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
font-size: 3.2em;
line-height: 1.1;
}
.card {
padding: 2em;
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

View file

@ -2,7 +2,7 @@ import "./app.css";
import App from "./App.svelte";
const app = new App({
target: document.getElementById("app"),
target: document.getElementById("app"),
});
export default app;

View file

@ -1,7 +1,7 @@
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
export default {
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
// for more information about preprocessors
preprocess: vitePreprocess(),
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
// for more information about preprocessors
preprocess: vitePreprocess(),
};

View file

@ -1,20 +1,20 @@
{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"resolveJsonModule": true,
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable checkJs if you'd like to use dynamic types in JS.
* Note that setting allowJs false does not prevent the use
* of JS in `.svelte` files.
*/
"allowJs": true,
"checkJs": true,
"isolatedModules": true
},
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
"references": [{ "path": "./tsconfig.node.json" }]
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"resolveJsonModule": true,
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable checkJs if you'd like to use dynamic types in JS.
* Note that setting allowJs false does not prevent the use
* of JS in `.svelte` files.
*/
"allowJs": true,
"checkJs": true,
"isolatedModules": true
},
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View file

@ -1,9 +1,9 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler"
},
"include": ["vite.config.ts"]
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler"
},
"include": ["vite.config.ts"]
}

View file

@ -3,8 +3,8 @@ import { svelte } from "@sveltejs/vite-plugin-svelte";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [svelte()],
build: {
manifest: "manifest.json",
},
plugins: [svelte()],
build: {
manifest: "manifest.json",
},
});

1
go.mod
View file

@ -5,6 +5,7 @@ go 1.19
require (
emperror.dev/errors v0.8.1
github.com/BurntSushi/toml v1.2.1
github.com/flosch/pongo2/v6 v6.0.0
github.com/georgysavva/scany/v2 v2.0.0
github.com/go-chi/chi/v5 v5.0.8
github.com/go-chi/render v1.0.2

2
go.sum
View file

@ -102,6 +102,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/flosch/pongo2/v6 v6.0.0 h1:lsGru8IAzHgIAw6H2m4PCyleO58I40ow6apih0WprMU=
github.com/flosch/pongo2/v6 v6.0.0/go.mod h1:CuDpFm47R0uGGE7z13/tTlt1Y6zdxvr2RLT5LJhsHEU=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=

16
package.json Normal file
View file

@ -0,0 +1,16 @@
{
"name": "mercury",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"watch:style": "sass -I . -w assets/scss/:web/frontend/assets/css/ --style compressed"
},
"keywords": [],
"author": "sam <sam@sleepycat.moe>",
"license": "APGL-3.0-only",
"devDependencies": {
"normalize.css": "^8.0.1",
"sass": "^1.66.1"
}
}

1028
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load diff

3
pnpm-workspace.yaml Normal file
View file

@ -0,0 +1,3 @@
packages:
- .
- frontend

View file

@ -1,8 +1,11 @@
package app
import (
"emperror.dev/errors"
"git.sleepycat.moe/sam/mercury/config"
"git.sleepycat.moe/sam/mercury/internal/database/sql"
"git.sleepycat.moe/sam/mercury/web/templates"
"github.com/flosch/pongo2/v6"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
@ -12,19 +15,27 @@ type App struct {
Config config.Config
Database *sql.Base
tmpl *pongo2.TemplateSet
}
func NewApp(cfg config.Config, db *sql.Base) *App {
func NewApp(cfg config.Config, db *sql.Base) (*App, error) {
app := &App{
Router: chi.NewRouter(),
Config: cfg,
Database: db,
}
tmpl, err := templates.New(cfg.Core.Dev)
if err != nil {
return nil, errors.Wrap(err, "creating templates")
}
app.tmpl = tmpl
app.Router.Use(app.Logger)
app.Router.Use(middleware.Recoverer)
return app
return app, nil
}
func (a *App) Account(q ...sql.Querier) *sql.AccountStore {

52
web/app/template.go Normal file
View file

@ -0,0 +1,52 @@
package app
import (
"net/http"
"time"
"github.com/flosch/pongo2/v6"
)
func (app *App) Template(w http.ResponseWriter, r *http.Request, tmplName string, ctx pongo2.Context) error {
tmpl, err := app.tmpl.FromCache(tmplName)
if err != nil {
return err
}
tctx := pongo2.Context{
"flash_message": app.getFlash(w, r),
}
tctx.Update(ctx)
w.Header().Set("Content-Type", "text/html")
return tmpl.ExecuteWriter(tctx, w)
}
const flashCookieName = "mercury-flash-message"
func (app *App) Flash(w http.ResponseWriter, msg string) {
http.SetCookie(w, &http.Cookie{
Name: flashCookieName,
Value: msg,
Path: "/",
HttpOnly: true,
Expires: time.Now().Add(time.Minute),
})
}
func (app *App) getFlash(w http.ResponseWriter, r *http.Request) string {
cookie, err := r.Cookie(flashCookieName)
if err != nil {
return ""
}
defer http.SetCookie(w, &http.Cookie{
Name: flashCookieName,
Value: "",
Path: "/",
HttpOnly: true,
Expires: time.Now(),
})
return cookie.Value
}

15
web/auth/auth.go Normal file
View file

@ -0,0 +1,15 @@
package auth
import "git.sleepycat.moe/sam/mercury/web/app"
type Auth struct {
*app.App
}
func New(app *app.App) *Auth {
auth := &Auth{
App: app,
}
return auth
}

11
web/auth/login.go Normal file
View file

@ -0,0 +1,11 @@
package auth
import (
"net/http"
"github.com/flosch/pongo2/v6"
)
func (app *Auth) GetLogin(w http.ResponseWriter, r *http.Request) {
app.Template(w, r, "auth/login.tpl", pongo2.Context{})
}

1
web/frontend/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
assets

View file

@ -1,13 +1,13 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Config.Name}}</title>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{.Config.Name}}</title>
{{.Vue.RenderTags}}
</head>
<body>
<div id="app"></div>
</body>
{{.Vue.RenderTags}}
</head>
<body>
<div id="app"></div>
</body>
</html>

View file

@ -1,22 +1,26 @@
package frontend
import (
"embed"
"html/template"
"net/http"
"os"
"path/filepath"
"git.sleepycat.moe/sam/mercury/frontend"
"git.sleepycat.moe/sam/mercury/internal/database"
"git.sleepycat.moe/sam/mercury/web/app"
"github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log"
vueglue "github.com/torenware/vite-go"
_ "embed"
)
//go:embed app.html
var htmlTemplate string
//go:embed assets
var assets embed.FS
type Frontend struct {
*app.App
glue *vueglue.VueGlue
@ -94,3 +98,14 @@ func (app *Frontend) ServeFrontend(w http.ResponseWriter, r *http.Request) {
log.Err(err).Msg("executing frontend template")
}
}
func (app *Frontend) ServeStaticAssets(w http.ResponseWriter, r *http.Request) {
if app.Config.Core.Dev {
// TODO: this is unsafe
path := filepath.Join("web/frontend/assets/", chi.URLParam(r, "*"))
http.ServeFile(w, r, path)
return
}
_ = assets
}

View file

@ -2,12 +2,23 @@ package web
import (
"git.sleepycat.moe/sam/mercury/web/app"
"git.sleepycat.moe/sam/mercury/web/auth"
"git.sleepycat.moe/sam/mercury/web/frontend"
"github.com/go-chi/chi/v5"
)
func Routes(app *app.App) {
// auth
app.Router.Route("/auth", func(r chi.Router) {
auth := auth.New(app)
r.Get("/login", auth.GetLogin)
})
// web app handlers
// also assets
frontend := frontend.New(app)
app.Router.HandleFunc(frontend.AssetsPath(), frontend.ServeAssets)
app.Router.HandleFunc("/static/*", frontend.ServeStaticAssets)
app.Router.HandleFunc("/web", frontend.ServeFrontend)
app.Router.HandleFunc("/web/*", frontend.ServeFrontend)
}

View file

@ -0,0 +1,19 @@
{% extends 'base.tpl' %}
{% block title %}
Log in
{% endblock %}
{% block content %}
<div class="auth">
<form method="post">
<p>
<label for="username">Username</label>
<input type="text" name="username" />
</p>
<p>
<label for="username">Password</label>
<input type="password" name="password" />
</p>
<input type="submit" value="Log in" />
</form>
</div>
{% endblock %}

13
web/templates/base.tpl Normal file
View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="/static/css/style.css" />
<title>{% block title %}Mercury{% endblock %}</title>
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>

View file

@ -0,0 +1,28 @@
package templates
import (
"embed"
"emperror.dev/errors"
"github.com/flosch/pongo2/v6"
)
//go:embed *
var fs embed.FS
func New(dev bool) (*pongo2.TemplateSet, error) {
if dev {
loader, err := pongo2.NewLocalFileSystemLoader("web/templates")
if err != nil {
return nil, errors.Wrap(err, "creating filesystem loader")
}
ts := pongo2.NewSet("web", loader)
ts.Debug = true
return ts, nil
}
loader := pongo2.NewFSLoader(fs)
ts := pongo2.NewSet("web", loader)
return ts, nil
}