from flask_sqlalchemy import SQLAlchemy from flask_login import UserMixin from werkzeug.security import generate_password_hash, check_password_hash from datetime import datetime from sqlalchemy.orm import relationship from extensions import db from enum import Enum # Association table for room members room_members = db.Table('room_members', db.Column('room_id', db.Integer, db.ForeignKey('room.id'), primary_key=True), db.Column('user_id', db.Integer, db.ForeignKey('user.id'), primary_key=True) ) # Association table for conversation members conversation_members = db.Table('conversation_members', db.Column('conversation_id', db.Integer, db.ForeignKey('conversation.id'), primary_key=True), db.Column('user_id', db.Integer, db.ForeignKey('user.id'), primary_key=True) ) class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(150), unique=True, nullable=False) last_name = db.Column(db.String(150), nullable=False, default='(You)') email = db.Column(db.String(150), unique=True, nullable=False) password_hash = db.Column(db.String(256)) is_admin = db.Column(db.Boolean, default=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) phone = db.Column(db.String(20)) company = db.Column(db.String(100)) position = db.Column(db.String(100)) notes = db.Column(db.Text) is_active = db.Column(db.Boolean, default=True) profile_picture = db.Column(db.String(255)) preferred_view = db.Column(db.String(10), default='grid', nullable=False) # 'grid' or 'list' room_permissions = relationship('RoomMemberPermission', back_populates='user') def set_password(self, password): self.password_hash = generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) def __repr__(self): return f'' class Room(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) description = db.Column(db.Text) created_at = db.Column(db.DateTime, default=datetime.utcnow) created_by = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) # Relationships creator = db.relationship('User', backref='created_rooms', foreign_keys=[created_by]) members = db.relationship('User', secondary=room_members, backref=db.backref('rooms', lazy='dynamic')) member_permissions = relationship('RoomMemberPermission', back_populates='room', cascade='all, delete-orphan') files = db.relationship('RoomFile', back_populates='room', cascade='all, delete-orphan') def __repr__(self): return f'' # Association table for room members with permissions class RoomMemberPermission(db.Model): __tablename__ = 'room_member_permissions' room_id = db.Column(db.Integer, db.ForeignKey('room.id'), primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True) can_view = db.Column(db.Boolean, default=True, nullable=False) can_download = db.Column(db.Boolean, default=False, nullable=False) can_upload = db.Column(db.Boolean, default=False, nullable=False) can_delete = db.Column(db.Boolean, default=False, nullable=False) can_rename = db.Column(db.Boolean, default=False, nullable=False) can_move = db.Column(db.Boolean, default=False, nullable=False) can_share = db.Column(db.Boolean, default=False, nullable=False) # Relationships user = relationship('User', back_populates='room_permissions') room = relationship('Room', back_populates='member_permissions') class RoomFile(db.Model): __tablename__ = 'room_file' id = db.Column(db.Integer, primary_key=True) room_id = db.Column(db.Integer, db.ForeignKey('room.id'), nullable=False) name = db.Column(db.String(255), nullable=False) path = db.Column(db.String(255), nullable=False, default='') type = db.Column(db.String(10), nullable=False) # 'file' or 'folder' size = db.Column(db.Integer) # in bytes, null for folders modified = db.Column(db.Float) # timestamp uploaded_by = db.Column(db.Integer, db.ForeignKey('user.id')) uploaded_at = db.Column(db.DateTime, default=datetime.utcnow) deleted = db.Column(db.Boolean, default=False) # New field for deleted status deleted_by = db.Column(db.Integer, db.ForeignKey('user.id')) # New field for tracking who deleted the file deleted_at = db.Column(db.DateTime) # New field for tracking when the file was deleted uploader = db.relationship('User', backref='uploaded_files', foreign_keys=[uploaded_by]) deleter = db.relationship('User', backref='deleted_room_files', foreign_keys=[deleted_by]) room = db.relationship('Room', back_populates='files') starred_by = db.relationship('User', secondary='user_starred_file', backref='starred_files') def __repr__(self): return f'' class UserStarredFile(db.Model): __tablename__ = 'user_starred_file' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) file_id = db.Column(db.Integer, db.ForeignKey('room_file.id'), nullable=False) starred_at = db.Column(db.DateTime, default=datetime.utcnow) # Add unique constraint to prevent duplicate stars __table_args__ = ( db.UniqueConstraint('user_id', 'file_id', name='unique_user_file_star'), ) def __repr__(self): return f'' class TrashedFile(db.Model): __tablename__ = 'trashed_file' id = db.Column(db.Integer, primary_key=True) room_id = db.Column(db.Integer, db.ForeignKey('room.id'), nullable=False) name = db.Column(db.String(255), nullable=False) original_path = db.Column(db.String(255), nullable=False, default='') type = db.Column(db.String(10), nullable=False) # 'file' or 'folder' size = db.Column(db.Integer) # in bytes, null for folders modified = db.Column(db.Float) # timestamp uploaded_by = db.Column(db.Integer, db.ForeignKey('user.id')) uploaded_at = db.Column(db.DateTime, default=datetime.utcnow) deleted_by = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) deleted_at = db.Column(db.DateTime, default=datetime.utcnow) room = db.relationship('Room', backref='trashed_files') uploader = db.relationship('User', foreign_keys=[uploaded_by], backref='uploaded_trashed_files') deleter = db.relationship('User', foreign_keys=[deleted_by], backref='deleted_trashed_files') # Changed from deleted_files to deleted_trashed_files def __repr__(self): return f'' class SiteSettings(db.Model): id = db.Column(db.Integer, primary_key=True) primary_color = db.Column(db.String(7), default='#16767b') # Default from colors.css secondary_color = db.Column(db.String(7), default='#741b5f') # Default from colors.css company_name = db.Column(db.String(100)) company_logo = db.Column(db.String(255)) # Store the filename of the logo company_website = db.Column(db.String(200)) company_email = db.Column(db.String(100)) company_phone = db.Column(db.String(20)) company_address = db.Column(db.String(200)) company_city = db.Column(db.String(100)) company_state = db.Column(db.String(100)) company_zip = db.Column(db.String(20)) company_country = db.Column(db.String(100)) company_description = db.Column(db.Text) company_industry = db.Column(db.String(100)) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) @classmethod def get_settings(cls): settings = cls.query.first() if not settings: settings = cls() db.session.add(settings) db.session.commit() return settings class Conversation(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) description = db.Column(db.Text) created_at = db.Column(db.DateTime, default=datetime.utcnow) created_by = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) # Relationships creator = db.relationship('User', backref='created_conversations', foreign_keys=[created_by]) members = db.relationship('User', secondary=conversation_members, backref=db.backref('conversations', lazy='dynamic')) messages = db.relationship('Message', back_populates='conversation', cascade='all, delete-orphan') def __repr__(self): return f'' class Message(db.Model): id = db.Column(db.Integer, primary_key=True) content = db.Column(db.Text, nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) conversation_id = db.Column(db.Integer, db.ForeignKey('conversation.id'), nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) # Relationships conversation = db.relationship('Conversation', back_populates='messages') user = db.relationship('User', backref='messages') attachments = db.relationship('MessageAttachment', back_populates='message', cascade='all, delete-orphan') def __repr__(self): return f'' class MessageAttachment(db.Model): id = db.Column(db.Integer, primary_key=True) message_id = db.Column(db.Integer, db.ForeignKey('message.id'), nullable=False) name = db.Column(db.String(255), nullable=False) path = db.Column(db.String(512), nullable=False) type = db.Column(db.String(100)) size = db.Column(db.Integer) # Size in bytes created_at = db.Column(db.DateTime, default=datetime.utcnow) # Relationships message = db.relationship('Message', back_populates='attachments') def __repr__(self): return f'' class EventType(Enum): # User events USER_LOGIN = 'user_login' USER_LOGOUT = 'user_logout' USER_CREATE = 'user_create' USER_UPDATE = 'user_update' USER_DELETE = 'user_delete' # Room events ROOM_CREATE = 'room_create' ROOM_UPDATE = 'room_update' ROOM_DELETE = 'room_delete' ROOM_MEMBER_ADD = 'room_member_add' ROOM_MEMBER_REMOVE = 'room_member_remove' ROOM_PERMISSION_UPDATE = 'room_permission_update' # File events FILE_UPLOAD = 'file_upload' FILE_DOWNLOAD = 'file_download' FILE_DELETE = 'file_delete' FILE_RENAME = 'file_rename' FILE_MOVE = 'file_move' FILE_STAR = 'file_star' FILE_UNSTAR = 'file_unstar' # Conversation events CONVERSATION_CREATE = 'conversation_create' CONVERSATION_UPDATE = 'conversation_update' CONVERSATION_DELETE = 'conversation_delete' CONVERSATION_MEMBER_ADD = 'conversation_member_add' CONVERSATION_MEMBER_REMOVE = 'conversation_member_remove' CONVERSATION_OPEN = 'conversation_open' # Message events MESSAGE_CREATE = 'message_create' MESSAGE_UPDATE = 'message_update' MESSAGE_DELETE = 'message_delete' MESSAGE_ATTACHMENT_ADD = 'message_attachment_add' MESSAGE_ATTACHMENT_REMOVE = 'message_attachment_remove' # Settings events SETTINGS_UPDATE = 'settings_update' class Event(db.Model): __tablename__ = 'events' id = db.Column(db.Integer, primary_key=True) event_type = db.Column(db.String(50), nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) timestamp = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) details = db.Column(db.JSON) # Store additional event-specific data ip_address = db.Column(db.String(45)) # IPv6 addresses can be up to 45 chars user_agent = db.Column(db.String(255)) # Relationships user = db.relationship('User', backref='events') def __repr__(self): return f'' class NotifType(Enum): # User notifications ACCOUNT_CREATED = 'account_created' PASSWORD_RESET = 'password_reset' ACCOUNT_DELETED = 'account_deleted' ACCOUNT_UPDATED = 'account_updated' # Room notifications ROOM_INVITE = 'room_invite' ROOM_INVITE_REMOVED = 'room_invite_removed' # Conversation notifications CONVERSATION_INVITE = 'conversation_invite' CONVERSATION_INVITE_REMOVED = 'conversation_invite_removed' CONVERSATION_MESSAGE = 'conversation_message' class Notif(db.Model): __tablename__ = 'notifs' id = db.Column(db.Integer, primary_key=True) notif_type = db.Column(db.String(50), nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) sender_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True) timestamp = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) read = db.Column(db.Boolean, default=False, nullable=False) details = db.Column(db.JSON) # Store additional notification-specific data # Relationships user = db.relationship('User', foreign_keys=[user_id], backref='notifications') sender = db.relationship('User', foreign_keys=[sender_id], backref='sent_notifications') def __repr__(self): return f''