chore: started working on the api
This commit is contained in:
parent
0fe7c51bab
commit
1c0d6a309b
1
go.mod
1
go.mod
@ -15,6 +15,7 @@ require (
|
|||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/charmbracelet/lipgloss v0.9.1 // indirect
|
github.com/charmbracelet/lipgloss v0.9.1 // indirect
|
||||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
|
2
go.sum
2
go.sum
@ -16,6 +16,8 @@ github.com/galeone/tfgo v0.0.0-20230715013254-16113111dc99 h1:8Bt1P/zy1gb37L4n8C
|
|||||||
github.com/galeone/tfgo v0.0.0-20230715013254-16113111dc99/go.mod h1:3YgYBeIX42t83uP27Bd4bSMxTnQhSbxl0pYSkCDB1tc=
|
github.com/galeone/tfgo v0.0.0-20230715013254-16113111dc99/go.mod h1:3YgYBeIX42t83uP27Bd4bSMxTnQhSbxl0pYSkCDB1tc=
|
||||||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||||
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
|
|
||||||
dbtypes "git.andr3h3nriqu3s.com/andr3/fyp/logic/db_types"
|
dbtypes "git.andr3h3nriqu3s.com/andr3/fyp/logic/db_types"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
|
"github.com/goccy/go-json"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Mul(n1 int, n2 int) int {
|
func Mul(n1 int, n2 int) int {
|
||||||
@ -347,6 +348,43 @@ type Context struct {
|
|||||||
Db *sql.DB
|
Db *sql.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c Context) ToJSON(r *http.Request, dat any) *Error {
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
|
||||||
|
err := decoder.Decode(dat)
|
||||||
|
if err != nil {
|
||||||
|
return c.Error500(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Context) SendJSON(w http.ResponseWriter, dat any) *Error {
|
||||||
|
w.Header().Add("content-type", "application/json")
|
||||||
|
text, err := json.Marshal(dat)
|
||||||
|
if err != nil {
|
||||||
|
return c.Error500(err)
|
||||||
|
}
|
||||||
|
if _, err = w.Write(text); err != nil {
|
||||||
|
return c.Error500(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Context) SendJSONStatus(w http.ResponseWriter, status int, dat any) *Error {
|
||||||
|
w.Header().Add("content-type", "application/json")
|
||||||
|
w.WriteHeader(status)
|
||||||
|
text, err := json.Marshal(dat)
|
||||||
|
if err != nil {
|
||||||
|
return c.Error500(err)
|
||||||
|
}
|
||||||
|
if _, err = w.Write(text); err != nil {
|
||||||
|
return c.Error500(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c Context) Error400(err error, message string, w http.ResponseWriter, path string, base string, data AnyMap) *Error {
|
func (c Context) Error400(err error, message string, w http.ResponseWriter, path string, base string, data AnyMap) *Error {
|
||||||
c.SetReportCaller(true)
|
c.SetReportCaller(true)
|
||||||
c.Logger.Error(message)
|
c.Logger.Error(message)
|
||||||
@ -609,6 +647,10 @@ func NewHandler(db *sql.DB) *Handle {
|
|||||||
if r.Header.Get("Request-Type") == "htmlfull" {
|
if r.Header.Get("Request-Type") == "htmlfull" {
|
||||||
ans = HTMLFULL
|
ans = HTMLFULL
|
||||||
}
|
}
|
||||||
|
if r.Header.Get("content-type") == "application/json" {
|
||||||
|
ans = JSON
|
||||||
|
}
|
||||||
|
|
||||||
//TODO JSON
|
//TODO JSON
|
||||||
|
|
||||||
//Login state
|
//Login state
|
||||||
@ -618,6 +660,9 @@ func NewHandler(db *sql.DB) *Handle {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.Header().Add("Access-Control-Allow-Origin", "*")
|
||||||
|
w.Header().Add("Access-Control-Allow-Headers", "*")
|
||||||
|
|
||||||
if r.Method == "GET" {
|
if r.Method == "GET" {
|
||||||
x.handleGets(w, r, context)
|
x.handleGets(w, r, context)
|
||||||
return
|
return
|
||||||
@ -630,6 +675,9 @@ func NewHandler(db *sql.DB) *Handle {
|
|||||||
x.handleDeletes(w, r, context)
|
x.handleDeletes(w, r, context)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if r.Method == "OPTIONS" {
|
||||||
|
return
|
||||||
|
}
|
||||||
panic("TODO handle method: " + r.Method)
|
panic("TODO handle method: " + r.Method)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
36
users.go
36
users.go
@ -4,7 +4,6 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
@ -79,8 +78,39 @@ func usersEndpints(db *sql.DB, handle *Handle) {
|
|||||||
handle.GetHTML("/login", AnswerTemplate("login.html", nil, 0))
|
handle.GetHTML("/login", AnswerTemplate("login.html", nil, 0))
|
||||||
handle.Post("/login", func(w http.ResponseWriter, r *http.Request, c *Context) *Error {
|
handle.Post("/login", func(w http.ResponseWriter, r *http.Request, c *Context) *Error {
|
||||||
if c.Mode == JSON {
|
if c.Mode == JSON {
|
||||||
fmt.Println("Handle JSON")
|
|
||||||
return &Error{Code: 404}
|
type UserLogin struct {
|
||||||
|
Email string `json:email`
|
||||||
|
Password string `json:password`
|
||||||
|
}
|
||||||
|
|
||||||
|
var dat UserLogin
|
||||||
|
|
||||||
|
if err := c.ToJSON(r, &dat); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*if (dat["email"] == nil || dat["password"] == nil) {
|
||||||
|
// TODO improve this
|
||||||
|
c.Logger.Warn("Email or password are empty")
|
||||||
|
return c.Error500(nil)
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// TODO Give this to the generateToken function
|
||||||
|
expiration := time.Now().Add(24 * time.Hour)
|
||||||
|
token, login := generateToken(db, dat.Email, dat.Password)
|
||||||
|
if !login {
|
||||||
|
return c.SendJSONStatus(w, http.StatusUnauthorized, "Email or password are incorrect")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cookie := &http.Cookie{Name: "auth", Value: token, HttpOnly: false, Expires: expiration}
|
||||||
|
http.SetCookie(w, cookie)
|
||||||
|
|
||||||
|
w.Header().Set("Location", "/")
|
||||||
|
w.WriteHeader(http.StatusSeeOther)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
|
13
webpage/.eslintignore
Normal file
13
webpage/.eslintignore
Normal 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
webpage/.eslintrc.cjs
Normal file
31
webpage/.eslintrc.cjs
Normal 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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
10
webpage/.gitignore
vendored
Normal file
10
webpage/.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/build
|
||||||
|
/.svelte-kit
|
||||||
|
/package
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
vite.config.ts.timestamp-*
|
1
webpage/.npmrc
Normal file
1
webpage/.npmrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
engine-strict=true
|
4
webpage/.prettierignore
Normal file
4
webpage/.prettierignore
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Ignore files for PNPM, NPM and YARN
|
||||||
|
pnpm-lock.yaml
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
8
webpage/.prettierrc
Normal file
8
webpage/.prettierrc
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"printWidth": 100,
|
||||||
|
"plugins": ["prettier-plugin-svelte"],
|
||||||
|
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||||
|
}
|
38
webpage/README.md
Normal file
38
webpage/README.md
Normal 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.
|
BIN
webpage/bun.lockb
Executable file
BIN
webpage/bun.lockb
Executable file
Binary file not shown.
34
webpage/package.json
Normal file
34
webpage/package.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"name": "webpage",
|
||||||
|
"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.36.0-next.4",
|
||||||
|
"prettier": "^3.1.1",
|
||||||
|
"prettier-plugin-svelte": "^3.1.2",
|
||||||
|
"sass": "^1.71.1",
|
||||||
|
"svelte": "^5.0.0-next.1",
|
||||||
|
"svelte-check": "^3.6.0",
|
||||||
|
"tslib": "^2.4.1",
|
||||||
|
"typescript": "^5.0.0",
|
||||||
|
"vite": "^5.0.3"
|
||||||
|
},
|
||||||
|
"type": "module"
|
||||||
|
}
|
60
webpage/src/NavBar.svelte
Normal file
60
webpage/src/NavBar.svelte
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { userStore } from './routes/UserStore.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="/"> Index </a>
|
||||||
|
</li>
|
||||||
|
{#if userStore.user}
|
||||||
|
<li>
|
||||||
|
<a href="/models"> Models </a>
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
|
<li class="expand"></li>
|
||||||
|
{#if userStore.user}
|
||||||
|
<li>
|
||||||
|
<a href="/user/info"> User Info </a>
|
||||||
|
<a href="/logout"> Logout </a>
|
||||||
|
</li>
|
||||||
|
{:else}
|
||||||
|
<li>
|
||||||
|
<a href="/login"> Login </a>
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<style class="scss">
|
||||||
|
nav {
|
||||||
|
background: #ececec;
|
||||||
|
margin: 0;
|
||||||
|
box-shadow: 0 0 8px 1px #888888ef;
|
||||||
|
height: 60px;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
display: flex;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px 40px;
|
||||||
|
|
||||||
|
li {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 10px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.expand {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
13
webpage/src/app.d.ts
vendored
Normal file
13
webpage/src/app.d.ts
vendored
Normal 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
webpage/src/app.html
Normal file
12
webpage/src/app.html
Normal 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>
|
1
webpage/src/lib/index.ts
Normal file
1
webpage/src/lib/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
// place files you want to import through the `$lib` alias in this folder.
|
42
webpage/src/lib/requests.svelte.ts
Normal file
42
webpage/src/lib/requests.svelte.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { userStore } from 'routes/UserStore.svelte';
|
||||||
|
|
||||||
|
const API = "http://localhost:8000";
|
||||||
|
|
||||||
|
export async function get(url: string) {
|
||||||
|
const headers = new Headers();
|
||||||
|
//headers.append('content-type', 'application/json');
|
||||||
|
if (userStore.user) {
|
||||||
|
headers.append('token', userStore.user.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = await fetch(`${API}/${url}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (r.status !== 200) {
|
||||||
|
throw r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function post(url: string, body: any) {
|
||||||
|
const headers = new Headers();
|
||||||
|
headers.append('content-type', 'application/json');
|
||||||
|
if (userStore.user) {
|
||||||
|
headers.append('token', userStore.user.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = await fetch(`${API}/${url}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: headers,
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (r.status !== 200) {
|
||||||
|
throw r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.json();
|
||||||
|
}
|
17
webpage/src/routes/+error.svelte
Normal file
17
webpage/src/routes/+error.svelte
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import Page404 from './Page404.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $page.error}
|
||||||
|
{#if $page.status == 404}
|
||||||
|
<Page404 />
|
||||||
|
{:else}
|
||||||
|
<h1>
|
||||||
|
{$page.status}
|
||||||
|
</h1>
|
||||||
|
<h2>
|
||||||
|
{$page.error.message}
|
||||||
|
</h2>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
9
webpage/src/routes/+layout.svelte
Normal file
9
webpage/src/routes/+layout.svelte
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import '../styles/fonts.css';
|
||||||
|
import '../styles/app.css';
|
||||||
|
import NavBar from "../NavBar.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<NavBar />
|
||||||
|
|
||||||
|
<slot />
|
4
webpage/src/routes/+layout.ts
Normal file
4
webpage/src/routes/+layout.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export const prerender = true;
|
||||||
|
export const ssr = false;
|
||||||
|
export const csr = true;
|
||||||
|
//export const trailingSlash = '';
|
1
webpage/src/routes/+page.svelte
Normal file
1
webpage/src/routes/+page.svelte
Normal file
@ -0,0 +1 @@
|
|||||||
|
<h1>Main Web Page</h1>
|
54
webpage/src/routes/Page404.svelte
Normal file
54
webpage/src/routes/Page404.svelte
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let { message, goBackLink } = $props<{message?: string, goBackLink?: string}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="page404">
|
||||||
|
<div>
|
||||||
|
<h1>
|
||||||
|
404
|
||||||
|
</h1>
|
||||||
|
{#if message}
|
||||||
|
<h2>
|
||||||
|
{message}
|
||||||
|
</h2>
|
||||||
|
{#if goBackLink}
|
||||||
|
<div class="description">
|
||||||
|
<a href={goBackLink}>
|
||||||
|
👈 Go back
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<h2>
|
||||||
|
Page Not found
|
||||||
|
</h2>
|
||||||
|
<div class="description">
|
||||||
|
The page you were looking for does not exist
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.page404 {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
height: calc(100vh - 60px);
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 10em;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 5em;
|
||||||
|
margin: 0;
|
||||||
|
margin-bottom: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.description {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
55
webpage/src/routes/UserStore.svelte.ts
Normal file
55
webpage/src/routes/UserStore.svelte.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { goto } from "$app/navigation";
|
||||||
|
|
||||||
|
type User = {
|
||||||
|
token: string,
|
||||||
|
id: string,
|
||||||
|
user_type: number,
|
||||||
|
username: string,
|
||||||
|
email: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createUserStore() {
|
||||||
|
let user = $state<User | undefined>(undefined);
|
||||||
|
|
||||||
|
function getValue() {
|
||||||
|
if (user == undefined) {
|
||||||
|
let storage = localStorage.getItem('user');
|
||||||
|
if (storage) {
|
||||||
|
try {
|
||||||
|
user = JSON.parse(storage);
|
||||||
|
} catch {
|
||||||
|
user = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
get user() {
|
||||||
|
return getValue();
|
||||||
|
},
|
||||||
|
set user(value: User | undefined) {
|
||||||
|
if (value) {
|
||||||
|
localStorage.setItem('user', JSON.stringify(value));
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem('user');
|
||||||
|
}
|
||||||
|
user = value;
|
||||||
|
},
|
||||||
|
checkUser(pathOnFail: string, level?: number) {
|
||||||
|
if (user && user.level > (level ?? 2) ) {
|
||||||
|
goto(pathOnFail);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
isLogin() {
|
||||||
|
if (getValue()) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const userStore = createUserStore();
|
83
webpage/src/routes/login/+page.svelte
Normal file
83
webpage/src/routes/login/+page.svelte
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { post } from 'src/lib/requests.svelte';
|
||||||
|
import 'src/styles/forms.css';
|
||||||
|
import { userStore } from '../UserStore.svelte';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
|
let submitted = $state(false);
|
||||||
|
|
||||||
|
let loginData = $state({
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
let errorMessage = $state("");
|
||||||
|
|
||||||
|
async function onSubmit() {
|
||||||
|
submitted = true;
|
||||||
|
errorMessage = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const req = await post('login', loginData);
|
||||||
|
userStore.user = req;
|
||||||
|
goto("/");
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Response) {
|
||||||
|
errorMessage = await e.json();
|
||||||
|
} else {
|
||||||
|
errorMessage = "Server could not perform login";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title> Login </title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<div class="login-page">
|
||||||
|
<div>
|
||||||
|
<h1>
|
||||||
|
Login
|
||||||
|
</h1>
|
||||||
|
<form on:submit|preventDefault={onSubmit} class:submitted >
|
||||||
|
<fieldset>
|
||||||
|
<label for="email">Email</label>
|
||||||
|
<input type="email" required name="email" bind:value={loginData.email} />
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input required name="password" type="password" bind:value={loginData.password} />
|
||||||
|
{#if errorMessage}
|
||||||
|
<span class="form-msg error">
|
||||||
|
{errorMessage}
|
||||||
|
<!--Either the password or the email are incorrect-->
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</fieldset>
|
||||||
|
<button>
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
<div class="spacer"></div>
|
||||||
|
<a class="simple-link text-center w100 spacer" href="/register">
|
||||||
|
Register
|
||||||
|
</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
/* Login Page */
|
||||||
|
.login-page {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
& > div {
|
||||||
|
width: 40vw;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
10
webpage/src/routes/logout/+page.svelte
Normal file
10
webpage/src/routes/logout/+page.svelte
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { userStore } from "../UserStore.svelte";
|
||||||
|
import { goto } from "$app/navigation";
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
userStore.user = undefined;
|
||||||
|
goto('/');
|
||||||
|
});
|
||||||
|
</script>
|
88
webpage/src/styles/app.css
Normal file
88
webpage/src/styles/app.css
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
*{box-sizing: border-box;font-family: 'Roboto', sans-serif;}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--nav-bar-size: 54px;
|
||||||
|
--white: #ffffff;
|
||||||
|
--grey: #dbdcde;
|
||||||
|
--light-grey: #fafafa;
|
||||||
|
--main: #fca311;
|
||||||
|
--sec: #14213d;
|
||||||
|
--black: #000000;
|
||||||
|
--red: 212, 38, 38;
|
||||||
|
--green: 92, 199, 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button,
|
||||||
|
button {
|
||||||
|
border-radius: 10px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 3px 6px;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 2px 8px 1px #66666655;
|
||||||
|
background: var(--main);
|
||||||
|
color: var(--black);
|
||||||
|
|
||||||
|
&.padded {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.danger {
|
||||||
|
background: rgb(var(--red));
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-start {
|
||||||
|
justify-content: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-end {
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-center {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grow-1 {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w100 {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.simple-link {
|
||||||
|
color: var(--sec);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bigger {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger {
|
||||||
|
color: red;
|
||||||
|
}
|
8
webpage/src/styles/fonts.css
Normal file
8
webpage/src/styles/fonts.css
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: MedievalSharp;
|
||||||
|
src: url(/MedievalSharp-Regular.ttf);
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-medieval {
|
||||||
|
font-family: MedievalSharp, cursive;
|
||||||
|
}
|
147
webpage/src/styles/forms.css
Normal file
147
webpage/src/styles/forms.css
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
/* forms */
|
||||||
|
|
||||||
|
a {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card form {
|
||||||
|
padding: 0;
|
||||||
|
border-radius: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
padding: 30px;
|
||||||
|
margin: 20px 0;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 2px 5px 8px 2px #66666655;
|
||||||
|
|
||||||
|
label,
|
||||||
|
fieldset legend {
|
||||||
|
display: block;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 2px 5px 1px #66666655;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 10px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:invalid:focus,
|
||||||
|
&.submitted input:invalid {
|
||||||
|
box-shadow: 0 2px 5px 1px rgba(var(--red), 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.submitted input:valid {
|
||||||
|
box-shadow: 0 2px 5px 1px rgba(var(--green), 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
& .spacer {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
padding-bottom: 15px;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
.form-msg {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: rgb(var(--red))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
margin-left: 50%;
|
||||||
|
width: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-radial {
|
||||||
|
label {
|
||||||
|
display: inline;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"] {
|
||||||
|
width: auto;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Upload files */
|
||||||
|
fieldset.file-upload {
|
||||||
|
input[type="file"] {
|
||||||
|
height: 1px;
|
||||||
|
width: 1px;
|
||||||
|
padding: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-holder {
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
height: 150px;
|
||||||
|
width: 150px;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: none;
|
||||||
|
transform: none;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: all 1s;
|
||||||
|
|
||||||
|
&.adapt {
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
max-width: 80%;
|
||||||
|
max-height: 80%;
|
||||||
|
min-height: 150px;
|
||||||
|
min-width: 150px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 80%;
|
||||||
|
object-fit: contain;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding-top: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset.file-upload:has(input[type="file"]:invalid:focus) .icon,
|
||||||
|
&.submitted fieldset.file-upload:has(input[type="file"]:invalid:focus) .icon {
|
||||||
|
box-shadow: 0 2px 5px 1px rgba(var(--red), 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.submitted fieldset.file-upload:has(input[type="file"]:valid:focus) .icon {
|
||||||
|
box-shadow: 0 2px 5px 1px rgba(var(--green), 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
BIN
webpage/static/favicon.png
Normal file
BIN
webpage/static/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
22
webpage/svelte.config.js
Normal file
22
webpage/svelte.config.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import adapter from '@sveltejs/adapter-auto';
|
||||||
|
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||||
|
|
||||||
|
/** @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(),
|
||||||
|
alias: {
|
||||||
|
src: "src",
|
||||||
|
routes: "src/routes",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
18
webpage/tsconfig.json
Normal file
18
webpage/tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
}
|
6
webpage/vite.config.ts
Normal file
6
webpage/vite.config.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [sveltekit()]
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user