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 accept invite handler
John Luther
3 weeks ago
8eb3d482
7b8e49c0
+88
4 changed files
expand all
collapse all
unified
split
cmd
server
main.go
internal
atproto
xrpc
client.go
db
db.go
handler
handler.go
+1
cmd/server/main.go
reviewed
···
105
105
mux.HandleFunc("PUT /api/docs/{rkey}/autosave", h.APIDocumentAutoSave)
106
106
mux.HandleFunc("DELETE /api/docs/{rkey}", h.APIDocumentDelete)
107
107
mux.HandleFunc("POST /api/docs/{rkey}/invite", h.DocumentInvite)
108
108
+
mux.HandleFunc("GET /docs/{rkey}/accept", h.AcceptInvite)
108
109
109
110
// Middleware stack
110
111
stack := middleware.Logger(
+22
internal/atproto/xrpc/client.go
reviewed
···
291
291
}
292
292
return nil
293
293
}
294
294
+
295
295
+
const collectionDocument = "com.diffdown.document"
296
296
+
297
297
+
// GetDocument fetches a document by its rkey.
298
298
+
func (c *Client) GetDocument(rkey string) (*model.Document, error) {
299
299
+
value, _, err := c.GetRecord(c.session.DID, collectionDocument, rkey)
300
300
+
if err != nil {
301
301
+
return nil, err
302
302
+
}
303
303
+
304
304
+
var doc model.Document
305
305
+
if err := json.Unmarshal(value, &doc); err != nil {
306
306
+
return nil, fmt.Errorf("unmarshal document: %w", err)
307
307
+
}
308
308
+
doc.RKey = rkey
309
309
+
return &doc, nil
310
310
+
}
311
311
+
312
312
+
// PutDocument creates or updates a document.
313
313
+
func (c *Client) PutDocument(rkey string, doc *model.Document) (string, string, error) {
314
314
+
return c.PutRecord(collectionDocument, rkey, doc)
315
315
+
}
+5
internal/db/db.go
reviewed
···
175
175
}
176
176
return &invite, nil
177
177
}
178
178
+
179
179
+
func (db *DB) DeleteInvite(token string) error {
180
180
+
_, err := db.Exec(`DELETE FROM invites WHERE token = ?`, token)
181
181
+
return err
182
182
+
}
+60
internal/handler/handler.go
reviewed
···
490
490
h.jsonResponse(w, map[string]string{"inviteLink": inviteLink}, http.StatusOK)
491
491
}
492
492
493
493
+
// AcceptInvite handles an invite acceptance.
494
494
+
func (h *Handler) AcceptInvite(w http.ResponseWriter, r *http.Request) {
495
495
+
user := h.currentUser(r)
496
496
+
if user == nil {
497
497
+
http.Redirect(w, r, "/auth/login", http.StatusSeeOther)
498
498
+
return
499
499
+
}
500
500
+
501
501
+
rKey := r.PathValue("rkey")
502
502
+
inviteToken := r.URL.Query().Get("invite")
503
503
+
if inviteToken == "" {
504
504
+
http.Error(w, "Invalid invite", http.StatusBadRequest)
505
505
+
return
506
506
+
}
507
507
+
508
508
+
invite, err := collaboration.ValidateInvite(h.DB, inviteToken, rKey)
509
509
+
if err != nil {
510
510
+
http.Error(w, err.Error(), http.StatusBadRequest)
511
511
+
return
512
512
+
}
513
513
+
514
514
+
session, err := h.DB.GetATProtoSession(user.ID)
515
515
+
if err != nil || session == nil {
516
516
+
http.Redirect(w, r, "/auth/atproto", http.StatusSeeOther)
517
517
+
return
518
518
+
}
519
519
+
520
520
+
client, err := h.xrpcClient(user.ID)
521
521
+
if err != nil {
522
522
+
log.Printf("AcceptInvite: xrpc client: %v", err)
523
523
+
http.Error(w, "Failed to connect to ATProto", http.StatusInternalServerError)
524
524
+
return
525
525
+
}
526
526
+
527
527
+
doc, err := client.GetDocument(rKey)
528
528
+
if err != nil {
529
529
+
http.Error(w, "Document not found", http.StatusNotFound)
530
530
+
return
531
531
+
}
532
532
+
533
533
+
for _, c := range doc.Collaborators {
534
534
+
if c == session.DID {
535
535
+
http.Redirect(w, r, "/docs/"+rKey, http.StatusSeeOther)
536
536
+
return
537
537
+
}
538
538
+
}
539
539
+
540
540
+
doc.Collaborators = append(doc.Collaborators, session.DID)
541
541
+
_, _, err = client.PutDocument(rKey, doc)
542
542
+
if err != nil {
543
543
+
log.Printf("AcceptInvite: add collaborator: %v", err)
544
544
+
http.Error(w, "Failed to add collaborator", http.StatusInternalServerError)
545
545
+
return
546
546
+
}
547
547
+
548
548
+
h.DB.DeleteInvite(invite.Token)
549
549
+
550
550
+
http.Redirect(w, r, "/docs/"+rKey, http.StatusSeeOther)
551
551
+
}
552
552
+
493
553
// --- API: Render markdown ---
494
554
495
555
func (h *Handler) APIRender(w http.ResponseWriter, r *http.Request) {