feat: complete workspace, triage, outliner and sharing overhaul (tasks 1-26)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 57s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 57s
This commit is contained in:
1 parent
df2f2bac67
commit
d39d1d1122
17 files changed
+1173
-501
No files matched your search
@@ -135,6 +135,7 @@ func (s *Server) Router() http.Handler {
|
||||
r.Patch("/api/v1/documents/{id}/blocks", s.handlePatchDocumentBlocks)
|
||||
r.Put("/api/v1/documents/{id}/move", s.handleMoveDocument)
|
||||
r.Delete("/api/v1/documents/{id}", s.handleDeleteDocument)
|
||||
r.Post("/api/v1/documents/{id}/blocks/{blockId}/enhanced-task", s.handleCreateEnhancedTask)
|
||||
|
||||
// Document Sharing & Collaboration
|
||||
r.Get("/api/v1/documents/{id}/shares", s.handleGetDocShares)
|
||||
@@ -151,6 +152,7 @@ func (s *Server) Router() http.Handler {
|
||||
r.Delete("/api/v1/lists/{id}/shares/{shareId}", s.handleDeleteListShare)
|
||||
r.Post("/api/v1/lists/{id}/items", s.handleCreateListItem)
|
||||
r.Patch("/api/v1/lists/{id}/items/{itemId}", s.handleToggleListItem)
|
||||
r.Put("/api/v1/lists/{id}/items/{itemId}", s.handleUpdateListItem)
|
||||
r.Delete("/api/v1/lists/{id}/items/{itemId}", s.handleDeleteListItem)
|
||||
r.Post("/api/v1/lists/{id}/items/reorder", s.handleReorderListItems)
|
||||
r.Put("/api/v1/lists/{id}/items/reorder", s.handleReorderListItems)
|
||||
@@ -828,6 +830,9 @@ func (s *Server) handleUpdateThought(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if req.IsCompleted != nil {
|
||||
s.store.SyncDocBlockAndTask(userID, thoughtID, *req.IsCompleted)
|
||||
}
|
||||
s.wsHub.BroadcastToAll("TASK_UPDATED", updated)
|
||||
writeJSON(w, http.StatusOK, updated)
|
||||
}
|
||||
@@ -835,6 +840,7 @@ func (s *Server) handleUpdateThought(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *Server) handleDeleteThought(w http.ResponseWriter, r *http.Request) {
|
||||
userID := auth.GetUserID(r.Context())
|
||||
thoughtID := chi.URLParam(r, "id")
|
||||
s.store.UnlinkTaskFromDocBlock(userID, thoughtID)
|
||||
if err := s.store.DeleteThought(userID, thoughtID); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
@@ -997,6 +1003,30 @@ func (s *Server) handleDeleteDocument(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, map[string]bool{"deleted": true})
|
||||
}
|
||||
|
||||
func (s *Server) handleCreateEnhancedTask(w http.ResponseWriter, r *http.Request) {
|
||||
userID := auth.GetUserID(r.Context())
|
||||
docID := chi.URLParam(r, "id")
|
||||
blockID := chi.URLParam(r, "blockId")
|
||||
|
||||
var req struct {
|
||||
Content string `json:"content"`
|
||||
Checked bool `json:"checked"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Ungültiges JSON")
|
||||
return
|
||||
}
|
||||
|
||||
thought, err := s.store.CreateTaskFromDocBlock(userID, docID, blockID, req.Content, req.Checked)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
s.wsHub.BroadcastToUser(userID, "TASK_CREATED", thought)
|
||||
writeJSON(w, http.StatusCreated, thought)
|
||||
}
|
||||
|
||||
// --- WORKSPACE FOLDERS ---
|
||||
|
||||
func (s *Server) handleGetFolders(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -1574,6 +1604,30 @@ func (s *Server) handleToggleListItem(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, item)
|
||||
}
|
||||
|
||||
func (s *Server) handleUpdateListItem(w http.ResponseWriter, r *http.Request) {
|
||||
listID := chi.URLParam(r, "id")
|
||||
itemID := chi.URLParam(r, "itemId")
|
||||
|
||||
var req struct {
|
||||
Content *string `json:"content"`
|
||||
Category *string `json:"category"`
|
||||
Quantity *string `json:"quantity"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Ungültiges JSON")
|
||||
return
|
||||
}
|
||||
|
||||
item, err := s.store.UpdateListItem(itemID, req.Content, req.Category, req.Quantity)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
s.wsHub.BroadcastListEvent(listID, "ITEM_UPDATED", item)
|
||||
writeJSON(w, http.StatusOK, item)
|
||||
}
|
||||
|
||||
func (s *Server) handleDeleteListItem(w http.ResponseWriter, r *http.Request) {
|
||||
listID := chi.URLParam(r, "id")
|
||||
itemID := chi.URLParam(r, "itemId")
|
||||
|
||||
+24
-10
@@ -1034,7 +1034,7 @@ func TestDecoupledFolderSharingAPI(t *testing.T) {
|
||||
}
|
||||
|
||||
// 7. Synchronous contents check:
|
||||
// User B moves the document into a subfolder: "MeineOrdner/TeamProjekt/Archiv"
|
||||
// User B (read-only recipient) cannot move documents within the shared folder:
|
||||
moveDocPayload := map[string]string{
|
||||
"folderPath": "MeineOrdner/TeamProjekt/Archiv",
|
||||
}
|
||||
@@ -1044,22 +1044,36 @@ func TestDecoupledFolderSharingAPI(t *testing.T) {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec = httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("User B move doc into subfolder failed: %s", rec.Body.String())
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("Expected User B move doc in read-only shared folder to fail, got %d", rec.Code)
|
||||
}
|
||||
|
||||
// User A must now see the document synchronized at "TeamProjekt/Archiv"!
|
||||
req = httptest.NewRequest(http.MethodGet, "/api/v1/documents/"+createdDoc.ID, nil)
|
||||
// User A (the owner) moves the document into a subfolder: "TeamProjekt/Archiv"
|
||||
moveDocPayload = map[string]string{
|
||||
"folderPath": "TeamProjekt/Archiv",
|
||||
}
|
||||
b, _ = json.Marshal(moveDocPayload)
|
||||
req = httptest.NewRequest(http.MethodPut, "/api/v1/documents/"+createdDoc.ID+"/move", bytes.NewReader(b))
|
||||
req.Header.Set("Authorization", "Bearer "+tokenA)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec = httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("User A get doc failed: %s", rec.Body.String())
|
||||
t.Fatalf("User A move doc into subfolder failed: %s", rec.Body.String())
|
||||
}
|
||||
var docASynced domain.Document
|
||||
_ = json.Unmarshal(rec.Body.Bytes(), &docASynced)
|
||||
if docASynced.FolderPath != "TeamProjekt/Archiv" {
|
||||
t.Fatalf("expected User A to see subfolder sync at 'TeamProjekt/Archiv', got %q", docASynced.FolderPath)
|
||||
|
||||
// User B must now see the document synchronized at "MeineOrdner/TeamProjekt/Archiv"!
|
||||
req = httptest.NewRequest(http.MethodGet, "/api/v1/documents/"+createdDoc.ID, nil)
|
||||
req.Header.Set("Authorization", "Bearer "+tokenB)
|
||||
rec = httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("User B get doc failed: %s", rec.Body.String())
|
||||
}
|
||||
var docBSynced domain.Document
|
||||
_ = json.Unmarshal(rec.Body.Bytes(), &docBSynced)
|
||||
if docBSynced.FolderPath != "MeineOrdner/TeamProjekt/Archiv" {
|
||||
t.Fatalf("expected User B to see subfolder sync at 'MeineOrdner/TeamProjekt/Archiv', got %q", docBSynced.FolderPath)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -148,8 +148,10 @@ type Thought struct {
|
||||
Source string `json:"source"` // manual | voice | webhook
|
||||
Status ThoughtStatus `json:"status"`
|
||||
IsCompleted bool `json:"isCompleted"`
|
||||
ConvertedType string `json:"convertedType,omitempty"` // task | document | list_item
|
||||
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
|
||||
@@ -270,6 +272,7 @@ type DocumentBlock struct {
|
||||
Level int `json:"level"`
|
||||
Position float64 `json:"position"`
|
||||
Collapsed bool `json:"collapsed,omitempty"`
|
||||
TaskID string `json:"taskId,omitempty"`
|
||||
}
|
||||
|
||||
func CompileBlocksToMarkdown(blocks []DocumentBlock) string {
|
||||
|
||||
+201
-17
@@ -414,9 +414,10 @@ func (s *Store) migrate() error {
|
||||
// Backfill username for existing accounts where username is empty
|
||||
_, _ = s.db.Exec(`UPDATE users SET username = LOWER(SUBSTR(email, 1, INSTR(email, '@') - 1)) WHERE (username IS NULL OR username = '') AND INSTR(email, '@') > 0`)
|
||||
|
||||
// Ensure all existing document shares are strictly read-only
|
||||
// Ensure all existing document & folder shares are strictly read-only
|
||||
_, _ = s.db.Exec(`UPDATE doc_shares SET permission = 'read' WHERE permission != 'read'`)
|
||||
_, _ = s.db.Exec(`UPDATE doc_team_shares SET permission = 'read' WHERE permission != 'read'`)
|
||||
_, _ = s.db.Exec(`UPDATE workspace_folder_shares SET permission = 'read' WHERE permission != 'read'`)
|
||||
|
||||
// Default system settings (public registration disabled by default; invite or admin creation required)
|
||||
_, _ = s.db.Exec(`INSERT OR IGNORE INTO system_settings (key, value) VALUES
|
||||
@@ -911,6 +912,30 @@ func (s *Store) CreateThoughtWithDetails(userID, content, priority, source, dueD
|
||||
}
|
||||
|
||||
tags := domain.ExtractTags(content)
|
||||
if strings.EqualFold(source, "voice") {
|
||||
hasDiktat := false
|
||||
for _, tag := range tags {
|
||||
if strings.EqualFold(tag, "diktat") {
|
||||
hasDiktat = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasDiktat {
|
||||
tags = append(tags, "diktat")
|
||||
}
|
||||
}
|
||||
if strings.EqualFold(source, "webhook") {
|
||||
hasWebhook := false
|
||||
for _, tag := range tags {
|
||||
if strings.EqualFold(tag, "webhook") {
|
||||
hasWebhook = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasWebhook {
|
||||
tags = append(tags, "webhook")
|
||||
}
|
||||
}
|
||||
tagsJSON, _ := json.Marshal(tags)
|
||||
|
||||
t := &domain.Thought{
|
||||
@@ -968,6 +993,11 @@ func scanThought(rows interface {
|
||||
}
|
||||
t.Tags = []string{}
|
||||
_ = json.Unmarshal([]byte(tagsStr), &t.Tags)
|
||||
if t.ConvertedType == "document_block" && strings.Contains(t.ConvertedRefID, ":") {
|
||||
parts := strings.SplitN(t.ConvertedRefID, ":", 2)
|
||||
t.DocID = parts[0]
|
||||
t.BlockID = parts[1]
|
||||
}
|
||||
t.Subtasks = []domain.SubTask{}
|
||||
if subtasksStr != "" && subtasksStr != "null" {
|
||||
_ = json.Unmarshal([]byte(subtasksStr), &t.Subtasks)
|
||||
@@ -1137,23 +1167,23 @@ func (s *Store) UpdateThoughtTriage(userID, thoughtID, action, convertTo, target
|
||||
title = title[:72] + "…"
|
||||
}
|
||||
blocks := make([]domain.DocumentBlock, 0)
|
||||
formattedDate := domain.FormatGermanDateTime(thought.CreatedAt)
|
||||
calloutText := "Ursprünglicher Gedanke aus deiner Inbox"
|
||||
if formattedDate != "" {
|
||||
calloutText += " vom " + formattedDate
|
||||
}
|
||||
|
||||
// 17. Wenn ich eine Aufgabe zu einer Notiz mache, muss die erste Zeile das "To-Do" noch mal als "To-Do" enthalten, auch wenn es schon in der Überschrift steht. Wenn es vorher schon abgehakt war, dann den Zustand natürlich übernehmen.
|
||||
blocks = append(blocks, domain.DocumentBlock{
|
||||
ID: domain.NewID("blk"),
|
||||
Type: "callout",
|
||||
Content: calloutText,
|
||||
Type: "todo",
|
||||
Content: thought.Content,
|
||||
Checked: thought.IsCompleted,
|
||||
Level: 0,
|
||||
})
|
||||
|
||||
// 17a. Die anderen Details aus der Aufgabe sollen natürlich ebenfalls übernommen werden (Notizen, Subtasks, Fälligkeit/Priorität).
|
||||
if strings.TrimSpace(thought.Notes) != "" {
|
||||
blocks = append(blocks, domain.DocumentBlock{
|
||||
ID: domain.NewID("blk"),
|
||||
Type: "bullet",
|
||||
Content: strings.TrimSpace(thought.Notes),
|
||||
Level: 0,
|
||||
Level: 1,
|
||||
})
|
||||
}
|
||||
if len(thought.Subtasks) > 0 {
|
||||
@@ -1163,10 +1193,25 @@ func (s *Store) UpdateThoughtTriage(userID, thoughtID, action, convertTo, target
|
||||
Type: "todo",
|
||||
Content: st.Title,
|
||||
Checked: st.IsCompleted,
|
||||
Level: 0,
|
||||
Level: 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
var metaItems []string
|
||||
if thought.DueDate != "" {
|
||||
metaItems = append(metaItems, "Fällig: "+thought.DueDate)
|
||||
}
|
||||
if thought.Priority != "" && thought.Priority != "normal" {
|
||||
metaItems = append(metaItems, "Priorität: "+thought.Priority)
|
||||
}
|
||||
if len(metaItems) > 0 {
|
||||
blocks = append(blocks, domain.DocumentBlock{
|
||||
ID: domain.NewID("blk"),
|
||||
Type: "bullet",
|
||||
Content: strings.Join(metaItems, " • "),
|
||||
Level: 1,
|
||||
})
|
||||
}
|
||||
if len(blocks) == 1 {
|
||||
blocks = append(blocks, domain.DocumentBlock{
|
||||
ID: domain.NewID("blk"),
|
||||
@@ -2354,6 +2399,11 @@ func (s *Store) UpdateDocument(userID, docID string, title, icon, folderPath *st
|
||||
}
|
||||
if blocks != nil {
|
||||
_ = s.indexDocBlockTags(doc.ID, doc.UserID, doc.Blocks)
|
||||
for _, b := range doc.Blocks {
|
||||
if b.TaskID != "" {
|
||||
s.SyncTaskFromBlockCheck(doc.UserID, b.TaskID, b.Checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
return doc, nil
|
||||
}
|
||||
@@ -2888,8 +2938,8 @@ func (s *Store) MoveDocument(userID, docID, targetFolderPath string) (*domain.Do
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !shFolder.IsOwner && shFolder.Permission != "write" {
|
||||
return nil, fmt.Errorf("keine Schreibberechtigung für diesen geteilten Ordner")
|
||||
if !shFolder.IsOwner {
|
||||
return nil, fmt.Errorf("nur der Eigentümer dieses geteilten Ordners kann Dateien dorthin verschieben")
|
||||
}
|
||||
|
||||
// Update document to belong to targetSharedFolderID
|
||||
@@ -3338,8 +3388,8 @@ func (s *Store) CreateFolderShare(currentUserID, folderID, targetType, targetIde
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !f.IsOwner && f.Permission != "write" {
|
||||
return nil, fmt.Errorf("nur der Eigentümer oder Bearbeiter kann Ordner-Freigaben verwalten")
|
||||
if !f.IsOwner {
|
||||
return nil, fmt.Errorf("nur der Eigentümer kann Ordner-Freigaben verwalten")
|
||||
}
|
||||
|
||||
cleanType := strings.TrimSpace(targetType)
|
||||
@@ -3352,9 +3402,8 @@ func (s *Store) CreateFolderShare(currentUserID, folderID, targetType, targetIde
|
||||
}
|
||||
}
|
||||
|
||||
if permission != "read" && permission != "write" {
|
||||
permission = "read"
|
||||
}
|
||||
// Geteilte Ordner können ausschließlich mit Leserberechtigung geteilt werden (wie Dokumente)
|
||||
permission = "read"
|
||||
|
||||
var targetID, targetName, email, avatarURL string
|
||||
if cleanType == "team" {
|
||||
@@ -4090,6 +4139,141 @@ func (s *Store) ToggleListItem(itemID string, isCompleted bool, userID, userName
|
||||
return &it, nil
|
||||
}
|
||||
|
||||
func (s *Store) UpdateListItem(itemID string, content *string, category *string, quantity *string) (*domain.ListItem, error) {
|
||||
now := domain.NowISO()
|
||||
var it domain.ListItem
|
||||
var dInt int
|
||||
err := s.db.QueryRow(
|
||||
`SELECT id, list_id, content, category, quantity, is_completed, completed_by, completed_by_name, sort_order, created_at, completed_at
|
||||
FROM list_items WHERE id = ?`,
|
||||
itemID,
|
||||
).Scan(&it.ID, &it.ListID, &it.Content, &it.Category, &it.Quantity, &dInt, &it.CompletedBy, &it.CompletedByName, &it.SortOrder, &it.CreatedAt, &it.CompletedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
it.IsCompleted = dInt != 0
|
||||
|
||||
if content != nil {
|
||||
it.Content = *content
|
||||
}
|
||||
if category != nil {
|
||||
it.Category = *category
|
||||
}
|
||||
if quantity != nil {
|
||||
it.Quantity = *quantity
|
||||
}
|
||||
|
||||
_, err = s.db.Exec(`UPDATE list_items SET content = ?, category = ?, quantity = ? WHERE id = ?`, it.Content, it.Category, it.Quantity, itemID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, _ = s.db.Exec(`UPDATE lists SET updated_at = ? WHERE id = ?`, now, it.ListID)
|
||||
return &it, nil
|
||||
}
|
||||
|
||||
func (s *Store) CreateTaskFromDocBlock(userID, docID, blockID, content string, checked bool) (*domain.Thought, error) {
|
||||
doc, err := s.GetDocumentByID(userID, docID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t, err := s.CreateThoughtWithDetails(userID, content, "normal", "manual", "", "", "todo", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.ConvertedType = "document_block"
|
||||
t.ConvertedRefID = docID + ":" + blockID
|
||||
t.DocID = docID
|
||||
t.BlockID = blockID
|
||||
t.IsCompleted = checked
|
||||
doneInt := 0
|
||||
if checked {
|
||||
doneInt = 1
|
||||
t.KanbanStatus = "done"
|
||||
}
|
||||
_, _ = s.db.Exec(`UPDATE thoughts SET converted_type = 'document_block', converted_ref_id = ?, is_completed = ?, kanban_status = ? WHERE id = ?`,
|
||||
t.ConvertedRefID, doneInt, t.KanbanStatus, t.ID)
|
||||
|
||||
var updatedBlocks []domain.DocumentBlock
|
||||
found := false
|
||||
for _, b := range doc.Blocks {
|
||||
if b.ID == blockID {
|
||||
b.TaskID = t.ID
|
||||
b.Checked = checked
|
||||
found = true
|
||||
}
|
||||
updatedBlocks = append(updatedBlocks, b)
|
||||
}
|
||||
if found {
|
||||
_, _ = s.UpdateDocument(userID, docID, nil, nil, nil, &updatedBlocks, nil, nil)
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (s *Store) SyncDocBlockAndTask(userID, taskID string, isCompleted bool) {
|
||||
t, err := s.GetThoughtByID(userID, taskID)
|
||||
if err != nil || t == nil || t.ConvertedType != "document_block" || !strings.Contains(t.ConvertedRefID, ":") {
|
||||
return
|
||||
}
|
||||
parts := strings.SplitN(t.ConvertedRefID, ":", 2)
|
||||
docID, blockID := parts[0], parts[1]
|
||||
doc, err := s.GetDocumentByID(userID, docID)
|
||||
if err != nil || doc == nil {
|
||||
return
|
||||
}
|
||||
var updatedBlocks []domain.DocumentBlock
|
||||
found := false
|
||||
for _, b := range doc.Blocks {
|
||||
if b.ID == blockID {
|
||||
if b.Checked != isCompleted {
|
||||
b.Checked = isCompleted
|
||||
found = true
|
||||
}
|
||||
}
|
||||
updatedBlocks = append(updatedBlocks, b)
|
||||
}
|
||||
if found {
|
||||
_, _ = s.UpdateDocument(userID, docID, nil, nil, nil, &updatedBlocks, nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) UnlinkTaskFromDocBlock(userID, taskID string) {
|
||||
t, err := s.GetThoughtByID(userID, taskID)
|
||||
if err != nil || t == nil || t.ConvertedType != "document_block" || !strings.Contains(t.ConvertedRefID, ":") {
|
||||
return
|
||||
}
|
||||
parts := strings.SplitN(t.ConvertedRefID, ":", 2)
|
||||
docID, blockID := parts[0], parts[1]
|
||||
doc, err := s.GetDocumentByID(userID, docID)
|
||||
if err != nil || doc == nil {
|
||||
return
|
||||
}
|
||||
var updatedBlocks []domain.DocumentBlock
|
||||
found := false
|
||||
for _, b := range doc.Blocks {
|
||||
if b.ID == blockID {
|
||||
b.TaskID = ""
|
||||
found = true
|
||||
}
|
||||
updatedBlocks = append(updatedBlocks, b)
|
||||
}
|
||||
if found {
|
||||
_, _ = s.UpdateDocument(userID, docID, nil, nil, nil, &updatedBlocks, nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) SyncTaskFromBlockCheck(userID, taskID string, checked bool) {
|
||||
if taskID == "" {
|
||||
return
|
||||
}
|
||||
doneInt := 0
|
||||
kanban := "todo"
|
||||
if checked {
|
||||
doneInt = 1
|
||||
kanban = "done"
|
||||
}
|
||||
_, _ = s.db.Exec(`UPDATE thoughts SET is_completed = ?, kanban_status = ? WHERE id = ? AND user_id = ?`, doneInt, kanban, taskID, userID)
|
||||
}
|
||||
|
||||
func (s *Store) DeleteListItem(itemID string) (string, error) {
|
||||
var listID string
|
||||
_ = s.db.QueryRow(`SELECT list_id FROM list_items WHERE id = ?`, itemID).Scan(&listID)
|
||||
|
||||
Reference in new issue
Block a user