Files
sandrodach d39d1d1122
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 57s
feat: complete workspace, triage, outliner and sharing overhaul (tasks 1-26)
2026-10-27 19:46:46 +01:00

506 lines
17 KiB
Go

package domain
import (
"crypto/rand"
"encoding/hex"
"fmt"
"regexp"
"strings"
"time"
)
// NewID generates a readable prefixed ID as specified in BRAIN.md (e.g. th_4x8z19a0b1)
func NewID(prefix string) string {
b := make([]byte, 5)
_, _ = rand.Read(b)
return fmt.Sprintf("%s_%s", prefix, hex.EncodeToString(b))
}
// NowISO returns the current UTC timestamp in ISO-8601 format
func NowISO() string {
return time.Now().UTC().Format(time.RFC3339)
}
var hashtagRegex = regexp.MustCompile(`#([a-zA-Z0-9äöüÄÖÜß_-]+)`)
// ExtractTags extracts #hashtags from content
func ExtractTags(content string) []string {
matches := hashtagRegex.FindAllStringSubmatch(content, -1)
seen := make(map[string]bool)
tags := make([]string, 0, len(matches))
for _, m := range matches {
if len(m) > 1 {
tag := strings.ToLower(m[1])
if !seen[tag] {
seen[tag] = true
tags = append(tags, tag)
}
}
}
return tags
}
// FormatGermanDateTime formats an ISO-8601 string into "25. Okt. 2026 - 15:40 Uhr"
func FormatGermanDateTime(isoStr string) string {
if isoStr == "" {
return ""
}
var t time.Time
var err error
formats := []string{
time.RFC3339Nano,
time.RFC3339,
"2006-01-02T15:04:05Z07:00",
"2006-01-02T15:04:05",
"2006-01-02 15:04:05",
"2006-01-02",
}
for _, f := range formats {
if parsed, parseErr := time.Parse(f, isoStr); parseErr == nil {
t = parsed
err = nil
break
} else {
err = parseErr
}
}
if err != nil {
return isoStr
}
t = t.Local()
germanMonths := map[time.Month]string{
time.January: "Jan.",
time.February: "Feb.",
time.March: "März",
time.April: "Apr.",
time.May: "Mai",
time.June: "Juni",
time.July: "Juli",
time.August: "Aug.",
time.September: "Sept.",
time.October: "Okt.",
time.November: "Nov.",
time.December: "Dez.",
}
mStr := germanMonths[t.Month()]
if mStr == "" {
mStr = t.Format("Jan")
}
return fmt.Sprintf("%d. %s %d - %02d:%02d Uhr", t.Day(), mStr, t.Year(), t.Hour(), t.Minute())
}
type User struct {
ID string `json:"id"`
Email string `json:"email"`
Username string `json:"username,omitempty"`
PasswordHash string `json:"-"`
DisplayName string `json:"displayName"`
AvatarURL string `json:"avatarUrl,omitempty"`
Role string `json:"role"` // admin | member
IsActive bool `json:"isActive"`
TokenVersion int `json:"tokenVersion"`
AlarmChannels []string `json:"alarmChannels"` // email | discord | webhook
AlarmEmail string `json:"alarmEmail"`
DiscordWebhookURL string `json:"discordWebhookUrl"`
CustomWebhookURL string `json:"customWebhookUrl"`
NotifyBeforeMinutes int `json:"notifyBeforeMinutes"`
CreatedAt string `json:"createdAt"`
}
type UserInvite struct {
ID string `json:"id"`
Code string `json:"code"`
Email string `json:"email,omitempty"`
Role string `json:"role"` // member | admin
TeamID string `json:"teamId,omitempty"`
TeamName string `json:"teamName,omitempty"`
CreatedBy string `json:"createdBy"`
ExpiresAt string `json:"expiresAt"`
UsedBy string `json:"usedBy,omitempty"`
CreatedAt string `json:"createdAt"`
}
type ThoughtStatus string
const (
StatusInbox ThoughtStatus = "inbox"
StatusKept ThoughtStatus = "kept"
StatusDiscarded ThoughtStatus = "discarded"
StatusConverted ThoughtStatus = "converted"
)
type SubTask struct {
ID string `json:"id"`
Title string `json:"title"`
IsCompleted bool `json:"isCompleted"`
CreatedAt string `json:"createdAt,omitempty"`
}
type Thought struct {
ID string `json:"id"`
UserID string `json:"userId"`
Content string `json:"content"`
Tags []string `json:"tags"`
Priority string `json:"priority"` // low | normal | high | urgent
Source string `json:"source"` // manual | voice | webhook
Status ThoughtStatus `json:"status"`
IsCompleted bool `json:"isCompleted"`
ConvertedType string `json:"convertedType,omitempty"` // task | document | list_item | document_block
ConvertedRefID string `json:"convertedRefId,omitempty"` // referenced entity ID
DocID string `json:"docId,omitempty"`
BlockID string `json:"blockId,omitempty"`
DueDate string `json:"dueDate,omitempty"`
TaskListID string `json:"taskListId,omitempty"`
KanbanStatus string `json:"kanbanStatus,omitempty"` // todo | in_progress | done
Notes string `json:"notes,omitempty"`
Subtasks []SubTask `json:"subtasks"`
AlarmSent bool `json:"alarmSent,omitempty"`
SortOrder int `json:"sortOrder"`
CreatedAt string `json:"createdAt"`
TriagedAt string `json:"triagedAt,omitempty"`
}
type ThoughtReorderItem struct {
ID string `json:"id"`
SortOrder int `json:"sortOrder"`
KanbanStatus *string `json:"kanbanStatus,omitempty"`
}
type ListItemReorderItem struct {
ID string `json:"id"`
SortOrder int `json:"sortOrder"`
Category *string `json:"category,omitempty"`
Content *string `json:"content,omitempty"`
}
type TaskListShare struct {
ID string `json:"id"`
TaskListID string `json:"taskListId"`
TargetType string `json:"targetType"` // team | user
TargetID string `json:"targetId"`
TargetName string `json:"targetName,omitempty"`
Email string `json:"email,omitempty"`
AvatarURL string `json:"avatarUrl,omitempty"`
CreatedAt string `json:"createdAt"`
}
type TaskListMember struct {
UserID string `json:"userId"`
DisplayName string `json:"displayName"`
Email string `json:"email"`
AvatarURL string `json:"avatarUrl,omitempty"`
Role string `json:"role"` // owner | member
}
type KanbanColumn struct {
ID string `json:"id"`
Name string `json:"name"`
Color string `json:"color,omitempty"`
IsDone bool `json:"isDone,omitempty"`
}
type TaskList struct {
ID string `json:"id"`
UserID string `json:"userId"`
SpaceID string `json:"spaceId,omitempty"`
SpaceName string `json:"spaceName,omitempty"`
TeamID string `json:"teamId,omitempty"`
TeamName string `json:"teamName,omitempty"`
OwnerName string `json:"ownerName,omitempty"`
IsOwner bool `json:"isOwner"`
Members []TaskListMember `json:"members,omitempty"`
Shares []TaskListShare `json:"shares,omitempty"`
Columns []KanbanColumn `json:"columns,omitempty"`
Title string `json:"title"`
Icon string `json:"icon"`
Color string `json:"color"`
TasksCount int `json:"tasksCount,omitempty"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type Organization struct {
ID string `json:"id"`
Name string `json:"name"`
OwnerID string `json:"ownerId"`
Teams []Team `json:"teams,omitempty"`
CreatedAt string `json:"createdAt"`
}
type Team struct {
ID string `json:"id"`
OrgID string `json:"orgId,omitempty"`
Name string `json:"name"`
Description string `json:"description"`
Members []TeamMember `json:"members"`
CreatedAt string `json:"createdAt"`
}
type TeamMember struct {
TeamID string `json:"teamId"`
UserID string `json:"userId"`
DisplayName string `json:"displayName"`
Email string `json:"email"`
AvatarURL string `json:"avatarUrl,omitempty"`
Role string `json:"role"` // admin | member
JoinedAt string `json:"joinedAt"`
}
type SystemSettings struct {
SharingMode string `json:"sharingMode"` // open | teams_only | strict_private
AllowRegistration bool `json:"allowRegistration"`
InstanceName string `json:"instanceName"`
SMTPHost string `json:"smtpHost"`
SMTPPort string `json:"smtpPort"`
SMTPUser string `json:"smtpUser"`
SMTPPass string `json:"smtpPass"`
SMTPFrom string `json:"smtpFrom"`
SMTPTLS bool `json:"smtpTls"`
SMTPConfiguredViaEnv bool `json:"smtpConfiguredViaEnv,omitempty"`
}
type DocumentBlock struct {
ID string `json:"id"`
DocumentID string `json:"documentId,omitempty"`
Type string `json:"type"` // h1 | h2 | h3 | paragraph | todo | bullet | code | callout | divider | table | progress
Content string `json:"content"`
Checked bool `json:"checked,omitempty"`
Lang string `json:"lang,omitempty"`
Level int `json:"level"`
Position float64 `json:"position"`
Collapsed bool `json:"collapsed,omitempty"`
TaskID string `json:"taskId,omitempty"`
}
func CompileBlocksToMarkdown(blocks []DocumentBlock) string {
var sb strings.Builder
for _, b := range blocks {
lvl := b.Level
if lvl < 0 {
lvl = 0
}
indent := strings.Repeat(" ", lvl)
switch b.Type {
case "h1":
sb.WriteString(indent + "- # " + b.Content + "\n")
case "h2":
sb.WriteString(indent + "- ## " + b.Content + "\n")
case "h3":
sb.WriteString(indent + "- ### " + b.Content + "\n")
case "todo":
mark := "[ ]"
if b.Checked {
mark = "[x]"
}
sb.WriteString(indent + "- " + mark + " " + b.Content + "\n")
case "code":
sb.WriteString(indent + "- ```" + b.Lang + "\n" + b.Content + "\n" + indent + " ```\n")
case "divider":
sb.WriteString(indent + "- ---\n")
default:
sb.WriteString(indent + "- " + b.Content + "\n")
}
}
return sb.String()
}
type DocBlocksPatchRequest struct {
Upserted []DocumentBlock `json:"upserted,omitempty"`
DeletedIDs []string `json:"deletedIds,omitempty"`
BlockOrder []string `json:"blockOrder,omitempty"`
Title *string `json:"title,omitempty"`
Icon *string `json:"icon,omitempty"`
}
type DocBlocksPatchEvent struct {
DocID string `json:"docId"`
Upserted []DocumentBlock `json:"upserted,omitempty"`
DeletedIDs []string `json:"deletedIds,omitempty"`
BlockOrder []string `json:"blockOrder,omitempty"`
Title *string `json:"title,omitempty"`
Icon *string `json:"icon,omitempty"`
UpdatedBy string `json:"updatedBy"`
UpdatedAt string `json:"updatedAt"`
}
type DocShare struct {
ID string `json:"id"`
DocID string `json:"docId"`
TargetType string `json:"targetType,omitempty"` // user | team
TargetID string `json:"targetId,omitempty"`
TargetName string `json:"targetName,omitempty"`
UserID string `json:"userId,omitempty"`
UserEmail string `json:"userEmail,omitempty"`
DisplayName string `json:"displayName,omitempty"`
Permission string `json:"permission"` // read | write
CreatedBy string `json:"createdBy"`
CreatedAt string `json:"createdAt"`
}
type Document struct {
ID string `json:"id"`
UserID string `json:"userId"`
SpaceID string `json:"spaceId,omitempty"`
Title string `json:"title"`
Icon string `json:"icon"`
Content string `json:"content"`
MarkdownCache string `json:"markdownCache,omitempty"`
Blocks []DocumentBlock `json:"blocks"`
Tags []string `json:"tags"`
FolderID string `json:"folderId,omitempty"`
FolderPath string `json:"folderPath,omitempty"`
IsPinned bool `json:"isPinned"`
IsArchived bool `json:"isArchived"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
// Collaboration & Permissions
Permission string `json:"permission"` // owner | write | read
IsOwner bool `json:"isOwner"`
OwnerName string `json:"ownerName,omitempty"`
SharedWith []DocShare `json:"sharedWith,omitempty"`
SharedFolderID string `json:"sharedFolderId,omitempty"`
SubFolderPath string `json:"subFolderPath,omitempty"`
IsSharedFolder bool `json:"isSharedFolder,omitempty"`
FolderPermission string `json:"folderPermission,omitempty"`
}
type TagSummary struct {
Tag string `json:"tag"`
Count int `json:"count"`
}
type MatchedTagBlock struct {
RootBlock DocumentBlock `json:"rootBlock"`
Children []DocumentBlock `json:"children"`
}
type TagDocumentGroup struct {
DocID string `json:"docId"`
Title string `json:"title"`
Icon string `json:"icon"`
FolderPath string `json:"folderPath"`
UpdatedAt string `json:"updatedAt"`
IsOwner bool `json:"isOwner"`
Permission string `json:"permission"`
OwnerName string `json:"ownerName,omitempty"`
MatchedBlocks []MatchedTagBlock `json:"matchedBlocks"`
}
type TagDetails struct {
Tag string `json:"tag"`
NoteContent string `json:"noteContent"`
NoteUpdatedAt string `json:"noteUpdatedAt,omitempty"`
Documents []TagDocumentGroup `json:"documents"`
TotalMatches int `json:"totalMatches"`
}
type ListShare struct {
ID string `json:"id"`
ListID string `json:"listId"`
TargetType string `json:"targetType"` // team | user
TargetID string `json:"targetId"`
TargetName string `json:"targetName,omitempty"`
Email string `json:"email,omitempty"`
Role string `json:"role"` // editor | viewer
CreatedBy string `json:"createdBy"`
CreatedAt string `json:"createdAt"`
}
type ListItem struct {
ID string `json:"id"`
ListID string `json:"listId"`
Content string `json:"content"`
Category string `json:"category,omitempty"`
Quantity string `json:"quantity,omitempty"`
IsCompleted bool `json:"isCompleted"`
CompletedBy string `json:"completedBy,omitempty"`
CompletedByName string `json:"completedByName,omitempty"`
SortOrder int `json:"sortOrder"`
CreatedAt string `json:"createdAt"`
CompletedAt string `json:"completedAt,omitempty"`
}
type SharedList struct {
ID string `json:"id"`
UserID string `json:"userId"`
OwnerName string `json:"ownerName,omitempty"`
IsOwner bool `json:"isOwner"`
Role string `json:"role,omitempty"` // editor | viewer
Title string `json:"title"`
Icon string `json:"icon"`
ListType string `json:"listType"` // shopping | checklist | vacation
Items []ListItem `json:"items"`
Shares []ListShare `json:"shares,omitempty"`
UpdatedAt string `json:"updatedAt"`
CreatedAt string `json:"createdAt"`
}
type APIKey struct {
ID string `json:"id"`
UserID string `json:"userId"`
Name string `json:"name"`
KeyPrefix string `json:"keyPrefix"`
KeyHash string `json:"-"`
RawKey string `json:"rawKey,omitempty"` // Only returned once on creation
Scopes string `json:"scopes"`
LastUsedAt string `json:"lastUsedAt,omitempty"`
CreatedAt string `json:"createdAt"`
}
type WSMessage struct {
Event string `json:"event"` // ITEM_CREATED | ITEM_UPDATED | ITEM_TOGGLED | ITEM_DELETED | LIST_CLEARED | USER_PRESENCE
ListID string `json:"list_id"`
Payload interface{} `json:"payload"`
Timestamp string `json:"timestamp"`
}
type DashboardStats struct {
InboxCount int `json:"inboxCount"`
TasksOpenCount int `json:"tasksOpenCount"`
TasksDoneCount int `json:"tasksDoneCount"`
DocsCount int `json:"docsCount"`
DocsTodosOpenCount int `json:"docsTodosOpenCount"`
ListsCount int `json:"listsCount"`
DiscardedCount int `json:"discardedCount"`
}
type FullExport struct {
ExportedAt string `json:"exportedAt"`
Version string `json:"version"`
User User `json:"user"`
Thoughts []Thought `json:"thoughts"`
Documents []Document `json:"documents"`
Lists []SharedList `json:"lists"`
TaskLists []TaskList `json:"taskLists,omitempty"`
}
type SharedFolder struct {
ID string `json:"id"`
OwnerID string `json:"ownerId"`
OwnerName string `json:"ownerName,omitempty"`
Name string `json:"name"`
MountPath string `json:"mountPath"`
IsOwner bool `json:"isOwner"`
Permission string `json:"permission"` // owner | write | read
Shares []FolderShare `json:"shares,omitempty"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type FolderShare struct {
ID string `json:"id"`
FolderID string `json:"folderId"`
TargetType string `json:"targetType"` // team | user
TargetID string `json:"targetId"`
TargetName string `json:"targetName,omitempty"`
Email string `json:"email,omitempty"`
AvatarURL string `json:"avatarUrl,omitempty"`
Permission string `json:"permission"` // read | write
CreatedBy string `json:"createdBy"`
CreatedAt string `json:"createdAt"`
}