1
0
mirror of https://github.com/Damillora/phoebe.git synced 2025-04-15 19:33:12 +00:00

Compare commits

...

9 Commits

15 changed files with 323 additions and 51 deletions

@ -3,6 +3,8 @@ package app
import (
"net/http"
"github.com/Damillora/Shioriko/pkg/database"
"github.com/Damillora/Shioriko/pkg/middleware"
"github.com/Damillora/Shioriko/pkg/models"
"github.com/Damillora/Shioriko/pkg/services"
"github.com/gin-gonic/gin"
@ -11,6 +13,11 @@ import (
func InitializeAuthRoutes(g *gin.Engine) {
g.POST("/api/auth/login", createToken)
protected := g.Group("/api/auth").Use(middleware.AuthMiddleware())
{
protected.POST("/token", createTokenLoggedIn)
}
}
func createToken(c *gin.Context) {
var model models.LoginFormModel
@ -50,3 +57,21 @@ func createToken(c *gin.Context) {
})
}
}
func createTokenLoggedIn(c *gin.Context) {
result, ok := c.Get("user")
if ok {
user := result.(*database.User)
if user != nil {
token := services.CreateToken(user)
c.JSON(http.StatusOK, models.TokenResponse{
Token: token,
})
}
} else {
c.JSON(http.StatusUnauthorized, models.ErrorResponse{
Code: http.StatusUnauthorized,
Message: "No authorized user",
})
}
}

