tangled
alpha
login
or
join now
diffdown.com
/
diffdown-app
0
fork
atom
Diffdown is a real-time collaborative Markdown editor/previewer built on the AT Protocol
diffdown.com
0
fork
atom
overview
issues
10
pulls
pipelines
feat: add document invite handler
John Luther
3 weeks ago
470fee2d
91b95e24
+79
2 changed files
expand all
collapse all
unified
split
cmd
server
main.go
internal
handler
handler.go
+1
cmd/server/main.go
reviewed
···
100
100
mux.HandleFunc("POST /api/docs/{rkey}/save", h.APIDocumentSave)
101
101
mux.HandleFunc("PUT /api/docs/{rkey}/autosave", h.APIDocumentAutoSave)
102
102
mux.HandleFunc("DELETE /api/docs/{rkey}", h.APIDocumentDelete)
103
103
+
mux.HandleFunc("POST /api/docs/{rkey}/invite", h.DocumentInvite)
103
104
104
105
// Middleware stack
105
106
stack := middleware.Logger(
+78
internal/handler/handler.go
reviewed
···
6
6
"html/template"
7
7
"log"
8
8
"net/http"
9
9
+
"os"
9
10
"regexp"
10
11
"strings"
11
12
"time"
12
13
13
14
"github.com/limeleaf/diffdown/internal/atproto/xrpc"
14
15
"github.com/limeleaf/diffdown/internal/auth"
16
16
+
"github.com/limeleaf/diffdown/internal/collaboration"
15
17
"github.com/limeleaf/diffdown/internal/db"
16
18
"github.com/limeleaf/diffdown/internal/model"
17
19
"github.com/limeleaf/diffdown/internal/render"
···
419
421
}
420
422
421
423
h.jsonResponse(w, map[string]string{"status": "ok"})
424
424
+
}
425
425
+
426
426
+
// DocumentInvite creates an invite link for a document.
427
427
+
func (h *Handler) DocumentInvite(w http.ResponseWriter, r *http.Request) {
428
428
+
user := h.currentUser(r)
429
429
+
if user == nil {
430
430
+
http.Redirect(w, r, "/auth/login", http.StatusSeeOther)
431
431
+
return
432
432
+
}
433
433
+
434
434
+
rkey := r.PathValue("rkey")
435
435
+
if rkey == "" {
436
436
+
http.Error(w, "Invalid document", http.StatusBadRequest)
437
437
+
return
438
438
+
}
439
439
+
440
440
+
client, err := h.xrpcClient(user.ID)
441
441
+
if err != nil {
442
442
+
log.Printf("DocumentInvite: xrpc client: %v", err)
443
443
+
h.render(w, "document_edit.html", PageData{Error: "Not authenticated with ATProto"})
444
444
+
return
445
445
+
}
446
446
+
447
447
+
value, _, err := client.GetRecord(client.DID(), collectionDocument, rkey)
448
448
+
if err != nil {
449
449
+
http.Error(w, "Document not found", http.StatusNotFound)
450
450
+
return
451
451
+
}
452
452
+
453
453
+
doc := &model.Document{}
454
454
+
if err := json.Unmarshal(value, doc); err != nil {
455
455
+
http.Error(w, "Invalid document", http.StatusInternalServerError)
456
456
+
return
457
457
+
}
458
458
+
doc.RKey = rkey
459
459
+
460
460
+
session, err := h.DB.GetATProtoSession(user.ID)
461
461
+
if err != nil || session == nil {
462
462
+
http.Error(w, "Unauthorized", http.StatusForbidden)
463
463
+
return
464
464
+
}
465
465
+
466
466
+
ownerDID := strings.TrimPrefix(doc.URI, "at://")
467
467
+
if idx := strings.Index(ownerDID, "/"); idx > 0 {
468
468
+
ownerDID = ownerDID[:idx]
469
469
+
}
470
470
+
if ownerDID == "" || session.DID != ownerDID {
471
471
+
http.Error(w, "Unauthorized", http.StatusForbidden)
472
472
+
return
473
473
+
}
474
474
+
475
475
+
if len(doc.Collaborators) >= 5 {
476
476
+
http.Error(w, "Maximum collaborators reached", http.StatusBadRequest)
477
477
+
return
478
478
+
}
479
479
+
480
480
+
invite, err := collaboration.CreateInvite(h.DB, rkey, session.DID)
481
481
+
if err != nil {
482
482
+
log.Printf("DocumentInvite: create invite: %v", err)
483
483
+
http.Error(w, "Failed to create invite", http.StatusInternalServerError)
484
484
+
return
485
485
+
}
486
486
+
487
487
+
baseURL := os.Getenv("BASE_URL")
488
488
+
if baseURL == "" {
489
489
+
baseURL = "http://127.0.0.1:8080"
490
490
+
}
491
491
+
inviteLink := fmt.Sprintf("%s/doc/%s?invite=%s", baseURL, rkey, invite.Token)
492
492
+
h.render(w, "document_edit.html", PageData{
493
493
+
Title: "Edit " + doc.Title,
494
494
+
User: user,
495
495
+
Content: map[string]interface{}{
496
496
+
"document": doc,
497
497
+
"inviteLink": inviteLink,
498
498
+
},
499
499
+
})
422
500
}
423
501
424
502
// --- API: Render markdown ---