mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-12-28 13:05:26 -05:00
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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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="")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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")),
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user