Add folders with password sharing and email invites

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-06 22:30:00 +03:00
parent a375ad330a
commit db2cef41bb
16 changed files with 1016 additions and 4 deletions
+89
View File
@@ -1,3 +1,4 @@
import uuid
from datetime import datetime, timezone
from flask_login import UserMixin
@@ -22,6 +23,7 @@ class User(UserMixin, db.Model):
)
photos = db.relationship("Photo", backref="owner", lazy="dynamic")
folders = db.relationship("Folder", backref="owner", lazy="dynamic")
def set_password(self, password):
self.password_hash = generate_password_hash(password)
@@ -43,6 +45,92 @@ class User(UserMixin, db.Model):
return int(result or 0)
class Folder(db.Model):
__tablename__ = "folders"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(120), nullable=False)
owner_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False, index=True)
share_token = db.Column(db.String(64), unique=True, nullable=False, index=True)
is_private = db.Column(db.Boolean, nullable=False, default=True)
password_hash = db.Column(db.String(256), nullable=True)
created_at = db.Column(
db.DateTime,
nullable=False,
default=lambda: datetime.now(timezone.utc),
)
photos = db.relationship("Photo", backref="folder", lazy="dynamic")
members = db.relationship("FolderMember", backref="folder", lazy="dynamic", cascade="all, delete-orphan")
invites = db.relationship("FolderInvite", backref="folder", lazy="dynamic", cascade="all, delete-orphan")
def __init__(self, **kwargs):
super().__init__(**kwargs)
if not self.share_token:
self.share_token = uuid.uuid4().hex
def set_access_password(self, password):
if password:
self.password_hash = generate_password_hash(password)
else:
self.password_hash = None
def check_access_password(self, password):
if not self.password_hash:
return True
return check_password_hash(self.password_hash, password)
@property
def has_password(self):
return bool(self.password_hash)
@property
def photo_count(self):
return self.photos.count()
def regenerate_share_token(self):
self.share_token = uuid.uuid4().hex
class FolderMember(db.Model):
__tablename__ = "folder_members"
id = db.Column(db.Integer, primary_key=True)
folder_id = db.Column(db.Integer, db.ForeignKey("folders.id"), nullable=False, index=True)
user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False, index=True)
role = db.Column(db.String(20), nullable=False, default="viewer")
added_at = db.Column(
db.DateTime,
nullable=False,
default=lambda: datetime.now(timezone.utc),
)
added_by_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True)
user = db.relationship("User", foreign_keys=[user_id])
added_by = db.relationship("User", foreign_keys=[added_by_id])
__table_args__ = (db.UniqueConstraint("folder_id", "user_id", name="uq_folder_member"),)
class FolderInvite(db.Model):
__tablename__ = "folder_invites"
id = db.Column(db.Integer, primary_key=True)
folder_id = db.Column(db.Integer, db.ForeignKey("folders.id"), nullable=False, index=True)
email = db.Column(db.String(120), nullable=False, index=True)
role = db.Column(db.String(20), nullable=False, default="viewer")
invited_by_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
created_at = db.Column(
db.DateTime,
nullable=False,
default=lambda: datetime.now(timezone.utc),
)
invited_by = db.relationship("User", foreign_keys=[invited_by_id])
__table_args__ = (db.UniqueConstraint("folder_id", "email", name="uq_folder_invite"),)
class Photo(db.Model):
__tablename__ = "photos"
@@ -52,6 +140,7 @@ class Photo(db.Model):
file_size = db.Column(db.Integer, nullable=False, default=0)
mime_type = db.Column(db.String(100), nullable=False, default="image/jpeg")
user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True, index=True)
folder_id = db.Column(db.Integer, db.ForeignKey("folders.id"), nullable=True, index=True)
created_at = db.Column(
db.DateTime,
nullable=False,