@ -18,7 +18,8 @@ func InitializeUserRoutes(g *gin.Engine) {
protected := g.Group("/api/user").Use(middleware.AuthMiddleware())
{
protected.GET("/profile", userProfile)
protected.POST("/update", userUpdate)
protected.PUT("/update", userUpdate)
protected.PUT("/update-password", userUpdatePassword)
}
}
@ -114,7 +115,45 @@ func userUpdate(c *gin.Context) {
result, ok := c.Get("user")
if ok {
user := result.(*database.User)
services.UpdateUser(user.ID, model)
services.UpdateUserProfile(user.ID, model)
c.JSON(http.StatusOK, nil)
} else {
c.JSON(http.StatusBadRequest, models.ErrorResponse{
Code: http.StatusBadRequest,
Message: "User does not exist",
})
}
}
func userUpdatePassword(c *gin.Context) {
var model models.UserUpdatePasswordModel
err := c.ShouldBindJSON(&model)
if err != nil {
c.JSON(http.StatusBadRequest, models.ErrorResponse{
Code: http.StatusBadRequest,
Message: err.Error(),
})
c.Abort()
return
}
validate := validator.New()
err = validate.Struct(model)
if err != nil {
c.JSON(http.StatusBadRequest, models.ErrorResponse{
Code: http.StatusBadRequest,
Message: err.Error(),
})
c.Abort()
return
}
result, ok := c.Get("user")
if ok {
user := result.(*database.User)
services.UpdateUserPassword(user.ID, model)
c.JSON(http.StatusOK, nil)
} else {
c.JSON(http.StatusBadRequest, models.ErrorResponse{
Code: http.StatusBadRequest,

@ -7,10 +7,13 @@ type UserCreateModel struct {
}
type UserUpdateModel struct {
Email string `json:"email" validate:"required,email"`
Username string `json:"username" validate:"required"`
OldPassword string `json:"oldPassword"`
NewPassword string `json:"newPassword"`
Email string `json:"email" validate:"required,email"`
Username string `json:"username" validate:"required"`
}
type UserUpdatePasswordModel struct {
OldPassword string `json:"old_password"`
NewPassword string `json:"new_password"`
}
type TagTypeCreateModel struct {

@ -39,7 +39,7 @@ func GetUserFromUsername(username string) *database.User {
return &user
}
func UpdateUser(id string, model models.UserUpdateModel) (*database.User, error) {
func UpdateUserProfile(id string, model models.UserUpdateModel) (*database.User, error) {
var user database.User
result := database.DB.Where("id = ?", id).First(&user)
@ -49,6 +49,19 @@ func UpdateUser(id string, model models.UserUpdateModel) (*database.User, error)
user.Email = model.Email
user.Username = model.Username
result = database.DB.Save(&user)
if result.Error != nil {
return nil, result.Error
}
return &user, nil
}
func UpdateUserPassword(id string, model models.UserUpdatePasswordModel) (*database.User, error) {
var user database.User
result := database.DB.Where("id = ?", id).First(&user)
if user.Password != "" {
verifyErr := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(model.OldPassword))
if verifyErr != nil {

@ -62,7 +62,7 @@
@extend .dropdown-item;
background-color: hsl(var(--bulma-scheme-h), var(--bulma-scheme-s), calc(var(--bulma-scheme-main-l) + var(--bulma-dropdown-item-background-l-delta)));
--bulma-dropdown-item-background-l-delta: 0%;
&:hover {
&:hover, &.focus, &:focus {
--bulma-dropdown-item-background-l-delta: var(--bulma-hover-background-l-delta);
--bulma-dropdown-item-border-l-delta: var(--bulma-hover-border-l-delta);
}

@ -37,6 +37,20 @@ export async function register({ email, username, password }) {
return response.data;
}
export async function updateToken() {
const endpoint = url + "/api/auth/token";
const response = await axios({
url: endpoint,
method: "POST",
headers: {
'Authorization': 'Bearer ' + current_token,
},
})
token.set(response.data.token);
return response.data;
}
export async function getTags() {
const endpoint = url + "/api/tag";
const response = await axios.get(endpoint);
@ -200,6 +214,36 @@ export async function updateTag(id, { name, tagTypeId }) {
return response.data;
}
export async function updateUserProfile({ email, username, }) {
const endpoint = url + "/api/user/update";
const response = await axios({
url: endpoint,
method: "PUT",
headers: {
'Authorization': 'Bearer ' + current_token,
},
withCredentials: true,
data: {
email, username,
}
})
return response.data;
}
export async function updateUserPassword({ old_password, new_password }) {
const endpoint = url + "/api/user/update-password";
const response = await axios({
url: endpoint,
method: "PUT",
headers: {
'Authorization': 'Bearer ' + current_token,
},
withCredentials: true,
data: {
old_password, new_password
}
})
return response.data;
}
export async function getTagTypes() {
const endpoint = url + "/api/tagtype";

@ -0,0 +1,8 @@
<script>
</script>
<div class="panel is-primary">
<div class="panel-heading">User Actions</div>
<a href="/user/profile" class="panel-block">Profile</a>
<a href="/user/password" class="panel-block">Change Password</a>
</div>

@ -4,6 +4,7 @@
import { getRelatedTags } from "$lib/api";
import AuthCheck from "$lib/components/checks/AuthCheck.svelte";
import TagLinkNumbered from "$lib/components/ui/TagLinkNumbered.svelte";
import TagTypeIndicator from "../ui/TagTypeIndicator.svelte";
let { tag, data, toggleRenameMenu } = $props();
let related_tags = $state([]);
@ -30,7 +31,7 @@
<div class="row">
<strong>Category:</strong>
</div>
<div class="row">{data.tagType}</div>
<div class="row"><TagTypeIndicator tagType={data.tagType} /></div>
</div>
<div class="panel-block column">
<div class="row">

@ -33,6 +33,7 @@
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
@ -46,20 +47,27 @@
</div>
<div class="navbar-end">
<div class="navbar-item has-dropdown is-hoverable">
<div
class="navbar-item has-dropdown is-hoverable"
>
<a
href={"#"}
class="navbar-link"
>
{loggedIn ? username : "Guest"}
</a>
{#if loggedIn}
<div class="navbar-link">{username}</div>
<div class="navbar-dropdown">
<div class="navbar-dropdown is-right">
<a href="/user/profile" class="navbar-item">
Profile
</a>
<a href="/user/password" class="navbar-item">
Change Password
</a>
<hr class="navbar-divider" />
<a href="/auth/logout" class="navbar-item">Log out</a>
</div>
{:else}
<div class="navbar-link">logged out</div>
<div class="navbar-dropdown">
<a href="/auth/register" class="navbar-item">
Register

@ -13,7 +13,7 @@ const getUsernameFromToken = (token) => {
const tokenData = (JSON.parse(atob(token.split('.')[1])));
return tokenData.name;
}
return "logged out";
return "Guest";
}

@ -54,8 +54,8 @@
let imagePercentage = $state("0%");
</script>
<div class="container">
<section class="section">
<section class="section">
<div class="container">
<div class="columns">
<div class="column is-one-third">
{#if post}
@ -90,9 +90,7 @@
</div>
{/if}
{:else}
<div class="skeleton-block">
</div>
<div class="skeleton-block"></div>
{/if}
</div>
<div class="column box">
@ -100,27 +98,33 @@
{#if post.width > 1000 && isOriginal == false}
<div class="notification is-info">
Resized to {imagePercentage} of the original image.
<a onclick="{() => { isOriginal = true; }}"
>View original</a
<a
onclick={() => {
isOriginal = true;
}}>View original</a
>
</div>
<figure class="image">
<ShiorikoImage alt={post.id} src={post.preview_path} />
<ShiorikoImage
alt={post.id}
src={post.preview_path}
/>
</figure>
{:else}
<div class="notification is-primary">
Currently viewing original image.
</div>
<figure class="image">
<ShiorikoImage alt={post.id} src={post.image_path} />
<ShiorikoImage
alt={post.id}
src={post.image_path}
/>
</figure>
{/if}
{:else}
<div class="skeleton-block">
</div>
<div class="skeleton-block"></div>
{/if}
</div>
</div>
</section>
</div>
</div>
</section>

@ -97,9 +97,6 @@
<div class="container">
<div class="block">
<div class="columns is-multiline">
<div class="column is-full">
<div class="block"></div>
</div>
<div class="column is-one-third">
<div class="panel is-primary">
<div class="panel-heading">Menu</div>

@ -184,9 +184,4 @@
</div>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h1 class="title">Upload Image</h1>
</div>
</section>
</section>

@ -0,0 +1,79 @@
<script>
import { updateToken, updateUserPassword, } from "$lib/api";
import UserActionsPanel from "$lib/components/panels/UserActionsPanel.svelte";
import { onMount } from "svelte";
let loading = $state(false);
let updated = $state(false);
let form = $state({
old_password: "",
new_password: "",
});
const getData = async () => {
loading = false;
};
const submitForm = async () => {
updated = false;
await updateUserPassword({ old_password: form.old_password, new_password: form.new_password })
await updateToken();
updated = true;
};
onMount(() => {
loading = true;
getData();
});
</script>
<section class="section">
<div class="container">
<div class="columns">
<div class="column is-one-third">
<UserActionsPanel />
</div>
<div class="column is-two-thirds">
<div class="box">
<h1 class="title">Change Password</h1>
{#if updated}
<div class="notification is-success">
Password updated!
</div>
{/if}
<form onsubmit={submitForm}>
<div class="field">
<label class="label" for="old_password">Current Password</label>
<div class="control">
<input
id="old_password"
class="input"
class:is-skeleton="{loading}"
type="password"
placeholder="Current password"
bind:value={form.old_password}
/>
</div>
</div>
<div class="field">
<label class="label" for="new_password">New Password</label>
<div class="control">
<input
id="new_password"
class="input"
class:is-skeleton="{loading}"
type="password"
placeholder="New password"
bind:value={form.new_password}
/>
</div>
</div>
<div class="field">
<button class="button is-primary is-fullwidth is-outlined" type="submit">Change Password</button>
</div>
</form>
</div>
</div>
</div>
</div>
</section>

@ -1,26 +1,82 @@
<script>
import { getUserProfile, updateToken, updateUserProfile } from "$lib/api";
import UserActionsPanel from "$lib/components/panels/UserActionsPanel.svelte";
import { onMount } from "svelte";
import { getUserProfile } from "$lib/api";
import AuthRequired from "$lib/components/checks/AuthRequired.svelte";
let user = $state();
let loading = $state(false);
let updated = $state(false);
let form = $state({
email: "",
username: "",
});
const getData = async () => {
user = await getUserProfile();
const user = await getUserProfile();
form.email = user.email;
form.username = user.username;
loading = false;
};
const submitForm = async () => {
updated = false;
await updateUserProfile({ email: form.email, username: form.username })
await updateToken();
updated = true;
};
onMount(() => {
loading = true;
getData();
});
</script>
<AuthRequired />
<section class="section">
<div class="container">
{#if user}
<h1 class="title">Welcome, {user.username}</h1>
<p>Email: {user.email}</p>
<p>Username: {user.username}</p>
{/if}
<div class="columns">
<div class="column is-one-third">
<UserActionsPanel />
</div>
<div class="column is-two-thirds">
<div class="box">
<h1 class="title">Profile</h1>
{#if updated}
<div class="notification is-success">
Profile updated!
</div>
{/if}
<form onsubmit={submitForm}>
<div class="field">
<label class="label" for="email">Email</label>
<div class="control">
<input
id="email"
class="input"
class:is-skeleton="{loading}"
type="text"
placeholder="Email"
bind:value={form.email}
/>
</div>
</div>
<div class="field">
<label class="label" for="username">Username</label>
<div class="control">
<input
id="username"
class="input"
class:is-skeleton="{loading}"
type="text"
placeholder="Username"
bind:value={form.username}
/>
</div>
</div>
<div class="field">
<button class="button is-primary is-fullwidth is-outlined" type="submit">Update</button>
</div>
</form>
</div>
</div>
</div>
</div>
</section>