Add promote / demote user to admin and reintroduce create user dashboard
This commit is contained in:
@@ -14,6 +14,10 @@ type UserCredentials struct {
|
|||||||
Password string `json:"password" binding:"required"`
|
Password string `json:"password" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SetAdminInput struct {
|
||||||
|
Admin *bool `json:"admin" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
func (store *Store) CreateUser(ctx *gin.Context) {
|
func (store *Store) CreateUser(ctx *gin.Context) {
|
||||||
claimsVal, ok := ctx.Get("userClaims")
|
claimsVal, ok := ctx.Get("userClaims")
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -101,6 +105,57 @@ func (store *Store) UpdateUser(ctx *gin.Context) {
|
|||||||
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "will be implemented"})
|
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "will be implemented"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (store *Store) SetUserAdmin(ctx *gin.Context) {
|
||||||
|
claimsVal, ok := ctx.Get("userClaims")
|
||||||
|
if !ok {
|
||||||
|
ctx.JSON(http.StatusUnauthorized, gin.H{"error": "user claims could not be found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
claims, ok := claimsVal.(*jwt.MapClaims)
|
||||||
|
if !ok {
|
||||||
|
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "invalid claims"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !(*claims)["admin"].(bool) {
|
||||||
|
ctx.JSON(http.StatusForbidden, gin.H{"error": "admin access required"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
callerIDF, ok := (*claims)["id"].(float64)
|
||||||
|
if !ok {
|
||||||
|
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "invalid user id in claims"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
callerID := uint(callerIDF)
|
||||||
|
|
||||||
|
targetID := ctx.Param("id")
|
||||||
|
|
||||||
|
var input SetAdminInput
|
||||||
|
if err := ctx.ShouldBindBodyWithJSON(&input); err != nil {
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var user models.User
|
||||||
|
if err := store.DB.First(&user, targetID).Error; err != nil {
|
||||||
|
ctx.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.ID == callerID {
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": "cannot change your own admin status"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user.Admin = *input.Admin
|
||||||
|
if err := store.DB.Save(&user).Error; err != nil {
|
||||||
|
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, user)
|
||||||
|
}
|
||||||
|
|
||||||
func (store *Store) DeleteUser(ctx *gin.Context) {
|
func (store *Store) DeleteUser(ctx *gin.Context) {
|
||||||
claimsVal, ok := ctx.Get("userClaims")
|
claimsVal, ok := ctx.Get("userClaims")
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ func main() {
|
|||||||
protected.DELETE("/user/:id", store.DeleteUser)
|
protected.DELETE("/user/:id", store.DeleteUser)
|
||||||
r.GET("/user", store.GetUsers)
|
r.GET("/user", store.GetUsers)
|
||||||
protected.POST("/user", store.CreateUser)
|
protected.POST("/user", store.CreateUser)
|
||||||
|
protected.PATCH("/user/:id/admin", store.SetUserAdmin)
|
||||||
|
|
||||||
// AUTH
|
// AUTH
|
||||||
r.POST("/auth/login", store.Login)
|
r.POST("/auth/login", store.Login)
|
||||||
|
|||||||
@@ -59,6 +59,16 @@ export const useAuthStore = defineStore("auth", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function setUserAdmin(userId, admin) {
|
||||||
|
try {
|
||||||
|
const res = await axios.patch(`/api/user/${userId}/admin`, { admin });
|
||||||
|
return res.data;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user,
|
user,
|
||||||
|
|
||||||
@@ -69,5 +79,6 @@ export const useAuthStore = defineStore("auth", () => {
|
|||||||
refreshToken,
|
refreshToken,
|
||||||
logOut,
|
logOut,
|
||||||
createUser,
|
createUser,
|
||||||
|
setUserAdmin,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import CreatePost from "./CreatePost.vue";
|
|||||||
import CreateFavorite from "./CreateFavorite.vue";
|
import CreateFavorite from "./CreateFavorite.vue";
|
||||||
import CreateActivity from "./CreateActivity.vue";
|
import CreateActivity from "./CreateActivity.vue";
|
||||||
import CreateRowing from "./CreateRowing.vue";
|
import CreateRowing from "./CreateRowing.vue";
|
||||||
|
import ManageUsers from "./ManageUsers.vue";
|
||||||
|
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
</script>
|
</script>
|
||||||
@@ -21,6 +22,7 @@ const auth = useAuthStore();
|
|||||||
<CreateFavorite class="bdr-2 bg-bg_primary" v-if="auth.loggedIn" />
|
<CreateFavorite class="bdr-2 bg-bg_primary" v-if="auth.loggedIn" />
|
||||||
<CreateActivity class="bdr-2 bg-bg_primary" v-if="auth.loggedIn" />
|
<CreateActivity class="bdr-2 bg-bg_primary" v-if="auth.loggedIn" />
|
||||||
<CreateRowing class="bdr-2 bg-bg_primary" v-if="auth.loggedIn" />
|
<CreateRowing class="bdr-2 bg-bg_primary" v-if="auth.loggedIn" />
|
||||||
|
<ManageUsers class="bdr-2 bg-bg_primary" v-if="auth.loggedIn" />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
45
nginx/vue/src/views/admin/ManageUsers.vue
Normal file
45
nginx/vue/src/views/admin/ManageUsers.vue
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<script setup>
|
||||||
|
import Button from "@/components/input/Button.vue";
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const auth = useAuthStore();
|
||||||
|
const users = ref([]);
|
||||||
|
|
||||||
|
async function fetchUsers() {
|
||||||
|
try {
|
||||||
|
const res = await axios.get("/api/user");
|
||||||
|
users.value = res.data;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleAdmin(user) {
|
||||||
|
try {
|
||||||
|
const res = await auth.setUserAdmin(user.id, !user.admin);
|
||||||
|
user.admin = res.admin;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(fetchUsers);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<h1>Manage Users</h1>
|
||||||
|
<div v-for="user in users" :key="user.id" class="flex flex-row items-center gap-2">
|
||||||
|
<span>{{ user.username }}</span>
|
||||||
|
<span v-if="user.admin">(admin)</span>
|
||||||
|
<Button
|
||||||
|
v-if="user.id !== auth.user.id"
|
||||||
|
@click="toggleAdmin(user)"
|
||||||
|
>
|
||||||
|
{{ user.admin ? "Demote" : "Promote" }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
Reference in New Issue
Block a user