diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 3c6f48bcc..908ef3e0a 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -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 */ diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index 0082dba12..0caf3b76d 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -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, }; diff --git a/frontend/src/api/signUps.js b/frontend/src/api/signUps.js new file mode 100644 index 000000000..1948f4496 --- /dev/null +++ b/frontend/src/api/signUps.js @@ -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; + }, +}; diff --git a/frontend/src/api/users.js b/frontend/src/api/users.js index 90123e733..14881e33c 100644 --- a/frontend/src/api/users.js +++ b/frontend/src/api/users.js @@ -7,6 +7,7 @@ const authURLs = { token: `${authPrefix}/token`, }; + const usersURLs = { users: `${userPrefix}`, self: `${userPrefix}/self`, diff --git a/frontend/src/components/Admin/ManageUsers/TheGroupTable.vue b/frontend/src/components/Admin/ManageUsers/TheGroupTable.vue new file mode 100644 index 000000000..e2b616dd6 --- /dev/null +++ b/frontend/src/components/Admin/ManageUsers/TheGroupTable.vue @@ -0,0 +1,252 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/Admin/ManageUsers/TheSignUpTable.vue b/frontend/src/components/Admin/ManageUsers/TheSignUpTable.vue new file mode 100644 index 000000000..adac5571f --- /dev/null +++ b/frontend/src/components/Admin/ManageUsers/TheSignUpTable.vue @@ -0,0 +1,218 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/Admin/ManageUsers/TheUserTable.vue b/frontend/src/components/Admin/ManageUsers/TheUserTable.vue new file mode 100644 index 000000000..48a8248a6 --- /dev/null +++ b/frontend/src/components/Admin/ManageUsers/TheUserTable.vue @@ -0,0 +1,252 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/pages/Admin/ManageUsers/index.vue b/frontend/src/pages/Admin/ManageUsers/index.vue index 484f72965..e70bd3810 100644 --- a/frontend/src/pages/Admin/ManageUsers/index.vue +++ b/frontend/src/pages/Admin/ManageUsers/index.vue @@ -1,249 +1,56 @@ diff --git a/mealie/db/database.py b/mealie/db/database.py index 4c437bc0e..37545e28b 100644 --- a/mealie/db/database.py +++ b/mealie/db/database.py @@ -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() diff --git a/mealie/db/models/_all_models.py b/mealie/db/models/_all_models.py index e0deb2401..a47c4d820 100644 --- a/mealie/db/models/_all_models.py +++ b/mealie/db/models/_all_models.py @@ -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 * diff --git a/mealie/db/models/sign_up.py b/mealie/db/models/sign_up.py new file mode 100644 index 000000000..11abd09f9 --- /dev/null +++ b/mealie/db/models/sign_up.py @@ -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, + } diff --git a/mealie/routes/deps.py b/mealie/routes/deps.py index 422efc17a..8390d4153 100644 --- a/mealie/routes/deps.py +++ b/mealie/routes/deps.py @@ -20,4 +20,5 @@ def query_user(user_email: str, session: Session = None) -> UserInDB: session = session if session else create_session() user = db.users.get(session, user_email, "email") session.close() - return UserInDB(**user) \ No newline at end of file + return UserInDB(**user) + diff --git a/mealie/routes/users/sign_up.py b/mealie/routes/users/sign_up.py new file mode 100644 index 000000000..ade9b32c1 --- /dev/null +++ b/mealie/routes/users/sign_up.py @@ -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"} diff --git a/mealie/routes/users/users.py b/mealie/routes/users/users.py index 309f1d9d3..be5de5aad 100644 --- a/mealie/routes/users/users.py +++ b/mealie/routes/users/users.py @@ -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) diff --git a/mealie/schema/sign_up.py b/mealie/schema/sign_up.py new file mode 100644 index 000000000..4c1087f18 --- /dev/null +++ b/mealie/schema/sign_up.py @@ -0,0 +1,13 @@ +from fastapi_camelcase import CamelModel + + +class SignUpIn(CamelModel): + name: str + + +class SignUpToken(SignUpIn): + token: str + + +class SignUpOut(SignUpToken): + id: int