chore: more work on the app

This commit is contained in:
Andre Henriques 2024-02-24 15:28:23 +00:00
parent 32771c7422
commit ce866725ff
10 changed files with 544 additions and 38 deletions

View File

@ -5,6 +5,13 @@ import (
"errors"
)
type UserType int
const (
User_Normal UserType = iota + 1
User_Admin
)
type User struct {
Id string
Username string

View File

@ -471,11 +471,19 @@ func (x Handle) createContext(handler *Handle, mode AnswerType, r *http.Request)
Prefix: r.URL.Path,
})
for _, r := range r.Cookies() {
if r.Name == "auth" {
token = &r.Value
}
}
if mode != JSON {
for _, r := range r.Cookies() {
if r.Name == "auth" {
token = &r.Value
}
}
} else {
t := r.Header.Get("token")
if t != "" {
token = &t
}
}
// TODO check that the token is still valid
@ -512,14 +520,19 @@ func Redirect(path string, mode AnswerType, w http.ResponseWriter, r *http.Reque
}
func Logoff(mode AnswerType, w http.ResponseWriter, r *http.Request) {
// Delete cookie
cookie := &http.Cookie{
Name: "auth",
Value: "",
Expires: time.Unix(0, 0),
}
http.SetCookie(w, cookie)
Redirect("/login", mode, w, r)
if (mode == JSON) {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("\"Not Authorized\""))
} else {
// Delete cookie
cookie := &http.Cookie{
Name: "auth",
Value: "",
Expires: time.Unix(0, 0),
}
http.SetCookie(w, cookie)
Redirect("/login", mode, w, r)
}
}
func notAuth(mode AnswerType, w http.ResponseWriter, r *http.Request) {

169
users.go
View File

@ -11,6 +11,7 @@ import (
"golang.org/x/crypto/bcrypt"
dbtypes "git.andr3h3nriqu3s.com/andr3/fyp/logic/db_types"
"git.andr3h3nriqu3s.com/andr3/fyp/logic/utils"
. "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils"
)
@ -168,52 +169,52 @@ func usersEndpints(db *sql.DB, handle *Handle) {
return err
}
if len(dat.Username) == 0 || len(dat.Password) == 0 || len(dat.Email) == 0 {
return c.SendJSONStatus(w, http.StatusBadRequest, "Please provide a valid json");
}
if len(dat.Username) == 0 || len(dat.Password) == 0 || len(dat.Email) == 0 {
return c.SendJSONStatus(w, http.StatusBadRequest, "Please provide a valid json")
}
rows, err := db.Query("select username, email from users where username=$1 or email=$2;", dat.Username, dat.Email)
if err != nil {
return c.Error500(err);
}
if err != nil {
return c.Error500(err)
}
defer rows.Close()
if rows.Next() {
var db_username string
var db_email string
err = rows.Scan(&db_username, &db_email)
if err != nil {
return c.Error500(err)
}
if (db_email == dat.Email) {
return c.SendJSONStatus(w, http.StatusBadRequest, "Email already in use!")
}
if (db_username == dat.Username) {
return c.SendJSONStatus(w, http.StatusBadRequest, "Username already in use!")
}
panic("Unrechable")
if err != nil {
return c.Error500(err)
}
if db_email == dat.Email {
return c.SendJSONStatus(w, http.StatusBadRequest, "Email already in use!")
}
if db_username == dat.Username {
return c.SendJSONStatus(w, http.StatusBadRequest, "Username already in use!")
}
panic("Unrechable")
}
if len([]byte(dat.Password)) > 68 {
return c.JsonBadRequest(w, "Password is to long!")
return c.JsonBadRequest(w, "Password is to long!")
}
salt := generateSalt()
hash_password, err := hashPassword(dat.Password, salt)
if err != nil {
return c.Error500(err)
return c.Error500(err)
}
_, err = db.Exec("insert into users (username, email, salt, password) values ($1, $2, $3, $4);", dat.Username, dat.Email, salt, hash_password)
if err != nil {
return c.Error500(err)
}
if err != nil {
return c.Error500(err)
}
// TODO Give this to the generateToken function
token, login := generateToken(db, dat.Email, dat.Password)
if !login {
return c.SendJSONStatus(w, 500, "Could not login after creatting account please try again later")
}
if !login {
return c.SendJSONStatus(w, 500, "Could not login after creatting account please try again later")
}
user, err := dbtypes.UserFromToken(c.Db, token)
if err != nil {
@ -335,6 +336,87 @@ func usersEndpints(db *sql.DB, handle *Handle) {
return nil
})
// Handles updating users
handle.Post("/user/info", func(w http.ResponseWriter, r *http.Request, c *Context) *Error {
if !CheckAuthLevel(int(dbtypes.User_Normal), w, r, c) {
return nil
}
if c.Mode != JSON {
return c.Error500(nil)
}
type UserData struct {
Id string `json:"id"`
Email string `json:"email"`
}
var dat UserData
if err := c.ToJSON(r, &dat); err != nil {
return err
}
if dat.Id != c.User.Id && c.User.UserType != int(dbtypes.User_Admin) {
return c.SendJSONStatus(w, 401, "You need to be an admin to update another users account")
}
if dat.Id != c.User.Id {
var data struct {
Id string
}
err := utils.GetDBOnce(c, &data, "users where id=$1", dat.Id)
if err == NotFoundError {
return c.JsonBadRequest(w, "User does not exist")
} else if err != nil {
return c.Error500(err)
}
}
var data struct {
Id string
}
err := utils.GetDBOnce(c, &data, "users where email=$1", dat.Email)
if err != nil && err != NotFoundError {
return c.Error500(err)
}
if err != NotFoundError {
if data.Id == dat.Id {
return c.JsonBadRequest(w, "Email is the name as the previous one!")
} else {
return c.JsonBadRequest(w, "Email already in use")
}
}
_, err = c.Db.Exec("update users set email=$2 where id=$1", dat.Id, dat.Email)
if err != nil {
return c.Error500(err)
}
var user struct {
Id string
Username string
Email string
User_Type int
}
err = utils.GetDBOnce(c, &user, "users where id=$1", dat.Id)
if err != nil {
return c.Error500(err)
}
toReturnUser := dbtypes.User{
Id: user.Id,
Username: user.Username,
Email: user.Email,
UserType: user.User_Type,
}
return c.SendJSON(w, toReturnUser)
})
handle.Post("/user/info/email", func(w http.ResponseWriter, r *http.Request, c *Context) *Error {
if !CheckAuthLevel(1, w, r, c) {
return nil
@ -367,7 +449,44 @@ func usersEndpints(db *sql.DB, handle *Handle) {
return nil
}
if c.Mode == JSON {
return c.Error500(nil)
var dat struct {
Old_Password string `json:"old_password"`
Password string `json:"password"`
Password2 string `json:"password2"`
}
if err := c.ToJSON(r, &dat); err != nil {
return err
}
if dat.Password == "" {
return c.JsonBadRequest(w, "Password can not be empty")
}
if dat.Password != dat.Password2 {
return c.JsonBadRequest(w, "New passwords did not match")
}
c.Logger.Warn("test", "dat", dat)
_, login := generateToken(db, c.User.Email, dat.Old_Password)
if !login {
return c.JsonBadRequest(w, "Password is incorrect");
}
salt := generateSalt()
hash_password, err := hashPassword(dat.Password, salt)
if err != nil {
return c.Error500(err)
}
_, err = db.Exec("update users set salt=$1, password=$2 where id=$3", salt, hash_password, c.User.Id)
if err != nil {
return c.Error500(err)
}
return c.SendJSON(w, c.User.Id)
}
r.ParseForm()

View File

@ -0,0 +1,43 @@
<script lang="ts">
let { replace_slot, accept, file } = $props<{
replace_slot?: boolean,
accept?: string,
file?: File,
}>();
let fileInput: HTMLInputElement;
let fileData: string | undefined = $state(undefined);
function onChange(e: Event & {currentTarget: HTMLInputElement}) {
if (!e.currentTarget.files) {
return;
}
file = e.currentTarget.files[0];
const fileReader = new FileReader();
fileReader.onloadend = () => {
fileData = String(fileReader.result);
//elm.classList.add("adapt");
}
fileReader.readAsDataURL(file)
}
</script>
<div class="icon-holder">
<button class="icon" class:adapt={replace_slot && file} on:click={() => fileInput.click()}>
{#if replace_slot && file}
<slot name="replaced" file={file}>
<img src={fileData} alt="" />
<slot name="replaced-name">
Image Uploaded
</slot>
</slot>
{:else}
<slot></slot>
{/if}
</button>
<input id="file" name="file" type="file" required accept={accept} bind:this={fileInput} on:change={onChange} />
</div>

View File

@ -0,0 +1,52 @@
<script context="module" lang="ts">
export type DisplayFn = (
msg: string,
options?: {
type?: 'error' | 'success';
timeToShow?: number;
}
) => void;
</script>
<script lang="ts">
let message = $state<string | undefined>(undefined);
let type = $state<'error' | 'success'>('error');
let timeout: number | undefined = undefined;
export function display(
msg: string,
options?: {
type?: 'error' | 'success';
timeToShow?: number;
}
) {
if (timeout) clearTimeout(timeout);
if (!msg) {
message = undefined;
return;
}
let { type: l_type, timeToShow } = options ?? { type: 'error', timeToShow: undefined };
if (l_type) {
type = l_type;
}
message = msg;
if (timeToShow) {
timeout = setTimeout(() => {
message = undefined;
timeout = undefined;
}, timeToShow);
}
}
</script>
{#if message}
<div class="form-msg {type}">
{message}
</div>
{/if}

View File

@ -0,0 +1,59 @@
<script lang="ts">
let list = $state<{
name: string,
id: string,
}[]>([]);
</script>
<svelte:head>
<title>
Models
</title>
</svelte:head>
<main>
{#if list.length > 0}
<div class="list-header">
<h2>My Models</h2>
<div class="expand"></div>
<a class="button" href="/models/add">
New
</a>
</div>
<table>
<thead>
<tr>
<th>
Name
</th>
<th>
<!-- Open Button -->
</th>
</tr>
</thead>
<tbody>
{#each list as item}
<tr>
<td>
{item.name}
</td>
<td class="text-center">
<a class="button simple" href="/models/edit?id={item.id}">
Edit
</a>
</td>
</tr>
{/each}
</tbody>
</table>
{:else}
<h2 class="text-center">
You don't have any models
</h2>
<div class="text-center">
<a class="button padded" href="/models/add">
Create a new model
</a>
</div>
{/if}
</main>

View File

@ -0,0 +1,80 @@
<script lang="ts">
import FileUpload from "src/lib/FileUpload.svelte";
import MessageSimple from "src/lib/MessageSimple.svelte";
import "src/styles/forms.css";
let submitted = $state(false);
let message: MessageSimple;
let buttonClicked: Promise<void> = $state(Promise.resolve());
let data = $state<{
name: string,
file?: File,
}>({
name: '',
file: undefined,
});
function onSubmit() {
message.display("");
buttonClicked = new Promise<void>(() => {});
}
</script>
<svelte:head>
<title>Create new Model</title>
</svelte:head>
<main>
<h1>
Create new Model
</h1>
<form class:submitted on:submit|preventDefault={onSubmit}>
<fieldset>
<label for="name">Name</label>
<input id="name" name="name" required bind:value={data.name} />
<!--{{if .NameFoundError}}
<span class="form-msg error">
You already have a model with that name.
</span>
{{end}}-->
</fieldset>
<fieldset class="file-upload" >
<label for="file">Base image</label>
<div class="form-msg">
Please provide a base image.<br/>
This image is a sample of the images that you are going to classfiy.
</div>
<FileUpload replace_slot bind:file={data.file} >
<img src="/imgs/upload-icon.png" alt="" />
<span>
Upload image
</span>
<div slot="replaced-name">
<span>
Image selected
</span>
</div>
</FileUpload>
</fieldset>
<MessageSimple bind:this={message} />
{#await buttonClicked}
<div class="text-center">
File Uploading
</div>
{:then}
<button>
Create
</button>
{/await}
</form>
</main>
<style lang="scss">
main {
padding: 20px 15vw;
}
</style>

View File

@ -0,0 +1,129 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { userStore } from 'src/routes/UserStore.svelte';
import { onMount } from 'svelte';
import 'src/styles/forms.css';
import { post } from 'src/lib/requests.svelte';
import MessageSimple, { type DisplayFn } from 'src/lib/MessageSimple.svelte';
onMount(() => {
if (!userStore.isLogin()) {
goto('/login');
}
});
let email = $state(userStore.user?.email ?? '');
let passwordData = $state({
old_password: '',
password: '',
password2: ''
});
let submiitedEmail = $state(false);
let submiitedPassword = $state(false);
let msgEmail: MessageSimple;
let msgPassword: MessageSimple;
async function onSubmitEmail() {
submiitedEmail = true;
msgEmail.display('');
if (!userStore.user) return;
try {
let req = await post('user/info', {
id: userStore.user.id,
email: email
});
userStore.user = {
...userStore.user,
...req
};
msgEmail.display('User updated successufly!', { type: 'success', timeToShow: 10000 });
} catch (e) {
if (e instanceof Response) {
msgEmail.display(await e.json());
} else {
msgEmail.display('Could not update email');
}
}
}
async function onSubmitPassword() {
submiitedPassword = true;
msgPassword.display('');
if (!userStore.user) return;
try {
await post('user/info/password', passwordData);
passwordData = { old_password: '', password: '', password2: '' };
msgPassword.display('Password updated successufly!', { type: 'success', timeToShow: 10000 });
} catch (e) {
if (e instanceof Response) {
msgPassword.display(await e.json());
} else {
msgPassword.display('Could not update password');
}
}
}
</script>
<svelte:head>
<title>User Info</title>
</svelte:head>
<div class="login-page">
<div>
<h1>User Infomation</h1>
<form on:submit|preventDefault={onSubmitEmail} class:submiitedEmail>
<fieldset>
<label for="email">Email</label>
<input type="email" required name="email" bind:value={email} />
</fieldset>
<MessageSimple bind:this={msgEmail} />
<button> Update </button>
</form>
<form on:submit|preventDefault={onSubmitPassword} class:submiitedPassword>
<fieldset>
<label for="old_password">Old Password</label>
<input
required
bind:value={passwordData.old_password}
name="old_password"
type="password"
/>
</fieldset>
<fieldset>
<label for="password">New Password</label>
<input required bind:value={passwordData.password} name="password" type="password" />
</fieldset>
<fieldset>
<label for="password2">Repeat New Password</label>
<input required bind:value={passwordData.password2} name="password2" type="password" />
</fieldset>
<MessageSimple bind:this={msgPassword} />
<div>
<button> Update </button>
</div>
</form>
<!-- TODO Delete -->
</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>

View File

@ -37,6 +37,10 @@ button {
}
}
a.button {
text-decoration: none;
}
.flex {
display: flex;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB