diff --git a/go.mod b/go.mod index eb658de..4b001f6 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/charmbracelet/lipgloss v0.9.1 // 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/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect diff --git a/go.sum b/go.sum index 755022d..8032581 100644 --- a/go.sum +++ b/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/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= 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/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= diff --git a/logic/utils/handler.go b/logic/utils/handler.go index 6e37a1a..decdef4 100644 --- a/logic/utils/handler.go +++ b/logic/utils/handler.go @@ -14,6 +14,7 @@ import ( dbtypes "git.andr3h3nriqu3s.com/andr3/fyp/logic/db_types" "github.com/charmbracelet/log" + "github.com/goccy/go-json" ) func Mul(n1 int, n2 int) int { @@ -347,6 +348,43 @@ type Context struct { 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 { c.SetReportCaller(true) c.Logger.Error(message) @@ -609,6 +647,10 @@ func NewHandler(db *sql.DB) *Handle { if r.Header.Get("Request-Type") == "htmlfull" { ans = HTMLFULL } + if r.Header.Get("content-type") == "application/json" { + ans = JSON + } + //TODO JSON //Login state @@ -618,6 +660,9 @@ func NewHandler(db *sql.DB) *Handle { return } + w.Header().Add("Access-Control-Allow-Origin", "*") + w.Header().Add("Access-Control-Allow-Headers", "*") + if r.Method == "GET" { x.handleGets(w, r, context) return @@ -630,6 +675,9 @@ func NewHandler(db *sql.DB) *Handle { x.handleDeletes(w, r, context) return } + if r.Method == "OPTIONS" { + return + } panic("TODO handle method: " + r.Method) }) diff --git a/users.go b/users.go index 94b38b0..9405b95 100644 --- a/users.go +++ b/users.go @@ -4,7 +4,6 @@ import ( "crypto/rand" "database/sql" "encoding/hex" - "fmt" "io" "net/http" "time" @@ -79,8 +78,39 @@ func usersEndpints(db *sql.DB, handle *Handle) { handle.GetHTML("/login", AnswerTemplate("login.html", nil, 0)) handle.Post("/login", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { 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() diff --git a/webpage/.eslintignore b/webpage/.eslintignore new file mode 100644 index 0000000..3897265 --- /dev/null +++ b/webpage/.eslintignore @@ -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 diff --git a/webpage/.eslintrc.cjs b/webpage/.eslintrc.cjs new file mode 100644 index 0000000..0b75758 --- /dev/null +++ b/webpage/.eslintrc.cjs @@ -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' + } + } + ] +}; diff --git a/webpage/.gitignore b/webpage/.gitignore new file mode 100644 index 0000000..6635cf5 --- /dev/null +++ b/webpage/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +node_modules +/build +/.svelte-kit +/package +.env +.env.* +!.env.example +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/webpage/.npmrc b/webpage/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/webpage/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/webpage/.prettierignore b/webpage/.prettierignore new file mode 100644 index 0000000..cc41cea --- /dev/null +++ b/webpage/.prettierignore @@ -0,0 +1,4 @@ +# Ignore files for PNPM, NPM and YARN +pnpm-lock.yaml +package-lock.json +yarn.lock diff --git a/webpage/.prettierrc b/webpage/.prettierrc new file mode 100644 index 0000000..9573023 --- /dev/null +++ b/webpage/.prettierrc @@ -0,0 +1,8 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "plugins": ["prettier-plugin-svelte"], + "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] +} diff --git a/webpage/README.md b/webpage/README.md new file mode 100644 index 0000000..5ce6766 --- /dev/null +++ b/webpage/README.md @@ -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. diff --git a/webpage/bun.lockb b/webpage/bun.lockb new file mode 100755 index 0000000..3db0581 Binary files /dev/null and b/webpage/bun.lockb differ diff --git a/webpage/package.json b/webpage/package.json new file mode 100644 index 0000000..39ec920 --- /dev/null +++ b/webpage/package.json @@ -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" +} diff --git a/webpage/src/NavBar.svelte b/webpage/src/NavBar.svelte new file mode 100644 index 0000000..6c87ba6 --- /dev/null +++ b/webpage/src/NavBar.svelte @@ -0,0 +1,60 @@ + + + + + diff --git a/webpage/src/app.d.ts b/webpage/src/app.d.ts new file mode 100644 index 0000000..743f07b --- /dev/null +++ b/webpage/src/app.d.ts @@ -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 {}; diff --git a/webpage/src/app.html b/webpage/src/app.html new file mode 100644 index 0000000..77a5ff5 --- /dev/null +++ b/webpage/src/app.html @@ -0,0 +1,12 @@ + + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/webpage/src/lib/index.ts b/webpage/src/lib/index.ts new file mode 100644 index 0000000..856f2b6 --- /dev/null +++ b/webpage/src/lib/index.ts @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/webpage/src/lib/requests.svelte.ts b/webpage/src/lib/requests.svelte.ts new file mode 100644 index 0000000..13738d4 --- /dev/null +++ b/webpage/src/lib/requests.svelte.ts @@ -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(); +} diff --git a/webpage/src/routes/+error.svelte b/webpage/src/routes/+error.svelte new file mode 100644 index 0000000..b08aa0e --- /dev/null +++ b/webpage/src/routes/+error.svelte @@ -0,0 +1,17 @@ + + +{#if $page.error} + {#if $page.status == 404} + + {:else} +

