feat: add database schema and backend foundation

Part 1 - Database (kanzlai schema in Supabase):
- Tenant-scoped tables: tenants, user_tenants, cases, parties,
  deadlines, appointments, documents, case_events
- Global reference tables: proceeding_types, deadline_rules, holidays
- RLS policies on all tenant-scoped tables
- Seed: UPC proceeding types, 32 deadline rules (INF/CCR/REV/PI/APP),
  ZPO civil rules (Berufung, Revision, Einspruch), 2026 holidays

Part 2 - Backend skeleton:
- config: env var loading (DATABASE_URL, SUPABASE_*, ANTHROPIC_API_KEY)
- db: sqlx connection pool with kanzlai search_path
- auth: JWT verification middleware adapted from youpc.org, context helpers
- models: Go structs for all tables with sqlx/json tags
- router: route registration with auth middleware, /health + placeholder API routes
- Updated main.go to wire everything together
This commit is contained in:
m
2026-03-25 13:17:33 +01:00
parent 193a4cd567
commit 1fc0874893
16 changed files with 478 additions and 11 deletions

View File

@@ -0,0 +1,23 @@
package models
import (
"time"
"github.com/google/uuid"
)
type Appointment struct {
ID uuid.UUID `db:"id" json:"id"`
TenantID uuid.UUID `db:"tenant_id" json:"tenant_id"`
CaseID *uuid.UUID `db:"case_id" json:"case_id,omitempty"`
Title string `db:"title" json:"title"`
Description *string `db:"description" json:"description,omitempty"`
StartAt time.Time `db:"start_at" json:"start_at"`
EndAt *time.Time `db:"end_at" json:"end_at,omitempty"`
Location *string `db:"location" json:"location,omitempty"`
AppointmentType *string `db:"appointment_type" json:"appointment_type,omitempty"`
CalDAVUID *string `db:"caldav_uid" json:"caldav_uid,omitempty"`
CalDAVEtag *string `db:"caldav_etag" json:"caldav_etag,omitempty"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}

View File

@@ -0,0 +1,23 @@
package models
import (
"encoding/json"
"time"
"github.com/google/uuid"
)
type Case struct {
ID uuid.UUID `db:"id" json:"id"`
TenantID uuid.UUID `db:"tenant_id" json:"tenant_id"`
CaseNumber string `db:"case_number" json:"case_number"`
Title string `db:"title" json:"title"`
CaseType *string `db:"case_type" json:"case_type,omitempty"`
Court *string `db:"court" json:"court,omitempty"`
CourtRef *string `db:"court_ref" json:"court_ref,omitempty"`
Status string `db:"status" json:"status"`
AISummary *string `db:"ai_summary" json:"ai_summary,omitempty"`
Metadata json.RawMessage `db:"metadata" json:"metadata"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}

View File

@@ -0,0 +1,22 @@
package models
import (
"encoding/json"
"time"
"github.com/google/uuid"
)
type CaseEvent struct {
ID uuid.UUID `db:"id" json:"id"`
TenantID uuid.UUID `db:"tenant_id" json:"tenant_id"`
CaseID uuid.UUID `db:"case_id" json:"case_id"`
EventType *string `db:"event_type" json:"event_type,omitempty"`
Title string `db:"title" json:"title"`
Description *string `db:"description" json:"description,omitempty"`
EventDate *time.Time `db:"event_date" json:"event_date,omitempty"`
CreatedBy *uuid.UUID `db:"created_by" json:"created_by,omitempty"`
Metadata json.RawMessage `db:"metadata" json:"metadata"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}

View File

@@ -0,0 +1,27 @@
package models
import (
"time"
"github.com/google/uuid"
)
type Deadline struct {
ID uuid.UUID `db:"id" json:"id"`
TenantID uuid.UUID `db:"tenant_id" json:"tenant_id"`
CaseID uuid.UUID `db:"case_id" json:"case_id"`
Title string `db:"title" json:"title"`
Description *string `db:"description" json:"description,omitempty"`
DueDate string `db:"due_date" json:"due_date"`
OriginalDueDate *string `db:"original_due_date" json:"original_due_date,omitempty"`
WarningDate *string `db:"warning_date" json:"warning_date,omitempty"`
Source string `db:"source" json:"source"`
RuleID *uuid.UUID `db:"rule_id" json:"rule_id,omitempty"`
Status string `db:"status" json:"status"`
CompletedAt *time.Time `db:"completed_at" json:"completed_at,omitempty"`
CalDAVUID *string `db:"caldav_uid" json:"caldav_uid,omitempty"`
CalDAVEtag *string `db:"caldav_etag" json:"caldav_etag,omitempty"`
Notes *string `db:"notes" json:"notes,omitempty"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}

