This commit is contained in:
hay-kot 2021-02-27 18:57:52 -09:00
commit f22522a52c
15 changed files with 934 additions and 242 deletions

View file

@ -86,6 +86,8 @@ export default {
search: false,
}),
methods: {
// For Later!
/**
* Checks if 'system' is set for dark mode and then sets the corrisponding value for vuetify
*/

View file

@ -7,8 +7,8 @@ import migration from "./migration";
import myUtils from "./upload";
import category from "./category";
import meta from "./meta";
import users from "./users"
import users from "./users";
import signUps from "./signUps";
export default {
recipes: recipe,
@ -20,5 +20,6 @@ export default {
utils: myUtils,
categories: category,
meta: meta,
users: users
users: users,
signUps: signUps,
};

View file

@ -0,0 +1,30 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
const signUpPrefix = baseURL + "users/sign-ups";
const signUpURLs = {
all: `${signUpPrefix}`,
createToken: `${signUpPrefix}`,
deleteToken: token => `${signUpPrefix}/${token}`,
createUser: token => `${signUpPrefix}/${token}`,
};
export default {
async getAll() {
let response = await apiReq.get(signUpURLs.all);
return response.data;
},
async createToken(data) {
let response = await apiReq.post(signUpURLs.createToken, data);
return response.data;
},
async deleteToken(token) {
let response = await apiReq.delete(signUpURLs.deleteToken(token));
return response.data;
},
async createUser(token, data) {
let response = await apiReq.post(signUpURLs.createUser(token), data);
return response.data;
},
};

View file

@ -7,6 +7,7 @@ const authURLs = {
token: `${authPrefix}/token`,
};
const usersURLs = {
users: `${userPrefix}`,
self: `${userPrefix}/self`,

View file

@ -0,0 +1,252 @@
<template>
<v-card outlined>
<Confirmation
ref="deleteUserDialog"
title="Confirm User Deletion"
:message="
`Are you sure you want to delete the user <b>${activeName} ID: ${activeId}<b/>`
"
icon="mdi-alert"
@confirm="deleteUser"
:width="450"
@close="closeDelete"
/>
<v-toolbar flat>
<v-icon large color="accent" class="mr-1">
mdi-account-group
</v-icon>
<v-toolbar-title class="headine">
User Groups
</v-toolbar-title>
<v-spacer> </v-spacer>
<v-dialog v-model="dialog" max-width="600px">
<template v-slot:activator="{ on, attrs }">
<v-btn small color="success" dark v-bind="attrs" v-on="on">
Create Group
</v-btn>
</template>
<v-card>
<v-app-bar dark dense color="primary">
<v-icon left>
mdi-account
</v-icon>
<v-toolbar-title class="headline">
{{ formTitle }}
</v-toolbar-title>
<v-spacer></v-spacer>
<v-toolbar-title class="headline">
User ID: {{ editedItem.id }}
</v-toolbar-title>
</v-app-bar>
<v-card-text>
<v-form ref="newUser">
<v-row>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="editedItem.fullName"
label="Full Name"
:rules="[existsRule]"
validate-on-blur
></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="editedItem.email"
label="Email"
:rules="[existsRule, emailRule]"
validate-on-blur
></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="editedItem.family"
label="Family Group"
></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="6" v-if="showPassword">
<v-text-field
v-model="editedItem.password"
label="User Password"
:rules="[existsRule, minRule]"
></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-form>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="grey" text @click="close">
Cancel
</v-btn>
<v-btn color="primary" @click="save">
Save
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
<v-divider></v-divider>
<v-card-text>
<v-data-table :headers="headers" :items="users" sort-by="calories">
<template v-slot:item.actions="{ item }">
<v-btn class="mr-1" small color="error" @click="deleteItem(item)">
<v-icon small left>
mdi-delete
</v-icon>
Delete
</v-btn>
<v-btn small color="success" @click="editItem(item)">
<v-icon small left class="mr-2">
mdi-pencil
</v-icon>
Edit
</v-btn>
</template>
<template v-slot:item.admin="{ item }">
{{ item.admin ? "Admin" : "User" }}
</template>
<template v-slot:no-data>
<v-btn color="primary" @click="initialize">
Reset
</v-btn>
</template>
</v-data-table>
</v-card-text>
</v-card>
</template>
<script>
import Confirmation from "@/components/UI/Confirmation";
import api from "@/api";
import { validators } from "@/mixins/validators";
export default {
components: { Confirmation },
mixins: [validators],
data: () => ({
dialog: false,
activeId: null,
activeName: null,
headers: [
{
text: "User ID",
align: "start",
sortable: false,
value: "id",
},
{ text: "Full Name", value: "fullName" },
{ text: "Email", value: "email" },
{ text: "Family", value: "family" },
{ text: "Admin", value: "admin" },
{ text: "", value: "actions", sortable: false, align: "center" },
],
users: [],
editedIndex: -1,
editedItem: {
id: 0,
fullName: "",
password: "",
email: "",
family: "",
admin: false,
},
defaultItem: {
id: 0,
fullName: "",
password: "",
email: "",
family: "",
admin: false,
},
}),
computed: {
formTitle() {
return this.editedIndex === -1 ? "New User" : "Edit User";
},
showPassword() {
return this.editedIndex === -1 ? true : false;
},
},
watch: {
dialog(val) {
val || this.close();
},
dialogDelete(val) {
val || this.closeDelete();
},
},
created() {
this.initialize();
},
methods: {
async initialize() {
this.users = await api.users.allUsers();
},
async deleteUser() {
await api.users.delete(this.activeId);
this.initialize();
},
editItem(item) {
this.editedIndex = this.users.indexOf(item);
this.editedItem = Object.assign({}, item);
this.dialog = true;
},
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();
},
deleteItemConfirm() {
this.users.splice(this.editedIndex, 1);
this.closeDelete();
},
close() {
this.dialog = false;
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem);
this.editedIndex = -1;
});
},
closeDelete() {
this.dialogDelete = false;
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem);
this.editedIndex = -1;
});
},
async save() {
if (this.editedIndex > -1) {
api.users.update(this.editedItem);
this.close();
} else if (this.$refs.newUser.validate()) {
api.users.create(this.editedItem);
this.close();
}
await this.initialize();
},
},
};
</script>
<style>
</style>

