Feature/user photo storage (#877)

* add default assets for user profile

* add recipe avatar

* change user_id to UUID

* add profile image upload

* setup image cache keys

* cleanup tests and add image tests

* purge user data on delete

* new user repository tests

* add user_id validator for int -> UUID conversion

* delete depreciated route

* force set content type

* refactor tests to use temp directory

* validate parent exists before createing

* set user_id to correct type

* update instruction id

* reset primary key on migration
This commit is contained in:
Hayden
2021-12-18 19:04:36 -09:00
committed by GitHub
parent a2f8f27193
commit ea7c4771ee
64 changed files with 433 additions and 181 deletions

View File

@@ -3,6 +3,7 @@ from __future__ import annotations
from typing import Any, Callable, Generic, TypeVar, Union
from uuid import UUID
from pydantic import UUID4
from sqlalchemy import func
from sqlalchemy.orm import load_only
from sqlalchemy.orm.session import Session
@@ -39,7 +40,7 @@ class AccessModel(Generic[T, D]):
def subscribe(self, func: Callable) -> None:
self.observers.append(func)
def by_user(self, user_id: int) -> AccessModel:
def by_user(self, user_id: UUID4) -> AccessModel:
self.limit_by_user = True
self.user_id = user_id
return self

View File

@@ -1,5 +1,8 @@
from mealie.db.models.users import User
from mealie.schema.user.user import PrivateUser
import random
import shutil
from mealie.assets import users as users_assets
from mealie.schema.user.user import PrivateUser, User
from ._access_model import AccessModel
@@ -11,3 +14,23 @@ class UserDataAccessModel(AccessModel[PrivateUser, User]):
self.session.commit()
return self.schema.from_orm(entry)
def create(self, user: PrivateUser):
new_user = super().create(user)
# Select Random Image
all_images = [
users_assets.img_random_1,
users_assets.img_random_2,
users_assets.img_random_3,
]
random_image = random.choice(all_images)
shutil.copy(random_image, new_user.directory() / "profile.webp")
return new_user
def delete(self, id: str) -> User:
entry = super().delete(id)
# Delete the user's directory
shutil.rmtree(PrivateUser.get_directory(id))
return entry

View File

@@ -39,7 +39,9 @@ def main():
db = get_database(session)
try:
init_user = db.users.get("1", "id")
init_user = db.users.get_all()
if not init_user:
raise Exception("No users found in database")
except Exception:
init_db(db)
return

View File

@@ -13,6 +13,10 @@ class GUID(TypeDecorator):
impl = CHAR
cache_ok = True
@staticmethod
def generate():
return uuid.uuid4()
def load_dialect_impl(self, dialect):
if dialect.name == "postgresql":
return dialect.type_descriptor(UUID())

View File

@@ -1,5 +1,3 @@
from uuid import uuid4
from sqlalchemy import Column, ForeignKey, String, orm
from .._model_base import BaseMixins, SqlAlchemyBase
@@ -8,7 +6,7 @@ from .._model_utils import GUID, auto_init
class GroupDataExportsModel(SqlAlchemyBase, BaseMixins):
__tablename__ = "group_data_exports"
id = Column(GUID, primary_key=True, default=uuid4)
id = Column(GUID, primary_key=True, default=GUID.generate)
group = orm.relationship("Group", back_populates="data_exports", single_parent=True)
group_id = Column(GUID, ForeignKey("groups.id"), index=True)

View File

@@ -1,5 +1,4 @@
from datetime import datetime
from uuid import uuid4
from sqlalchemy import Column, ForeignKey, orm
from sqlalchemy.sql.sqltypes import Boolean, DateTime, String
@@ -12,7 +11,7 @@ from .._model_utils.guid import GUID
class ReportEntryModel(SqlAlchemyBase, BaseMixins):
__tablename__ = "report_entries"
id = Column(GUID, primary_key=True, default=uuid4)
id = Column(GUID, primary_key=True, default=GUID.generate)
success = Column(Boolean, default=False)
message = Column(String, nullable=True)
@@ -29,7 +28,7 @@ class ReportEntryModel(SqlAlchemyBase, BaseMixins):
class ReportModel(SqlAlchemyBase, BaseMixins):
__tablename__ = "group_reports"
id = Column(GUID, primary_key=True, default=uuid4)
id = Column(GUID, primary_key=True, default=GUID.generate)
name = Column(String, nullable=False)
status = Column(String, nullable=False)

View File

@@ -1,5 +1,3 @@
from uuid import uuid4
from sqlalchemy import Column, ForeignKey, Integer, String, orm
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
@@ -9,7 +7,7 @@ from mealie.db.models._model_utils.guid import GUID
class RecipeComment(SqlAlchemyBase, BaseMixins):
__tablename__ = "recipe_comments"
id = Column(GUID, primary_key=True, default=uuid4)
id = Column(GUID, primary_key=True, default=GUID.generate)
text = Column(String)
# Recipe Link
@@ -17,7 +15,7 @@ class RecipeComment(SqlAlchemyBase, BaseMixins):
recipe = orm.relationship("RecipeModel", back_populates="comments")
# User Link
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
user_id = Column(GUID, ForeignKey("users.id"), nullable=False)
user = orm.relationship("User", back_populates="comments", single_parent=True, foreign_keys=[user_id])
@auto_init()

View File

@@ -1,5 +1,3 @@
from uuid import uuid4
from sqlalchemy import Column, ForeignKey, Integer, String, orm
from .._model_base import BaseMixins, SqlAlchemyBase
@@ -9,7 +7,7 @@ from .._model_utils.guid import GUID
class RecipeIngredientRefLink(SqlAlchemyBase, BaseMixins):
__tablename__ = "recipe_ingredient_ref_link"
instruction_id = Column(Integer, ForeignKey("recipe_instructions.id"))
instruction_id = Column(GUID, ForeignKey("recipe_instructions.id"))
reference_id = Column(GUID)
@auto_init()
@@ -19,7 +17,7 @@ class RecipeIngredientRefLink(SqlAlchemyBase, BaseMixins):
class RecipeInstruction(SqlAlchemyBase):
__tablename__ = "recipe_instructions"
id = Column(GUID, primary_key=True, default=uuid4)
id = Column(GUID, primary_key=True, default=GUID.generate)
parent_id = Column(Integer, ForeignKey("recipes.id"))
position = Column(Integer)
type = Column(String, default="")

View File

@@ -49,7 +49,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
group_id = sa.Column(GUID, sa.ForeignKey("groups.id"))
group = orm.relationship("Group", back_populates="recipes", foreign_keys=[group_id])
user_id = sa.Column(sa.Integer, sa.ForeignKey("users.id"))
user_id = sa.Column(GUID, sa.ForeignKey("users.id"))
user = orm.relationship("User", uselist=False, foreign_keys=[user_id])
meal_entries = orm.relationship("GroupMealPlan", back_populates="recipe", cascade="all, delete-orphan")

View File

@@ -1,12 +1,13 @@
from sqlalchemy import Column, ForeignKey, Integer, String, orm
from sqlalchemy import Column, ForeignKey, String, orm
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_utils import GUID
class PasswordResetModel(SqlAlchemyBase, BaseMixins):
__tablename__ = "password_reset_tokens"
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
user_id = Column(GUID, ForeignKey("users.id"), nullable=False)
user = orm.relationship("User", back_populates="password_reset_tokens", uselist=False)
token = Column(String(64), unique=True, nullable=False)

View File

@@ -1,10 +1,11 @@
from sqlalchemy import Column, ForeignKey, Integer, Table
from .._model_base import SqlAlchemyBase
from .._model_utils import GUID
users_to_favorites = Table(
"users_to_favorites",
SqlAlchemyBase.metadata,
Column("user_id", Integer, ForeignKey("users.id")),
Column("user_id", GUID, ForeignKey("users.id")),
Column("recipe_id", Integer, ForeignKey("recipes.id")),
)

View File

@@ -11,19 +11,21 @@ from .user_to_favorite import users_to_favorites
class LongLiveToken(SqlAlchemyBase, BaseMixins):
__tablename__ = "long_live_tokens"
parent_id = Column(Integer, ForeignKey("users.id"))
name = Column(String, nullable=False)
token = Column(String, nullable=False)
user_id = Column(GUID, ForeignKey("users.id"))
user = orm.relationship("User")
def __init__(self, session, name, token, parent_id) -> None:
def __init__(self, name, token, user_id, **_) -> None:
self.name = name
self.token = token
self.user = User.get_ref(session, parent_id)
self.user_id = user_id
class User(SqlAlchemyBase, BaseMixins):
__tablename__ = "users"
id = Column(GUID, primary_key=True, default=GUID.generate)
full_name = Column(String, index=True)
username = Column(String, index=True, unique=True)
email = Column(String, unique=True, index=True)
@@ -34,6 +36,8 @@ class User(SqlAlchemyBase, BaseMixins):
group_id = Column(GUID, ForeignKey("groups.id"))
group = orm.relationship("Group", back_populates="users")
cache_key = Column(String, default="1234")
# Group Permissions
can_manage = Column(Boolean, default=False)
can_invite = Column(Boolean, default=False)