+ {$page.status} +

+

+ {$page.error.message} +

+ {/if} +{/if} diff --git a/webpage/src/routes/+layout.svelte b/webpage/src/routes/+layout.svelte new file mode 100644 index 0000000..9d2737a --- /dev/null +++ b/webpage/src/routes/+layout.svelte @@ -0,0 +1,9 @@ + + + + + diff --git a/webpage/src/routes/+layout.ts b/webpage/src/routes/+layout.ts new file mode 100644 index 0000000..2dedff1 --- /dev/null +++ b/webpage/src/routes/+layout.ts @@ -0,0 +1,4 @@ +export const prerender = true; +export const ssr = false; +export const csr = true; +//export const trailingSlash = ''; diff --git a/webpage/src/routes/+page.svelte b/webpage/src/routes/+page.svelte new file mode 100644 index 0000000..9845c90 --- /dev/null +++ b/webpage/src/routes/+page.svelte @@ -0,0 +1 @@ +

Main Web Page

diff --git a/webpage/src/routes/Page404.svelte b/webpage/src/routes/Page404.svelte new file mode 100644 index 0000000..e45f516 --- /dev/null +++ b/webpage/src/routes/Page404.svelte @@ -0,0 +1,54 @@ + + +
+
+

+ 404 +

+ {#if message} +

+ {message} +

+ {#if goBackLink} + + {/if} + {:else} +

+ Page Not found +

+
+ The page you were looking for does not exist +
+ {/if} +
+
+ + diff --git a/webpage/src/routes/UserStore.svelte.ts b/webpage/src/routes/UserStore.svelte.ts new file mode 100644 index 0000000..e882482 --- /dev/null +++ b/webpage/src/routes/UserStore.svelte.ts @@ -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(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(); diff --git a/webpage/src/routes/login/+page.svelte b/webpage/src/routes/login/+page.svelte new file mode 100644 index 0000000..41240a4 --- /dev/null +++ b/webpage/src/routes/login/+page.svelte @@ -0,0 +1,83 @@ + + + + Login + + + + + diff --git a/webpage/src/routes/logout/+page.svelte b/webpage/src/routes/logout/+page.svelte new file mode 100644 index 0000000..35bb995 --- /dev/null +++ b/webpage/src/routes/logout/+page.svelte @@ -0,0 +1,10 @@ + diff --git a/webpage/src/styles/app.css b/webpage/src/styles/app.css new file mode 100644 index 0000000..50800a7 --- /dev/null +++ b/webpage/src/styles/app.css @@ -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; +} diff --git a/webpage/src/styles/fonts.css b/webpage/src/styles/fonts.css new file mode 100644 index 0000000..57e7936 --- /dev/null +++ b/webpage/src/styles/fonts.css @@ -0,0 +1,8 @@ +@font-face { + font-family: MedievalSharp; + src: url(/MedievalSharp-Regular.ttf); +} + +.font-medieval { + font-family: MedievalSharp, cursive; +} diff --git a/webpage/src/styles/forms.css b/webpage/src/styles/forms.css new file mode 100644 index 0000000..b75209d --- /dev/null +++ b/webpage/src/styles/forms.css @@ -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); + } + +} diff --git a/webpage/static/favicon.png b/webpage/static/favicon.png new file mode 100644 index 0000000..825b9e6 Binary files /dev/null and b/webpage/static/favicon.png differ diff --git a/webpage/svelte.config.js b/webpage/svelte.config.js new file mode 100644 index 0000000..d94ae00 --- /dev/null +++ b/webpage/svelte.config.js @@ -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; diff --git a/webpage/tsconfig.json b/webpage/tsconfig.json new file mode 100644 index 0000000..82081ab --- /dev/null +++ b/webpage/tsconfig.json @@ -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 +} diff --git a/webpage/vite.config.ts b/webpage/vite.config.ts new file mode 100644 index 0000000..bbf8c7d --- /dev/null +++ b/webpage/vite.config.ts @@ -0,0 +1,6 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [sveltekit()] +});