View File

@@ -0,0 +1,43 @@
package models
import (
"time"
"github.com/google/uuid"
)
type DeadlineRule struct {
ID uuid.UUID `db:"id" json:"id"`
ProceedingTypeID *int `db:"proceeding_type_id" json:"proceeding_type_id,omitempty"`
ParentID *uuid.UUID `db:"parent_id" json:"parent_id,omitempty"`
Code *string `db:"code" json:"code,omitempty"`
Name string `db:"name" json:"name"`
Description *string `db:"description" json:"description,omitempty"`
PrimaryParty *string `db:"primary_party" json:"primary_party,omitempty"`
EventType *string `db:"event_type" json:"event_type,omitempty"`
IsMandatory bool `db:"is_mandatory" json:"is_mandatory"`
DurationValue int `db:"duration_value" json:"duration_value"`
DurationUnit string `db:"duration_unit" json:"duration_unit"`
Timing *string `db:"timing" json:"timing,omitempty"`
RuleCode *string `db:"rule_code" json:"rule_code,omitempty"`
DeadlineNotes *string `db:"deadline_notes" json:"deadline_notes,omitempty"`
SequenceOrder int `db:"sequence_order" json:"sequence_order"`
ConditionRuleID *uuid.UUID `db:"condition_rule_id" json:"condition_rule_id,omitempty"`
AltDurationValue *int `db:"alt_duration_value" json:"alt_duration_value,omitempty"`
AltDurationUnit *string `db:"alt_duration_unit" json:"alt_duration_unit,omitempty"`
AltRuleCode *string `db:"alt_rule_code" json:"alt_rule_code,omitempty"`
IsActive bool `db:"is_active" json:"is_active"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}
type ProceedingType struct {
ID int `db:"id" json:"id"`
Code string `db:"code" json:"code"`
Name string `db:"name" json:"name"`
Description *string `db:"description" json:"description,omitempty"`
Jurisdiction *string `db:"jurisdiction" json:"jurisdiction,omitempty"`
DefaultColor string `db:"default_color" json:"default_color"`
SortOrder int `db:"sort_order" json:"sort_order"`
IsActive bool `db:"is_active" json:"is_active"`
}

View File

@@ -0,0 +1,23 @@
package models
import (
"encoding/json"
"time"
"github.com/google/uuid"
)
type Document struct {
ID uuid.UUID `db:"id" json:"id"`
TenantID uuid.UUID `db:"tenant_id" json:"tenant_id"`
CaseID uuid.UUID `db:"case_id" json:"case_id"`
Title string `db:"title" json:"title"`
DocType *string `db:"doc_type" json:"doc_type,omitempty"`
FilePath *string `db:"file_path" json:"file_path,omitempty"`
FileSize *int `db:"file_size" json:"file_size,omitempty"`
MimeType *string `db:"mime_type" json:"mime_type,omitempty"`
AIExtracted *json.RawMessage `db:"ai_extracted" json:"ai_extracted,omitempty"`
UploadedBy *uuid.UUID `db:"uploaded_by" json:"uploaded_by,omitempty"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}

View File

@@ -0,0 +1,17 @@
package models
import (
"encoding/json"
"github.com/google/uuid"
)
type Party struct {
ID uuid.UUID `db:"id" json:"id"`
TenantID uuid.UUID `db:"tenant_id" json:"tenant_id"`
CaseID uuid.UUID `db:"case_id" json:"case_id"`
Name string `db:"name" json:"name"`
Role *string `db:"role" json:"role,omitempty"`
Representative *string `db:"representative" json:"representative,omitempty"`
ContactInfo json.RawMessage `db:"contact_info" json:"contact_info"`
}

View File

@@ -0,0 +1,24 @@
package models
import (
"encoding/json"
"time"
"github.com/google/uuid"
)
type Tenant struct {
ID uuid.UUID `db:"id" json:"id"`
Name string `db:"name" json:"name"`
Slug string `db:"slug" json:"slug"`
Settings json.RawMessage `db:"settings" json:"settings"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}
type UserTenant struct {
UserID uuid.UUID `db:"user_id" json:"user_id"`
TenantID uuid.UUID `db:"tenant_id" json:"tenant_id"`
Role string `db:"role" json:"role"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
}