View file

@ -0,0 +1,218 @@
<template>
<v-card outlined>
<Confirmation
ref="deleteUserDialog"
title="Confirm User Deletion"
:message="`Are you sure you want to delete the link <b>${activeName}<b/>`"
icon="mdi-alert"
@confirm="deleteUser"
:width="450"
@close="closeDelete"
/>
<v-toolbar flat>
<v-icon large color="accent" class="mr-1">
mdi-link-variant
</v-icon>
<v-toolbar-title class="headine">
Sign Up Links
</v-toolbar-title>
<v-spacer> </v-spacer>
<v-dialog v-model="dialog" max-width="600px">
<template v-slot:activator="{ on, attrs }">
<v-btn small color="success" dark v-bind="attrs" v-on="on">
Create Link
</v-btn>
</template>
<v-card>
<v-app-bar dark dense color="primary">
<v-icon left>
mdi-account
</v-icon>
<v-toolbar-title class="headline">
Create Link
</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
<v-card-text>
<v-form ref="newUser">
<v-text-field
v-model="editedItem.name"
label="Link Name"
:rules="[existsRule]"
validate-on-blur
></v-text-field>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="grey" text @click="close">
Cancel
</v-btn>
<v-btn color="primary" @click="save">
Save
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
<v-divider></v-divider>
<v-card-text>
<v-data-table :headers="headers" :items="links" sort-by="calories">
<template v-slot:item.token="{ item }">
{{ `${baseURL}/user/sign-up/${item.token}` }}
<v-btn
icon
class="mr-1"
small
color="accent"
@click="updateClipboard(`${baseURL}/user/sign-up/${item.token}`)"
>
<v-icon>
mdi-content-copy
</v-icon>
</v-btn>
</template>
<template v-slot:item.actions="{ item }">
<v-btn class="mr-1" small color="error" @click="deleteItem(item)">
<v-icon small left>
mdi-delete
</v-icon>
Delete
</v-btn>
</template>
</v-data-table>
</v-card-text>
</v-card>
</template>
<script>
import Confirmation from "@/components/UI/Confirmation";
import api from "@/api";
import { validators } from "@/mixins/validators";
export default {
components: { Confirmation },
mixins: [validators],
data: () => ({
dialog: false,
activeId: null,
activeName: null,
headers: [
{
text: "Link ID",
align: "start",
sortable: false,
value: "id",
},
{ text: "Name", value: "name" },
{ text: "Token", value: "token" },
{ text: "", value: "actions", sortable: false, align: "center" },
],
links: [],
editedIndex: -1,
editedItem: {
name: "",
token: "",
id: 0,
},
defaultItem: {
name: "",
token: "",
id: 0,
},
}),
computed: {
baseURL() {
return window.location.origin;
},
},
watch: {
dialog(val) {
val || this.close();
},
dialogDelete(val) {
val || this.closeDelete();
},
},
created() {
this.initialize();
},
methods: {
updateClipboard(newClip) {
navigator.clipboard.writeText(newClip).then(
function() {
console.log("Copied", newClip);
},
function() {
console.log("Copy Failed", newClip);
}
);
},
async initialize() {
this.links = await api.signUps.getAll();
},
async deleteUser() {
await api.signUps.deleteToken(this.activeId);
this.initialize();
},
editItem(item) {
this.editedIndex = this.links.indexOf(item);
this.editedItem = Object.assign({}, item);
this.dialog = true;
},
deleteItem(item) {
this.activeId = item.token;
this.activeName = item.name;
this.editedIndex = this.links.indexOf(item);
this.editedItem = Object.assign({}, item);
this.$refs.deleteUserDialog.open();
},
deleteItemConfirm() {
this.links.splice(this.editedIndex, 1);
this.closeDelete();
},
close() {
this.dialog = false;
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem);
this.editedIndex = -1;
});
},
closeDelete() {
this.dialogDelete = false;
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem);
this.editedIndex = -1;
});
},
async save() {
if (this.editedIndex > -1) {
api.links.update(this.editedItem);
this.close();
} else if (this.$refs.newUser.validate()) {
api.signUps.createToken({ name: this.editedItem.name });
this.close();
}
await this.initialize();
},
},
};
</script>
<style>
</style>

