feat(projects): Aktivitaetsverlauf je Projekt
Was ist hier eigentlich passiert - eine Zeitleiste auf der Projektseite beantwortet das jetzt: Statuswechsel, Notizen, Aufgaben, neue Commits. Der Verlauf soll erzaehlen, nicht protokollieren. Deshalb bekommen Status und Repo-Verknuepfung einen eigenen Eintrag, waehrend alle uebrigen Felder zu einem zusammengefasst werden - sonst ertraenkt eine Feinjustage der Farbe alles Wichtige. Ein Titel, der sich aendert, kommt gar nicht vor. Beim Abgleich ist Zurueckhaltung noch wichtiger: er laeuft im Zweifel stuendlich. Vermerkt wird nur, wenn der letzte Commit ein anderer ist als beim letzten Mal, und beim ersten Abgleich gar nicht - dass ein Repo Commits hat, ist keine Neuigkeit. Fehlschlaege stehen nur beim Uebergang in den Fehlerzustand drin, sonst schlaegt ein abgelaufenes Token stuendlich neu auf. Der Text wird beim Schreiben formuliert, nicht beim Anzeigen. Ein Eintrag soll auch dann lesbar bleiben, wenn es die Notiz, um die es ging, laengst nicht mehr gibt. Zweihundert Eintraege je Projekt bleiben erhalten, der taegliche Durchgang stutzt den Rest. Der Export nimmt den Verlauf nicht mit - er beschriebe, was in dieser Instanz passiert ist, und verwiese auf Konten, die es anderswo nicht gibt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ActivityKind" AS ENUM ('PROJECT_CREATED', 'PROJECT_UPDATED', 'STATUS_CHANGED', 'NOTE_ADDED', 'NOTE_UPDATED', 'NOTE_DELETED', 'TASK_ADDED', 'TASK_MOVED', 'TASK_DELETED', 'TASKS_IMPORTED', 'REPO_LINKED', 'REPO_COMMITS', 'SYNC_FAILED');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Activity" (
|
||||
"id" TEXT NOT NULL,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"userId" TEXT,
|
||||
"kind" "ActivityKind" NOT NULL,
|
||||
"summary" TEXT NOT NULL,
|
||||
"meta" JSONB,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "Activity_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Activity_projectId_createdAt_idx" ON "Activity"("projectId", "createdAt");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Activity" ADD CONSTRAINT "Activity_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Activity" ADD CONSTRAINT "Activity_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
+98
-3
@@ -52,6 +52,25 @@ enum NotificationEvent {
|
||||
PROJECT_STALE
|
||||
}
|
||||
|
||||
/// Was im Verlauf eines Projekts festgehalten wird. Bewusst grob: der
|
||||
/// Verlauf soll erzaehlen, was passiert ist, und nicht jede Feldaenderung
|
||||
/// mitschreiben.
|
||||
enum ActivityKind {
|
||||
PROJECT_CREATED
|
||||
PROJECT_UPDATED
|
||||
STATUS_CHANGED
|
||||
NOTE_ADDED
|
||||
NOTE_UPDATED
|
||||
NOTE_DELETED
|
||||
TASK_ADDED
|
||||
TASK_MOVED
|
||||
TASK_DELETED
|
||||
TASKS_IMPORTED
|
||||
REPO_LINKED
|
||||
REPO_COMMITS
|
||||
SYNC_FAILED
|
||||
}
|
||||
|
||||
/// Spalten des Aufgabenbretts innerhalb eines Projekts.
|
||||
enum TaskStatus {
|
||||
TODO
|
||||
@@ -93,11 +112,21 @@ model User {
|
||||
failedLoginCount Int @default(0)
|
||||
lockedUntil DateTime?
|
||||
|
||||
/// TOTP-Schluessel, AES-256-GCM-verschluesselt (siehe src/lib/crypto.ts).
|
||||
/// Hinterlegt heisst noch nicht scharf: erst ein bestaetigter Code setzt
|
||||
/// totpEnabledAt. Bis dahin darf die Anmeldung nicht danach fragen, sonst
|
||||
/// sperrt sich aus, wer die Einrichtung abbricht.
|
||||
totpSecretCipher String?
|
||||
totpEnabledAt DateTime?
|
||||
|
||||
sessions Session[]
|
||||
credentials Credential[]
|
||||
projects Project[]
|
||||
connections GitConnection[]
|
||||
notificationTargets NotificationTarget[]
|
||||
activities Activity[]
|
||||
recoveryCodes RecoveryCode[]
|
||||
pendingLogins PendingLogin[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -142,6 +171,45 @@ model Credential {
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
/// Ein Wiederherstellungscode fuer den Fall, dass der Authenticator weg ist.
|
||||
/// Jeder Code gilt genau einmal.
|
||||
model RecoveryCode {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
/// SHA-256 des Codes. Kein langsames Verfahren wie bei Passwoertern: die
|
||||
/// Codes sind zufaellig erzeugt und lang genug, dass Raten aussichtslos
|
||||
/// ist – und bei einer Anmeldung waeren sonst zehn teure Vergleiche noetig.
|
||||
codeHash String @unique
|
||||
|
||||
usedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
/// Zwischenzustand einer Anmeldung: Passwort stimmt, der zweite Faktor fehlt
|
||||
/// noch. Bewusst eine eigene Zeile und keine halbfertige Sitzung – so kann
|
||||
/// aus diesem Zustand nichts entstehen, was einer Anmeldung aehnelt.
|
||||
model PendingLogin {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
/// Wie bei der Sitzung liegt nur der SHA-256-Hash des Cookie-Tokens hier.
|
||||
tokenHash String @unique
|
||||
/// Fehlversuche. Nach zu vielen wird der Vorgang verworfen, damit die
|
||||
/// sechs Stellen nicht in Ruhe durchprobiert werden koennen.
|
||||
attempts Int @default(0)
|
||||
|
||||
expiresAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([userId])
|
||||
@@index([expiresAt])
|
||||
}
|
||||
|
||||
/// Kurzlebige WebAuthn-Challenge. Wird nach Gebrauch geloescht, damit sie
|
||||
/// kein zweites Mal verwendet werden kann.
|
||||
model WebAuthnChallenge {
|
||||
@@ -285,9 +353,10 @@ model Project {
|
||||
/// erst, wenn danach wieder ein Commit kam und es dann wieder still wurde.
|
||||
notifiedStaleAt DateTime?
|
||||
|
||||
notes Note[]
|
||||
tasks Task[]
|
||||
snapshot RepoSnapshot?
|
||||
notes Note[]
|
||||
tasks Task[]
|
||||
snapshot RepoSnapshot?
|
||||
activities Activity[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -361,6 +430,32 @@ model Task {
|
||||
@@index([projectId, status, position])
|
||||
}
|
||||
|
||||
/// Ein Eintrag im Verlauf eines Projekts: was ist wann passiert.
|
||||
model Activity {
|
||||
id String @id @default(cuid())
|
||||
projectId String
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
|
||||
/// Wer. null bedeutet: die Anwendung selbst, also der Zeitgeber beim
|
||||
/// automatischen Abgleich.
|
||||
userId String?
|
||||
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
||||
|
||||
kind ActivityKind
|
||||
/// Fertig formulierter Einzeiler. Die Formulierung beim Schreiben
|
||||
/// festzuhalten spart der Anzeige das Nachschlagen von Namen, die es
|
||||
/// spaeter womoeglich nicht mehr gibt – etwa den einer geloeschten Notiz.
|
||||
summary String
|
||||
/// Zusatzangaben wie { from: "IDEA", to: "DONE" }. Fuer die Anzeige nicht
|
||||
/// noetig, aber nuetzlich, falls spaeter ausgewertet werden soll.
|
||||
meta Json?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
/// Absteigend gelesen, immer je Projekt.
|
||||
@@index([projectId, createdAt])
|
||||
}
|
||||
|
||||
/// Freie Notizen zu einem Projekt.
|
||||
model Note {
|
||||
id String @id @default(cuid())
|
||||
|
||||
Reference in New Issue
Block a user