from datetime import datetime, timezone from app.extensions import db class AddressBook(db.Model): __tablename__ = 'address_books' id = db.Column(db.Integer, primary_key=True) owner_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False, index=True) name = db.Column(db.String(255), nullable=False) description = db.Column(db.Text, nullable=True) color = db.Column(db.String(7), default='#3788d8') created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc)) updated_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc)) contacts = db.relationship('Contact', backref='address_book', lazy='dynamic', cascade='all, delete-orphan') shares = db.relationship('AddressBookShare', backref='address_book', lazy='dynamic', cascade='all, delete-orphan') # `owner` wird automatisch durch User.address_books backref erzeugt def to_dict(self): return { 'id': self.id, 'owner_id': self.owner_id, 'name': self.name, 'description': self.description, 'color': self.color, 'created_at': self.created_at.isoformat() if self.created_at else None, } class Contact(db.Model): __tablename__ = 'contacts' id = db.Column(db.Integer, primary_key=True) address_book_id = db.Column(db.Integer, db.ForeignKey('address_books.id'), nullable=False, index=True) uid = db.Column(db.String(255), unique=True, nullable=False) vcard_data = db.Column(db.Text, nullable=False) # Structured name fields prefix = db.Column(db.String(64), nullable=True) first_name = db.Column(db.String(128), nullable=True) middle_name = db.Column(db.String(128), nullable=True) last_name = db.Column(db.String(128), nullable=True, index=True) suffix = db.Column(db.String(64), nullable=True) display_name = db.Column(db.String(255), nullable=True, index=True) nickname = db.Column(db.String(128), nullable=True) # Organisation organization = db.Column(db.String(255), nullable=True) department = db.Column(db.String(255), nullable=True) job_title = db.Column(db.String(255), nullable=True) # Primary fields for quick listing (denormalised) primary_email = db.Column(db.String(255), nullable=True, index=True) primary_phone = db.Column(db.String(50), nullable=True) # JSON-encoded multi-valued fields # Each list entry: {"type": "home|work|other|mobile|fax|pager|...", "value": "..."} emails = db.Column(db.Text, nullable=True) phones = db.Column(db.Text, nullable=True) # address: {"type": ..., "street": ..., "po_box": ..., "city": ..., # "region": ..., "postal_code": ..., "country": ...} addresses = db.Column(db.Text, nullable=True) websites = db.Column(db.Text, nullable=True) impp = db.Column(db.Text, nullable=True) # {"protocol": "skype", "value": "..."} categories = db.Column(db.Text, nullable=True) # ["family", "work", ...] # Dates birthday = db.Column(db.String(10), nullable=True) # YYYY-MM-DD anniversary = db.Column(db.String(10), nullable=True) # Free text notes = db.Column(db.Text, nullable=True) # Photo: data URL (data:image/jpeg;base64,...) oder http(s):// photo = db.Column(db.Text, nullable=True) # Legacy column kept for old clients / migrations email = db.Column(db.String(255), nullable=True) phone = db.Column(db.String(50), nullable=True) created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc)) updated_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc)) def to_dict(self): import json def _loads(s, default): if not s: return default try: return json.loads(s) except (ValueError, TypeError): return default return { 'id': self.id, 'address_book_id': self.address_book_id, 'uid': self.uid, 'prefix': self.prefix, 'first_name': self.first_name, 'middle_name': self.middle_name, 'last_name': self.last_name, 'suffix': self.suffix, 'display_name': self.display_name, 'nickname': self.nickname, 'organization': self.organization, 'department': self.department, 'job_title': self.job_title, 'emails': _loads(self.emails, []), 'phones': _loads(self.phones, []), 'addresses': _loads(self.addresses, []), 'websites': _loads(self.websites, []), 'impp': _loads(self.impp, []), 'categories': _loads(self.categories, []), 'birthday': self.birthday, 'anniversary': self.anniversary, 'notes': self.notes, 'photo': self.photo, 'primary_email': self.primary_email or self.email, 'primary_phone': self.primary_phone or self.phone, 'created_at': self.created_at.isoformat() if self.created_at else None, 'updated_at': self.updated_at.isoformat() if self.updated_at else None, } class AddressBookShare(db.Model): __tablename__ = 'address_book_shares' id = db.Column(db.Integer, primary_key=True) address_book_id = db.Column(db.Integer, db.ForeignKey('address_books.id'), nullable=False, index=True) shared_with_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False, index=True) permission = db.Column(db.String(20), nullable=False, default='read') color = db.Column(db.String(7), nullable=True) # personal display color shared_with = db.relationship('User', backref='shared_address_books') __table_args__ = ( db.UniqueConstraint('address_book_id', 'shared_with_id', name='uq_addressbook_share'), )