View file

@ -0,0 +1,252 @@
<template>
<v-card outlined>
<Confirmation
ref="deleteUserDialog"
title="Confirm User Deletion"
:message="
`Are you sure you want to delete the user <b>${activeName} ID: ${activeId}<b/>`
"
icon="mdi-alert"
@confirm="deleteUser"
:width="450"
@close="closeDelete"
/>
<v-toolbar flat>
<v-icon large color="accent" class="mr-1">
mdi-account
</v-icon>
<v-toolbar-title class="headine">
Users
</v-toolbar-title>
<v-spacer> </v-spacer>
<v-dialog v-model="dialog" max-width="600px">
<template v-slot:activator="{ on, attrs }">
<v-btn small color="success" dark v-bind="attrs" v-on="on">
Create User
</v-btn>
</template>
<v-card>
<v-app-bar dark dense color="primary">
<v-icon left>
mdi-account
</v-icon>
<v-toolbar-title class="headline">
{{ formTitle }}
</v-toolbar-title>
<v-spacer></v-spacer>
<v-toolbar-title class="headline">
User ID: {{ editedItem.id }}
</v-toolbar-title>
</v-app-bar>
<v-card-text>
<v-form ref="newUser">
<v-row>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="editedItem.fullName"
label="Full Name"
:rules="[existsRule]"
validate-on-blur
></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="editedItem.email"
label="Email"
:rules="[existsRule, emailRule]"
validate-on-blur
></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="editedItem.family"
label="Family Group"
></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="6" v-if="showPassword">
<v-text-field
v-model="editedItem.password"
label="User Password"
:rules="[existsRule, minRule]"
></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-form>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="grey" text @click="close">
Cancel
</v-btn>
<v-btn color="primary" @click="save">
Save
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
<v-divider></v-divider>
<v-card-text>
<v-data-table :headers="headers" :items="users" sort-by="calories">
<template v-slot:item.actions="{ item }">
<v-btn class="mr-1" small color="error" @click="deleteItem(item)">
<v-icon small left>
mdi-delete
</v-icon>
Delete
</v-btn>
<v-btn small color="success" @click="editItem(item)">
<v-icon small left class="mr-2">
mdi-pencil
</v-icon>
Edit
</v-btn>
</template>
<template v-slot:item.admin="{ item }">
{{ item.admin ? "Admin" : "User" }}
</template>
<template v-slot:no-data>
<v-btn color="primary" @click="initialize">
Reset
</v-btn>
</template>
</v-data-table>
</v-card-text>
</v-card>
</template>
<script>
import Confirmation from "@/components/UI/Confirmation";
import api from "@/api";
import { validators } from "@/mixins/validators";
export default {
components: { Confirmation },
mixins: [validators],
data: () => ({
dialog: false,
activeId: null,
activeName: null,
headers: [
{
text: "User ID",
align: "start",
sortable: false,
value: "id",
},
{ text: "Full Name", value: "fullName" },
{ text: "Email", value: "email" },
{ text: "Family", value: "family" },
{ text: "Admin", value: "admin" },
{ text: "", value: "actions", sortable: false, align: "center" },
],
users: [],
editedIndex: -1,
editedItem: {
id: 0,
fullName: "",
password: "",
email: "",
family: "",
admin: false,
},
defaultItem: {
id: 0,
fullName: "",
password: "",
email: "",
family: "",
admin: false,
},
}),
computed: {
formTitle() {
return this.editedIndex === -1 ? "New User" : "Edit User";
},
showPassword() {
return this.editedIndex === -1 ? true : false;
},
},
watch: {
dialog(val) {
val || this.close();
},
dialogDelete(val) {
val || this.closeDelete();
},
},
created() {
this.initialize();
},
methods: {
async initialize() {
this.users = await api.users.allUsers();
},
async deleteUser() {
await api.users.delete(this.activeId);
this.initialize();
},
editItem(item) {
this.editedIndex = this.users.indexOf(item);
this.editedItem = Object.assign({}, item);
this.dialog = true;
},
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();
},
deleteItemConfirm() {
this.users.splice(this.editedIndex, 1);
this.closeDelete();
},
close() {
this.dialog = false;
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem);
this.editedIndex = -1;
});
},
closeDelete() {
this.dialogDelete = false;
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem);
this.editedIndex = -1;
});
},
async save() {
if (this.editedIndex > -1) {
api.users.update(this.editedItem);
this.close();
} else if (this.$refs.newUser.validate()) {
api.users.create(this.editedItem);
this.close();
}
await this.initialize();
},
},
};
</script>
<style>
</style>

