User & Notification Models
Diese Domäne umfasst Benutzer, Workspaces, Favoriten sowie das zweistufige Benachrichtigungssystem (Announcements + Notifications) mit Quiet Hours und Präferenzen.
User
Benutzer-Model mit WorkOS SSO-Integration. Dispatcht bei Erstellung das UserRegistered-Event.
Tabelle: users
| Feld | Typ | Nullable | Beschreibung |
|---|---|---|---|
id | bigint | PK | Primary Key |
name | string | nein | Anzeigename |
email | string | nein | E-Mail-Adresse (unique) |
workos_id | string | ja | WorkOS SSO-Identifier |
avatar | string | ja | Avatar-URL |
timezone | string | ja | Zeitzone (Default: Europe/Berlin) |
email_verified_at | datetime | ja | E-Mail verifiziert am |
password | string | ja | Passwort (hashed) |
remember_token | string | ja | Remember-Me-Token |
newsletter_opted_in_at | datetime | ja | Newsletter-Bestaetigung (Double-Opt-In) |
newsletter_token | varchar(64) | ja | Token fuer Newsletter-Bestaetigung |
newsletter_token_expires_at | datetime | ja | Token-Ablauf (7 Tage) |
privacy_accepted_at | datetime | ja | Datenschutz-Zustimmung bei Registrierung |
terms_accepted_at | datetime | ja | AGB-Zustimmung bei Registrierung |
created_at | datetime | nein | Erstellt am |
updated_at | datetime | nein | Aktualisiert am |
Relations:
| Methode | Typ | Related Model | FK | Beschreibung |
|---|---|---|---|---|
workspaces() | hasMany | Workspace | owner_id | Eigene Workspaces |
notificationPreferences() | hasMany | NotificationPreference | user_id | Benachrichtigungs-Einstellungen |
quietHours() | hasOne | UserQuietHours | user_id | Ruhezeiten |
notifications() | morphMany | DatabaseNotification | notifiable_id | Per-User Notifications |
Hidden: workos_id, remember_token, newsletter_token
Casts: email_verified_at → datetime, password → hashed, newsletter_opted_in_at → datetime, newsletter_token_expires_at → datetime, privacy_accepted_at → datetime, terms_accepted_at → datetime
Accessors: timezone -- gibt Europe/Berlin zurück falls kein Wert gesetzt.
Boot-Event:
static::created(function (User $user) {
UserRegistered::dispatch($user->id, $user->name, $user->email, $user->avatar);
});
Methoden:
| Methode | Return | Beschreibung |
|---|---|---|
initials() | string | Initialen aus dem Namen |
isAdmin() | bool | Prüft ob E-Mail in config('postbox.admin_emails') |
visibleNotifications() | MorphMany | Notifications mit accessibleBy() Scope |
unreadNotificationsCount() | int | Anzahl ungelesener, sichtbarer Notifications |
visibleAnnouncements() | Builder | Announcements per forUser() Scope |
unreadAnnouncements() | Builder | Ungelesene Announcements |
hasNewsletterSubscription() | bool | Newsletter bestaetigt (newsletter_opted_in_at IS NOT NULL) |
hasNewsletterPending() | bool | Token existiert, aber nicht bestaetigt |
Location: app/Models/User.php
Workspace
Container für Watchers. Jeder User kann mehrere Workspaces haben. Der Admin-Workspace (ID 999999999999) spiegelt automatisch alle Profile.
Tabelle: workspaces
| Feld | Typ | Nullable | Beschreibung |
|---|---|---|---|
id | bigint | PK | Primary Key |
owner_id | bigint (FK) | nein | Besitzer-User |
name | string | nein | Workspace-Name |
description | text | ja | Beschreibung |
created_at | datetime | nein | Erstellt am |
updated_at | datetime | nein | Aktualisiert am |
Relations:
| Methode | Typ | Related Model | FK | Beschreibung |
|---|---|---|---|---|
owner() | belongsTo | User | owner_id | Besitzer |
watchers() | hasMany | Watcher | workspace_id | Watchers im Workspace |
Admin-Workspace: ID 999999999999 (AdminWorkspaceManager::ADMIN_WORKSPACE_ID), wird per watchers:backfill-admin-workspace stündlich aktualisiert.
Location: app/Models/Workspace.php
FavoriteSocialProfile
Favorisierte Profile eines Users. Unique-Constraint auf (user_id, social_profile_id).
Tabelle: favorite_social_profiles
| Feld | Typ | Nullable | Beschreibung |
|---|---|---|---|
id | bigint | PK | Primary Key |
user_id | bigint (FK) | nein | User |
social_profile_id | bigint (FK) | nein | Favorisiertes Profil |
created_at | datetime | nein | Erstellt am |
updated_at | datetime | nein | Aktualisiert am |
Relations:
| Methode | Typ | Related Model | FK | Beschreibung |
|---|---|---|---|---|
user() | belongsTo | User | user_id | Zugehöriger User |
profile() | belongsTo | SocialProfile | social_profile_id | Favorisiertes Profil |
Location: app/Models/FavoriteSocialProfile.php
UserQuietHours
Ruhezeiten-Konfiguration pro User. Steuert, wann Benachrichtigungen unterdrückt werden. Pro Wochentag aktivier-/deaktivierbar.
Tabelle: user_quiet_hours
| Feld | Typ | Nullable | Beschreibung |
|---|---|---|---|
id | bigint | PK | Primary Key |
user_id | bigint (FK) | nein | Zugehöriger User (unique) |
enabled | boolean | nein | Ruhezeiten aktiviert |
window_start | string | nein | Start-Uhrzeit (z.B. 22:00) |
window_end | string | nein | End-Uhrzeit (z.B. 08:00) |
monday | boolean | nein | Montag aktiv |
tuesday | boolean | nein | Dienstag aktiv |
wednesday | boolean | nein | Mittwoch aktiv |
thursday | boolean | nein | Donnerstag aktiv |
friday | boolean | nein | Freitag aktiv |
saturday | boolean | nein | Samstag aktiv |
sunday | boolean | nein | Sonntag aktiv |
created_at | datetime | nein | Erstellt am |
updated_at | datetime | nein | Aktualisiert am |
Relations:
| Methode | Typ | Related Model | FK | Beschreibung |
|---|---|---|---|---|
user() | belongsTo | User | user_id | Zugehöriger User |
Casts: enabled → boolean, monday...sunday → boolean
Methoden:
| Methode | Return | Beschreibung |
|---|---|---|
isCurrentlyQuiet() | bool | Prüft ob gerade Ruhezeit ist |
nextActiveTime() | Carbon | Nächster Zeitpunkt nach Ruhezeit |
activeDayLabels() | array | Labels der aktiven Tage |
Location: app/Models/UserQuietHours.php
NotificationPreference
Benachrichtigungs-Einstellungen pro User und Workspace. Steuert App- und E-Mail-Kanal pro Notification-Typ.
Tabelle: notification_preferences
| Feld | Typ | Nullable | Beschreibung |
|---|---|---|---|
id | bigint | PK | Primary Key |
user_id | bigint (FK) | nein | Zugehöriger User |
workspace_id | bigint (FK) | ja | Zugehöriger Workspace (null = global) |
notification_type | string | nein | Typ (z.B. daily_scrape, import_completed) |
channel_app | boolean | nein | In-App-Kanal aktiviert |
channel_email | boolean | nein | E-Mail-Kanal aktiviert |
created_at | datetime | nein | Erstellt am |
updated_at | datetime | nein | Aktualisiert am |
Relations:
| Methode | Typ | Related Model | FK | Beschreibung |
|---|---|---|---|---|
user() | belongsTo | User | user_id | Zugehöriger User |
workspace() | belongsTo | Workspace | workspace_id | Zugehöriger Workspace |
Methoden: isChannelEnabled($channel) -- prüft ob ein bestimmter Kanal aktiviert ist.
Location: app/Models/NotificationPreference.php
NotificationStat
Aggregierte Statistiken für Benachrichtigungen. Upsert-Counter per Tag, Kanal und Typ.
Tabelle: notification_stats
| Feld | Typ | Nullable | Beschreibung |
|---|---|---|---|
id | bigint | PK | Primary Key |
date | date | nein | Tag |
channel | string | nein | Kanal (app, email) |
type | string | nein | Notification-Typ |
count | integer | nein | Anzahl |
created_at | datetime | nein | Erstellt am |
updated_at | datetime | nein | Aktualisiert am |
Casts: date → date, count → integer
Methoden: record($channel, $type) (static) -- Upsert-Increment auf dem aktuellen Tag.
Location: app/Models/NotificationStat.php
Announcement
Geteilte Benachrichtigungen (1 Zeile pro Event, Read-Tracking über AnnouncementRead). Unterstützt Audience-Targeting und Ablaufzeiten.
Tabelle: announcements
| Feld | Typ | Nullable | Beschreibung |
|---|---|---|---|
id | bigint | PK | Primary Key |
type | string | nein | Announcement-Typ |
data | json | nein | Payload (title, body, icon, url) |
admin_only | boolean | nein | Nur für Admins sichtbar |
audience_type | string | ja | user, workspace, all |
audience_id | integer | ja | Ziel-ID (User-ID oder Workspace-ID) |
expires_at | datetime | ja | Ablaufzeitpunkt |
created_at | datetime | nein | Erstellt am |
updated_at | datetime | nein | Aktualisiert am |
Relations:
| Methode | Typ | Related Model | FK | Beschreibung |
|---|---|---|---|---|
reads() | hasMany | AnnouncementRead | announcement_id | Read-/Dismiss-Status |
Scopes:
| Scope | Beschreibung |
|---|---|
forUser($user) | Announcements sichtbar für diesen User |
notExpired() | Nur nicht-abgelaufene |
visibleTo($user) | Admin-Only-Filter |
notDismissedBy($user) | Nicht dismissed |
unreadBy($user) | Ungelesene |
Casts: data → array, admin_only → boolean, expires_at → datetime, audience_id → integer
Methoden: isReadBy($user), isUnreadBy($user), markAsReadBy($user), isExpired(), getTitle(), getBody(), getUrl(), getIcon()
Location: app/Models/Announcement.php
AnnouncementRead
Read-/Dismiss-Status pro User und Announcement. Composite Primary Key ohne Auto-Increment.
Tabelle: announcement_reads
| Feld | Typ | Nullable | Beschreibung |
|---|---|---|---|
user_id | bigint (FK) | PK | User-ID |
announcement_id | bigint (FK) | PK | Announcement-ID |
read_at | datetime | ja | Gelesen am |
dismissed_at | datetime | ja | Dismissed am |
Relations:
| Methode | Typ | Related Model | FK | Beschreibung |
|---|---|---|---|---|
user() | belongsTo | User | user_id | Lesender User |
announcement() | belongsTo | Announcement | announcement_id | Zugehörige Announcement |
Timestamps: false (kein created_at/updated_at)
Casts: read_at → datetime, dismissed_at → datetime
Location: app/Models/AnnouncementRead.php
DatabaseNotification
Erweitert Laravels BaseDatabaseNotification um expires_at und admin_only. Per-User Notifications (im Gegensatz zu Announcements, die geteilt werden).
Tabelle: notifications
| Feld | Typ | Nullable | Beschreibung |
|---|---|---|---|
id | uuid | PK | UUID Primary Key |
type | string | nein | Notification-Klasse |
notifiable_type | string | nein | Polymorphe Typ-Spalte |
notifiable_id | bigint | nein | Polymorphe ID-Spalte |
data | json | nein | Notification-Payload |
read_at | datetime | ja | Gelesen am |
admin_only | boolean | nein | Nur für Admins sichtbar |
expires_at | datetime | ja | Ablaufzeitpunkt |
created_at | datetime | nein | Erstellt am |
updated_at | datetime | nein | Aktualisiert am |
Scopes:
| Scope | Beschreibung |
|---|---|
notExpired() | expires_at IS NULL OR expires_at > NOW() |
visibleTo($user) | Admin-Only-Filter basierend auf $user->isAdmin() |
accessibleBy($user) | Kombination: visibleTo() + notExpired() |
Casts: data → array, read_at → datetime, expires_at → datetime, admin_only → boolean
Methoden: isExpired()
Location: app/Models/DatabaseNotification.php