super user CRUD

This commit is contained in:
hay-kot 2021-02-23 14:57:01 -09:00
commit 60806b783e
8 changed files with 170 additions and 59 deletions

View file

@ -1,6 +1,11 @@
const baseURL = "/api/";
import axios from "axios";
import utils from "@/utils";
import { store } from "../store/store";
axios.defaults.headers.common[
"Authorization"
] = `Bearer ${store.getters.getToken}`;
function processResponse(response) {
try {

View file

@ -1,23 +1,49 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
const authPrefix = baseURL + "auth/";
const authPrefix = baseURL + "auth";
const userPrefix = baseURL + "users";
const authURLs = {
token: `${authPrefix}token`,
token: `${authPrefix}/token`,
};
// const usersURLs = {
// }
const usersURLs = {
users: `${userPrefix}`,
self: `${userPrefix}/self`,
userID: id => `${userPrefix}/${id}`,
};
export default {
async login(formData) {
let response = await apiReq.post(authURLs.token, formData, {
let response = await apiReq.post(authURLs.token, formData, {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
});
console.log(response);
return response;
},
async allUsers() {
let response = await apiReq.get(usersURLs.users);
return response.data;
},
async create(user) {
let response = await apiReq.post(usersURLs.users, user);
return response.data;
},
async self() {
let response = await apiReq.get(usersURLs.self);
return response.data;
},
async byID(id) {
let response = await apiReq.get(usersURLs.userID(id));
return response.data;
},
async update(user) {
let response = await apiReq.put(usersURLs.userID(user.id), user);
return response.data;
},
async delete(id) {
let response = await apiReq.delete(usersURLs.userID(id));
return response.data;
},
};

View file

@ -21,14 +21,20 @@
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="grey" text @click="cancel"> {{ $t("general.cancel") }} </v-btn>
<v-btn :color="color" text @click="confirm"> {{ $t("general.confirm") }} </v-btn>
<v-btn color="grey" text @click="cancel">
{{ $t("general.cancel") }}
</v-btn>
<v-btn :color="color" text @click="confirm">
{{ $t("general.confirm") }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
const CLOSE_EVENT = "close";
const OPEN_EVENT = "open";
/**
* Confirmation Component used to add a second validaion step to an action.
* @version 1.0.1
@ -51,7 +57,7 @@ export default {
*/
icon: {
type: String,
default: "mid-alert-circle"
default: "mid-alert-circle",
},
/**
* Color theme of the component. Chose one of the defined theme colors.
@ -59,28 +65,35 @@ export default {
*/
color: {
type: String,
default: "error"
default: "error",
},
/**
* Define the max width of the component.
*/
width: {
type: Number,
default: 400
default: 400,
},
/**
* zIndex of the component.
*/
zIndex: {
type: Number,
default: 200
}
default: 200,
},
},
watch: {
dialog() {
if (this.dialog === false) {
this.$emit(CLOSE_EVENT);
} else this.$emit(OPEN_EVENT);
},
},
data: () => ({
/**
* Keep state of open or closed
*/
dialog: false
dialog: false,
}),
methods: {
/**
@ -120,8 +133,8 @@ export default {
//Hide Modal
this.dialog = false;
}
}
},
},
};
</script>

View file

@ -37,7 +37,7 @@
<v-row>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="editedItem.full_name"
v-model="editedItem.fullName"
label="Full Name"
></v-text-field>
</v-col>
@ -52,12 +52,18 @@
v-model="editedItem.family"
label="Family Group"
></v-text-field>
<v-col cols="12" sm="12" md="3">
<v-switch
v-model="editedItem.admin"
label="Admin"
></v-switch>
</v-col>
</v-col>
<v-col cols="12" sm="12" md="6" v-if="showPassword">
<v-text-field
v-model="editedItem.password"
label="User Password"
></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3">
<v-switch
v-model="editedItem.admin"
label="Admin"
></v-switch>
</v-col>
</v-row>
</v-container>
@ -76,10 +82,14 @@
</v-dialog>
<Confirmation
ref="deleteUserDialog"
title="Confirm Delete User"
message="Are you sure you want to delete the user?"
title="Confirm User Deletion"
:message="
`Are you sure you want to delete the user <b>${activeName} ID: ${activeId}<b/>`
"
icon="mdi-alert"
@confirm="deleteItemConfirm"
@confirm="deleteUser"
:width="450"
@close="closeDelete"
/>
</v-toolbar>
</template>
@ -110,11 +120,13 @@
<script>
import Confirmation from "@/components/UI/Confirmation";
import api from "@/api";
export default {
components: { Confirmation },
data: () => ({
dialog: false,
dialogDelete: false,
activeId: null,
activeName: null,
headers: [
{
text: "User ID",
@ -122,7 +134,7 @@ export default {
sortable: false,
value: "id",
},
{ text: "Full Name", value: "full_name" },
{ text: "Full Name", value: "fullName" },
{ text: "Email", value: "email" },
{ text: "Family", value: "family" },
{ text: "Admin", value: "admin" },
@ -132,14 +144,14 @@ export default {
editedIndex: -1,
editedItem: {
id: 0,
full_name: "",
fullName: "",
email: "",
family: "",
admin: false,
},
defaultItem: {
id: 0,
full_name: "",
fullName: "",
email: "",
family: "",
admin: false,
@ -150,6 +162,9 @@ export default {
formTitle() {
return this.editedIndex === -1 ? "New User" : "Edit User";
},
showPassword() {
return this.editedIndex === -1 ? true : false;
},
},
watch: {
@ -166,16 +181,13 @@ export default {
},
methods: {
initialize() {
this.users = [
{
id: 1,
full_name: "Change Me",
email: "changeme@email.com",
family: "public",
admin: false,
},
];
async initialize() {
this.users = await api.users.allUsers();
},
async deleteUser() {
await api.users.delete(this.editedIndex);
this.initialize();
},
editItem(item) {
@ -185,6 +197,8 @@ export default {
},
deleteItem(item) {
this.activeId = item.id;
this.activeName = item.fullName;
this.editedIndex = this.users.indexOf(item);
this.editedItem = Object.assign({}, item);
this.$refs.deleteUserDialog.open();
@ -211,12 +225,14 @@ export default {
});
},
save() {
async save() {
if (this.editedIndex > -1) {
Object.assign(this.users[this.editedIndex], this.editedItem);
console.log("New User", this.editedItem);
api.users.update(this.editedItem);
} else {
this.users.push(this.editedItem);
api.users.create(this.editedItem);
}
await this.initialize();
this.close();
},
},

View file

@ -2,18 +2,35 @@
<v-card>
<v-card-title class="headline">
<span>
<v-avatar color="accent" size="40" class="mr-2">
<v-avatar color="accent" size="40" class="mr-2" v-if="!loading">
<img src="https://cdn.vuetifyjs.com/images/john.jpg" alt="John" />
</v-avatar>
<v-progress-circular
v-else
indeterminate
color="primary"
large
class="mr-2"
>
</v-progress-circular>
</span>
Profile
<v-spacer></v-spacer>
User ID: {{ user.id }}
</v-card-title>
<v-divider></v-divider>
<v-card-text>
<v-form>
<v-text-field label="Full Name"> </v-text-field>
<v-text-field label="Email"> </v-text-field>
<v-text-field label="Group" readonly> </v-text-field>
<v-text-field label="Full Name" v-model="user.fullName"> </v-text-field>
<v-text-field label="Email" v-model="user.email"> </v-text-field>
<v-text-field
label="Family"
readonly
v-model="user.family"
persistent-hint
hint="Family groups can only be set by administrators"
>
</v-text-field>
</v-form>
</v-card-text>
<v-card-actions>
@ -22,7 +39,7 @@
{{ $t("settings.change-password") }}
</v-btn>
<v-spacer></v-spacer>
<v-btn color="success" class="mr-2">
<v-btn color="success" class="mr-2" @click="updateUser">
<v-icon left> mdi-content-save </v-icon>
{{ $t("general.save") }}
</v-btn>
@ -32,27 +49,43 @@
<script>
// import AvatarPicker from '@/components/AvatarPicker'
import api from "@/api";
export default {
pageTitle: "My Profile",
data() {
return {
loading: false,
form: {
firstName: "John",
lastName: "Doe",
contactEmail: "john@doe.com",
avatar: "MALE_CAUCASIAN_BLOND_BEARD",
user: {
fullName: "Change Me",
email: "changeme@email.com",
family: "public",
admin: true,
id: 1,
},
showAvatarPicker: false,
};
},
async mounted() {
this.refreshProfile();
},
methods: {
async refreshProfile() {
this.user = await api.users.self();
},
openAvatarPicker() {
this.showAvatarPicker = true;
},
selectAvatar(avatar) {
this.form.avatar = avatar;
this.user.avatar = avatar;
},
async updateUser() {
this.loading = true;
let newKey = await api.users.update(this.user);
this.$store.commit("setToken", newKey.access_token);
this.refreshProfile();
this.loading = false;
},
},
};

View file

@ -1,5 +1,6 @@
import api from "@/api";
import Vuetify from "../../plugins/vuetify";
import axios from "axios";
function inDarkMode(payload) {
let isDark;
@ -42,6 +43,7 @@ const mutations = {
},
setToken(state, payload) {
state.isLoggedIn = true;
axios.defaults.headers.common["Authorization"] = `Bearer ${payload}`;
state.token = payload;
},
};
@ -56,6 +58,7 @@ const actions = {
}
},
async initTheme({ dispatch, getters }) {
//If theme is empty resetTheme
if (Object.keys(getters.getActiveTheme).length === 0) {

View file

@ -3,7 +3,6 @@ from fastapi.logger import logger
from schema.settings import SiteSettings, Webhooks
from sqlalchemy.orm import Session
from sqlalchemy.orm.session import Session
from fastapi.logger import logger
from db.database import db
from db.db_setup import create_session

View file

@ -1,8 +1,10 @@
from datetime import timedelta
from core.security import get_password_hash
from db.database import db
from db.db_setup import generate_session
from fastapi import APIRouter, Depends
from routes.deps import manager
from routes.deps import manager, query_user
from schema.user import UserBase, UserIn, UserInDB, UserOut
from sqlalchemy.orm.session import Session
@ -35,6 +37,14 @@ async def get_all_users(
return {"details": "user not authorized"}
@router.get("/self", response_model=UserOut)
async def get_user_by_id(
current_user: UserInDB = Depends(manager),
session: Session = Depends(generate_session),
):
return current_user.dict()
@router.get("/{id}", response_model=UserOut)
async def get_user_by_id(
id: int,
@ -44,7 +54,7 @@ async def get_user_by_id(
return db.users.get(session, id)
@router.put("/{id}", response_model=UserOut)
@router.put("/{id}")
async def update_user(
id: int,
new_data: UserBase,
@ -53,7 +63,13 @@ async def update_user(
):
if current_user.id == id or current_user.admin:
return db.users.update(session, id, new_data.dict())
updated_user = db.users.update(session, id, new_data.dict())
email = updated_user.get("email")
if current_user.id == id:
access_token = manager.create_access_token(
data=dict(sub=email), expires=timedelta(hours=2)
)
return {"access_token": access_token, "token_type": "bearer"}
return