View file

@ -1,249 +1,56 @@
<template>
<v-data-table
:headers="headers"
:items="users"
sort-by="calories"
class="elevation-1"
>
<template v-slot:top>
<v-toolbar flat>
<v-toolbar-title>Mealie Users</v-toolbar-title>
<v-divider class="mx-4" inset vertical></v-divider>
<v-spacer></v-spacer>
<v-dialog v-model="dialog" max-width="600px">
<template v-slot:activator="{ on, attrs }">
<v-btn color="primary" dark class="mb-2" v-bind="attrs" v-on="on">
Create User
</v-btn>
</template>
<v-card>
<v-app-bar dark dense color="primary">
<v-icon left>
mdi-account
</v-icon>
<div>
<v-card flat>
<v-tabs
v-model="tab"
background-color="primary"
centered
dark
icons-and-text
>
<v-tabs-slider></v-tabs-slider>
<v-toolbar-title class="headline">
{{ formTitle }}
</v-toolbar-title>
<v-tab>
Users
<v-icon>mdi-account</v-icon>
</v-tab>
<v-spacer></v-spacer>
<v-toolbar-title class="headline">
User ID: {{ editedItem.id }}
</v-toolbar-title>
</v-app-bar>
<v-tab>
Sign-Up Links
<v-icon>mdi-account-plus-outline</v-icon>
</v-tab>
<v-card-text>
<v-form ref="newUser">
<v-row>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="editedItem.fullName"
label="Full Name"
:rules="[existsRule]"
validate-on-blur
></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="editedItem.email"
label="Email"
:rules="[existsRule, emailRule]"
validate-on-blur
></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="editedItem.family"
label="Family Group"
></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="6" v-if="showPassword">
<v-text-field
v-model="editedItem.password"
label="User Password"
:rules="[existsRule, minRule]"
></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-form>
</v-card-text>
<v-tab>
Groups
<v-icon>mdi-account-group</v-icon>
</v-tab>
</v-tabs>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="grey" text @click="close">
Cancel
</v-btn>
<v-btn color="primary" @click="save">
Save
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<Confirmation
ref="deleteUserDialog"
title="Confirm User Deletion"
:message="
`Are you sure you want to delete the user <b>${activeName} ID: ${activeId}<b/>`
"
icon="mdi-alert"
@confirm="deleteUser"
:width="450"
@close="closeDelete"
/>
</v-toolbar>
</template>
<template v-slot:item.actions="{ item }">
<v-btn class="mr-1" small color="error" @click="deleteItem(item)">
<v-icon small left>
mdi-delete
</v-icon>
Delete
</v-btn>
<v-btn small color="success" @click="editItem(item)">
<v-icon small left class="mr-2">
mdi-pencil
</v-icon>
Edit
</v-btn>
</template>
<template v-slot:item.admin="{ item }">
{{ item.admin ? "Admin" : "User" }}
</template>
<template v-slot:no-data>
<v-btn color="primary" @click="initialize">
Reset
</v-btn>
</template>
</v-data-table>
<v-tabs-items v-model="tab">
<v-tab-item>
<TheUserTable />
</v-tab-item>
<v-tab-item>
<TheSignUpTable />
</v-tab-item>
<v-tab-item>
<TheGroupTable />
</v-tab-item>
</v-tabs-items>
</v-card>
</div>
</template>
<script>
import Confirmation from "@/components/UI/Confirmation";
import api from "@/api";
import { validators } from "@/mixins/validators";
import TheUserTable from "@/components/Admin/ManageUsers/TheUserTable";
import TheGroupTable from "@/components/Admin/ManageUsers/TheGroupTable";
import TheSignUpTable from "@/components/Admin/ManageUsers/TheSignUpTable";
export default {
components: { Confirmation },
mixins: [validators],
data: () => ({
dialog: false,
activeId: null,
activeName: null,
headers: [
{
text: "User ID",
align: "start",
sortable: false,
value: "id",
},
{ text: "Full Name", value: "fullName" },
{ text: "Email", value: "email" },
{ text: "Family", value: "family" },
{ text: "Admin", value: "admin" },
{ text: "", value: "actions", sortable: false, align: "center" },
],
users: [],
editedIndex: -1,
editedItem: {
id: 0,
fullName: "",
password: "",
email: "",
family: "",
admin: false,
},
defaultItem: {
id: 0,
fullName: "",
password: "",
email: "",
family: "",
admin: false,
},
}),
computed: {
formTitle() {
return this.editedIndex === -1 ? "New User" : "Edit User";
},
showPassword() {
return this.editedIndex === -1 ? true : false;
},
},
watch: {
dialog(val) {
val || this.close();
},
dialogDelete(val) {
val || this.closeDelete();
},
},
created() {
this.initialize();
},
methods: {
async initialize() {
this.users = await api.users.allUsers();
},
async deleteUser() {
await api.users.delete(this.activeId);
this.initialize();
},
editItem(item) {
this.editedIndex = this.users.indexOf(item);
this.editedItem = Object.assign({}, item);
this.dialog = true;
},
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();
},
deleteItemConfirm() {
this.users.splice(this.editedIndex, 1);
this.closeDelete();
},
close() {
this.dialog = false;
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem);
this.editedIndex = -1;
});
},
closeDelete() {
this.dialogDelete = false;
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem);
this.editedIndex = -1;
});
},
async save() {
if (this.editedIndex > -1) {
api.users.update(this.editedItem);
this.close();
} else if (this.$refs.newUser.validate()) {
api.users.create(this.editedItem);
this.close();
}
await this.initialize();
},
components: { TheUserTable, TheGroupTable, TheSignUpTable },
data() {
return {
tab: 0,
};
},
};
</script>

View file

@ -4,6 +4,7 @@ from db.db_base import BaseDocument
from db.models.mealplan import MealPlanModel
from db.models.recipe import Category, RecipeModel, Tag
from db.models.settings import SiteSettingsModel
from db.models.sign_up import SignUp
from db.models.theme import SiteThemeModel
from db.models.users import User
@ -70,6 +71,11 @@ class _Users(BaseDocument):
return return_data
class _SignUps(BaseDocument):
def __init__(self) -> None:
self.primary_key = "token"
self.sql_model = SignUp
class Database:
def __init__(self) -> None:
@ -80,6 +86,7 @@ class Database:
self.categories = _Categories()
self.tags = _Tags()
self.users = _Users()
self.sign_ups = _SignUps()
db = Database()

View file

@ -3,3 +3,4 @@ from db.models.recipe import *
from db.models.settings import *
from db.models.theme import *
from db.models.users import *
from db.models.sign_up import *

View file

@ -0,0 +1,25 @@
from db.models.model_base import BaseMixins, SqlAlchemyBase
from sqlalchemy import Column, Integer, String
class SignUp(SqlAlchemyBase, BaseMixins):
__tablename__ = "sign_ups"
id = Column(Integer, primary_key=True)
token = Column(String, nullable=False, index=True)
name = Column(String, index=True)
def __init__(
self,
session,
token,
name,
) -> None:
self.token = token
self.name = name
def dict(self):
return {
"id": self.id,
"name": self.name,
"token": self.token,
}

View file

@ -21,3 +21,4 @@ def query_user(user_email: str, session: Session = None) -> UserInDB:
user = db.users.get(session, user_email, "email")
session.close()
return UserInDB(**user)

View file

@ -0,0 +1,81 @@
import uuid
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 schema.sign_up import SignUpIn, SignUpOut, SignUpToken
from schema.snackbar import SnackResponse
from schema.user import UserIn, UserInDB
from sqlalchemy.orm.session import Session
router = APIRouter(prefix="/api/users/sign-ups", tags=["User Signup"])
@router.get("", response_model=list[SignUpOut])
async def get_all_open_sign_ups(
current_user=Depends(manager),
session: Session = Depends(generate_session),
):
""" Returns a list of open sign up links """
all_sign_ups = db.sign_ups.get_all(session)
return all_sign_ups
@router.post("", response_model=SignUpToken)
async def create_user_sign_up_key(
key_data: SignUpIn,
current_user: UserInDB = Depends(manager),
session: Session = Depends(generate_session),
):
""" Generates a Random Token that a new user can sign up with """
if current_user.admin:
sign_up = {"token": str(uuid.uuid1().hex), "name": key_data.name}
db_entry = db.sign_ups.create(session, sign_up)
return db_entry
else:
return {"details": "not authorized"}
@router.post("/{token}")
async def create_user_with_token(
token: str,
new_user: UserIn,
session: Session = Depends(generate_session),
):
""" Creates a user with a valid sign up token """
# Validate Token
db_entry = db.sign_ups.get(session, token, limit=1)
if not db_entry:
return {"details": "invalid token"}
# Create User
new_user.password = get_password_hash(new_user.password)
data = db.users.create(session, new_user.dict())
# DeleteToken
db.sign_ups.delete(session, token)
# Respond
return SnackResponse.success(f"User Created: {new_user.full_name}", data)
@router.delete("/{token}")
async def delete_token(
token: str,
current_user: UserInDB = Depends(manager),
session: Session = Depends(generate_session),
):
""" Removed a token from the database """
if current_user.admin:
db.sign_ups.delete(session, token)
return SnackResponse.error("Sign Up Token Deleted")
else:
return {"details", "not authorized"}

View file

@ -1,7 +1,8 @@
from fastapi import APIRouter
from routes.users import auth, crud
from routes.users import auth, crud, sign_up
router = APIRouter()
router.include_router(sign_up.router)
router.include_router(auth.router)
router.include_router(crud.router)

13
mealie/schema/sign_up.py Normal file
View file

@ -0,0 +1,13 @@
from fastapi_camelcase import CamelModel
class SignUpIn(CamelModel):
name: str
class SignUpToken(SignUpIn):
token: str
class SignUpOut(SignUpToken):
id: int