Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions fed/apgateway.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2025 Dima Krasner
Copyright 2025, 2026 Dima Krasner

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -239,7 +239,7 @@ func (l *Listener) handleAPGatewayGet(w http.ResponseWriter, r *http.Request) {
union all
select json(notes.object) as raw from notes
join persons on notes.author = persons.id
where notes.cid = $1 and notes.public = 1 and persons.ed25519privkey is not null
where notes.cid = $1 and notes.deleted = 0 and notes.public = 1 and persons.ed25519privkey is not null
union all
select json(outbox.activity) as raw from outbox
join persons on outbox.activity->>'$.actor' = persons.id
Expand Down
4 changes: 2 additions & 2 deletions fed/post.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2023 - 2025 Dima Krasner
Copyright 2023 - 2026 Dima Krasner

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -40,7 +40,7 @@ func (l *Listener) handlePost(w http.ResponseWriter, r *http.Request) {
slog.Info("Fetching post", "post", postID)

var note string
if err := l.DB.QueryRowContext(r.Context(), `select json(object) from notes where id = ? and public = 1`, postID).Scan(&note); err != nil && errors.Is(err, sql.ErrNoRows) {
if err := l.DB.QueryRowContext(r.Context(), `select json(object) from notes where id = ? and public = 1 and deleted = 0`, postID).Scan(&note); err != nil && errors.Is(err, sql.ErrNoRows) {
w.WriteHeader(http.StatusNotFound)
return
} else if err != nil {
Expand Down
6 changes: 3 additions & 3 deletions fed/resolve.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2023 - 2025 Dima Krasner
Copyright 2023 - 2026 Dima Krasner

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -158,7 +158,7 @@ func deleteActor(ctx context.Context, db *sql.DB, id string) {
slog.Warn("Failed to delete shares by actor", "id", id, "error", err)
}

if _, err := db.ExecContext(ctx, `delete from notes where author = ?`, id); err != nil {
if _, err := db.ExecContext(ctx, `update notes set object = jsonb_set(jsonb_remove(object, '$.name', '$.summary', '$.tag', '$.attachment'), '$.content', '[deleted]'), deleted = 1 where author = ?`, id); err != nil {
slog.Warn("Failed to delete notes by actor", "id", id, "error", err)
}

Expand All @@ -170,7 +170,7 @@ func deleteActor(ctx context.Context, db *sql.DB, id string) {
slog.Warn("Failed to delete keys for actor", "id", id, "error", err)
}

if _, err := db.ExecContext(ctx, `delete from persons where id = ?`, id); err != nil {
if _, err := db.ExecContext(ctx, `update persons set deleted = 1 where id = ?`, id); err != nil {
slog.Warn("Failed to delete actor", "id", id, "error", err)
}
}
Expand Down
6 changes: 3 additions & 3 deletions fed/resolve_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2024, 2025 Dima Krasner
Copyright 2024 - 2026 Dima Krasner

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -3728,7 +3728,7 @@ func TestResolve_FederatedActorOldCacheActorDeleted(t *testing.T) {
assert.Empty(client.Data)

var ok int
assert.NoError(db.QueryRow(`select not exists (select 1 from notes where author = 'https://0.0.0.0/user/dan') and not exists (select 1 from persons where id = 'https://0.0.0.0/user/dan')`).Scan(&ok))
assert.NoError(db.QueryRow(`select not exists (select 1 from notes where author = 'https://0.0.0.0/user/dan' and deleted = 0) and not exists (select 1 from persons where id = 'https://0.0.0.0/user/dan' and deleted = 0)`).Scan(&ok))
assert.Equal(1, ok)
}

Expand Down Expand Up @@ -3893,7 +3893,7 @@ func TestResolve_FederatedActorFirstTimeDeleted(t *testing.T) {
assert.Empty(client.Data)

var ok int
assert.NoError(db.QueryRow(`select exists (select 1 from notes where author = 'https://0.0.0.0/user/dan') and not exists (select 1 from persons where id = 'https://0.0.0.0/user/dan')`).Scan(&ok))
assert.NoError(db.QueryRow(`select exists (select 1 from notes where author = 'https://0.0.0.0/user/dan' and deleted = 0) and not exists (select 1 from persons where id = 'https://0.0.0.0/user/dan' and deleted = 0)`).Scan(&ok))
assert.Equal(1, ok)
}

Expand Down
3 changes: 2 additions & 1 deletion front/bookmark.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2024, 2025 Dima Krasner
Copyright 2024 - 2026 Dima Krasner

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -46,6 +46,7 @@ func (h *Handler) bookmark(w text.Writer, r *Request, args ...string) {
select 1 from notes
where
notes.id = $1 and
notes.deleted = 0 and
(
notes.author = $2 or
notes.public = 1 or
Expand Down
5 changes: 3 additions & 2 deletions front/communities.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2024, 2025 Dima Krasner
Copyright 2024 - 2026 Dima Krasner

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -42,7 +42,8 @@ func (h *Handler) communities(w text.Writer, r *Request, args ...string) {
persons.id = notes.author
where
persons.host = $1 and
persons.actor->>'$.type' = 'Group'
persons.actor->>'$.type' = 'Group' and
notes.deleted = 0
) u
group by
u.id
Expand Down
4 changes: 2 additions & 2 deletions front/delete.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2023 - 2025 Dima Krasner
Copyright 2023 - 2026 Dima Krasner

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -34,7 +34,7 @@ func (h *Handler) delete(w text.Writer, r *Request, args ...string) {
postID := "https://" + args[1]

var note ap.Object
if err := h.DB.QueryRowContext(r.Context, `select json(object) from notes where id = ? and author in (select id from persons where cid = ?)`, postID, ap.Canonical(r.User.ID)).Scan(&note); err != nil && errors.Is(err, sql.ErrNoRows) {
if err := h.DB.QueryRowContext(r.Context, `select json(object) from notes where id = ? and deleted = 0 and author in (select id from persons where cid = ?)`, postID, ap.Canonical(r.User.ID)).Scan(&note); err != nil && errors.Is(err, sql.ErrNoRows) {
r.Log.Warn("Attempted to delete a non-existing post", "post", postID, "error", err)
w.Error()
return
Expand Down
6 changes: 3 additions & 3 deletions front/edit.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2023 - 2025 Dima Krasner
Copyright 2023 - 2026 Dima Krasner

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -35,7 +35,7 @@ func (h *Handler) doEdit(w text.Writer, r *Request, args []string, readInput inp
postID := "https://" + args[1]

var note ap.Object
if err := h.DB.QueryRowContext(r.Context, `select json(object) from notes where id = ? and author in (select id from persons where cid = ?)`, postID, ap.Canonical(r.User.ID)).Scan(&note); errors.Is(err, sql.ErrNoRows) {
if err := h.DB.QueryRowContext(r.Context, `select json(object) from notes where id = ? and deleted = 0 and author in (select id from persons where cid = ?)`, postID, ap.Canonical(r.User.ID)).Scan(&note); errors.Is(err, sql.ErrNoRows) {
r.Log.Warn("Attempted to edit non-existing post", "post", postID, "error", err)
w.Error()
return
Expand Down Expand Up @@ -77,7 +77,7 @@ func (h *Handler) doEdit(w text.Writer, r *Request, args []string, readInput inp
}

var parent ap.Object
if err := h.DB.QueryRowContext(r.Context, `select json(object) from notes where cid = ?`, ap.Canonical(note.InReplyTo)).Scan(&parent); errors.Is(err, sql.ErrNoRows) {
if err := h.DB.QueryRowContext(r.Context, `select json(object) from notes where cid = ? and deleted = 0`, ap.Canonical(note.InReplyTo)).Scan(&parent); errors.Is(err, sql.ErrNoRows) {
r.Log.Warn("Parent post does not exist", "parent", note.InReplyTo)
} else if err != nil {
r.Log.Warn("Failed to fetch parent post", "parent", note.InReplyTo, "error", err)
Expand Down
10 changes: 7 additions & 3 deletions front/fts.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2024, 2025 Dima Krasner
Copyright 2024 - 2026 Dima Krasner

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -69,6 +69,7 @@ func (h *Handler) fts(w text.Writer, r *Request, args ...string) {
groups.actor->>'$.type' = 'Group' and exists (select 1 from shares where shares.by = groups.id and shares.note = notes.id)
where
notes.public = 1 and
notes.deleted = 0 and
notesfts.content match $1
order by rank desc
limit $2
Expand All @@ -90,6 +91,7 @@ func (h *Handler) fts(w text.Writer, r *Request, args ...string) {
notes.id = notesfts.id
where
notes.public = 1 and
notes.deleted = 0 and
notesfts.content match $1
union all
select notes.id, notes.object, notes.author, notes.inserted, rank, 1 as aud from
Expand All @@ -112,7 +114,8 @@ func (h *Handler) fts(w text.Writer, r *Request, args ...string) {
where
follows.follower = $2 and
follows.accepted = 1 and
notesfts.content match $1
notesfts.content match $1 and
notes.deleted = 0
union all
select notes.id, notes.object, notes.author, notes.inserted, rank, 0 as aud from
notesfts
Expand All @@ -124,7 +127,8 @@ func (h *Handler) fts(w text.Writer, r *Request, args ...string) {
$2 in (notes.cc0, notes.to0, notes.cc1, notes.to1, notes.cc2, notes.to2) or
(notes.to2 is not null and exists (select 1 from json_each(notes.object->'$.to') where value = $2)) or
(notes.cc2 is not null and exists (select 1 from json_each(notes.object->'$.cc') where value = $2))
)
) and
notes.deleted = 0
) u
join persons authors on
authors.id = u.author and coalesce(authors.actor->>'$.discoverable', 1)
Expand Down
4 changes: 2 additions & 2 deletions front/hashtag.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2023 - 2025 Dima Krasner
Copyright 2023 - 2026 Dima Krasner

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -32,7 +32,7 @@ func (h *Handler) hashtag(w text.Writer, r *Request, args ...string) {
func(offset int) (*sql.Rows, error) {
return h.DB.QueryContext(
r.Context,
`select json(notes.object), json(persons.actor), null, notes.inserted from notes join hashtags on notes.id = hashtags.note left join (select object->>'$.inReplyTo' as id, count(*) as count from notes where inserted >= unixepoch() - 7*24*60*60 group by object->>'$.inReplyTo') replies on notes.id = replies.id left join persons on notes.author = persons.id where notes.public = 1 and hashtags.hashtag = $1 order by replies.count desc, notes.inserted/(24*60*60) desc, notes.inserted desc limit $2 offset $3`,
`select json(notes.object), json(persons.actor), null, notes.inserted from notes join hashtags on notes.id = hashtags.note left join (select object->>'$.inReplyTo' as id, count(*) as count from notes where deleted = 0 and inserted >= unixepoch() - 7*24*60*60 group by object->>'$.inReplyTo') replies on notes.id = replies.id left join persons on notes.author = persons.id where notes.public = 1 and notes.deleted = 0 and hashtags.hashtag = $1 order by replies.count desc, notes.inserted/(24*60*60) desc, notes.inserted desc limit $2 offset $3`,
tag,
h.Config.PostsPerPage,
offset,
Expand Down
5 changes: 3 additions & 2 deletions front/hashtags.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2023 - 2025 Dima Krasner
Copyright 2023 - 2026 Dima Krasner

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -70,7 +70,8 @@ func (h *Handler) hashtags(w text.Writer, r *Request, args ...string) {
where
follows.accepted = 1 and
follows.follower like ? and
notes.inserted > unixepoch()-60*60*24*7
notes.inserted > unixepoch()-60*60*24*7 and
notes.deleted = 0
)
group by
hashtag
Expand Down
6 changes: 3 additions & 3 deletions front/local.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2023 - 2025 Dima Krasner
Copyright 2023 - 2026 Dima Krasner

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -36,7 +36,7 @@ func (h *Handler) local(w text.Writer, r *Request, args ...string) {
select notes.object, persons.actor, null as sharer, notes.inserted from persons
join notes
on notes.author = persons.id
where notes.public = 1 and persons.host = $1
where notes.public = 1 and notes.deleted = 0 and persons.host = $1
union all
select notes.object, persons.actor, sharers.actor as sharer, shares.inserted from persons sharers
join shares
Expand All @@ -45,7 +45,7 @@ func (h *Handler) local(w text.Writer, r *Request, args ...string) {
on notes.id = shares.note
join persons
on persons.id = notes.author
where notes.public = 1 and sharers.host = $1
where notes.public = 1 and notes.deleted = 0 and sharers.host = $1
)
order by inserted desc
limit $2
Expand Down
35 changes: 22 additions & 13 deletions front/outbox.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2023 - 2025 Dima Krasner
Copyright 2023 - 2026 Dima Krasner

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -55,7 +55,8 @@ func (h *Handler) userOutbox(w text.Writer, r *Request, args ...string) {
actorID := "https://" + args[1]

var actor ap.Actor
if err := h.DB.QueryRowContext(r.Context, `select json(actor) from persons where id = ?`, actorID).Scan(&actor); err != nil && errors.Is(err, sql.ErrNoRows) {
var deleted int
if err := h.DB.QueryRowContext(r.Context, `select json(actor), deleted from persons where id = ?`, actorID).Scan(&actor, &deleted); errors.Is(err, sql.ErrNoRows) {
r.Log.Info("Person was not found", "actor", actorID)
w.Status(40, "User not found")
return
Expand All @@ -79,13 +80,13 @@ func (h *Handler) userOutbox(w text.Writer, r *Request, args ...string) {
// unauthenticated users can only see public posts in a group
rows, err = h.DB.QueryContext(
r.Context,
`select json(u.object), json(authors.actor), null, max(u.inserted, coalesce(max(replies.inserted), 0)) from (
`select json(u.object), json(authors.actor), null, max(u.inserted, coalesce(max(replies.inserted) filter (where replies.deleted = 0), 0)) from (
select notes.id, notes.object, notes.author, shares.inserted from shares
join notes on notes.id = shares.note
where shares.by = $1 and notes.public = 1 and notes.object->>'$.inReplyTo' is null
where shares.by = $1 and notes.public = 1 and notes.deleted = 0 and notes.object->>'$.inReplyTo' is null
union all
select notes.id, notes.object, notes.author, notes.inserted from notes
where notes.author = $1 and notes.public = 1 and notes.object->>'$.inReplyTo' is null
where notes.author = $1 and notes.public = 1 and notes.deleted = 0 and notes.object->>'$.inReplyTo' is null
) u
join persons authors on authors.id = u.author
left join notes replies on replies.object->>'$.inReplyTo' = u.id
Expand All @@ -99,7 +100,7 @@ func (h *Handler) userOutbox(w text.Writer, r *Request, args ...string) {
// users can see public posts in a group and non-public posts if they follow the group
rows, err = h.DB.QueryContext(
r.Context,
`select json(u.object), json(authors.actor), null, max(u.inserted, coalesce(max(replies.inserted), 0)) from (
`select json(u.object), json(authors.actor), null, max(u.inserted, coalesce(max(replies.inserted) filter (where replies.deleted = 0), 0)) from (
select notes.id, notes.object, notes.author, shares.inserted from shares
join notes on notes.id = shares.note
where
Expand All @@ -108,6 +109,7 @@ func (h *Handler) userOutbox(w text.Writer, r *Request, args ...string) {
notes.public = 1 or
exists (select 1 from follows where follower = $2 and followed = $1 and accepted = 1)
) and
notes.deleted = 0 and
notes.object->>'$.inReplyTo' is null
union all
select notes.id, notes.object, notes.author, notes.inserted from notes
Expand All @@ -117,6 +119,7 @@ func (h *Handler) userOutbox(w text.Writer, r *Request, args ...string) {
notes.public = 1 or
exists (select 1 from follows where follower = $2 and followed = $1 and accepted = 1)
) and
notes.deleted = 0 and
notes.object->>'$.inReplyTo' is null
) u
join persons authors on authors.id = u.author
Expand All @@ -135,14 +138,14 @@ func (h *Handler) userOutbox(w text.Writer, r *Request, args ...string) {
`select json(object), json(actor), json(sharer), max(inserted) from (
select notes.id, persons.actor, notes.object, notes.inserted, null as sharer from notes
join persons on persons.id = $1
where notes.author = $1 and notes.public = 1
where notes.author = $1 and notes.public = 1 and notes.deleted = 0
union all
select notes.id, authors.actor, notes.object, shares.inserted, sharers.actor as by from
shares
join notes on notes.id = shares.note
join persons authors on authors.id = notes.author
join persons sharers on sharers.id = $1
where shares.by = $1 and notes.public = 1
where shares.by = $1 and notes.public = 1 and notes.deleted = 0
)
group by id
order by max(inserted) desc limit $2 offset $3`,
Expand All @@ -157,13 +160,13 @@ func (h *Handler) userOutbox(w text.Writer, r *Request, args ...string) {
`select json(object), json(actor), json(sharer), max(inserted) from (
select notes.id, persons.actor, notes.object, notes.inserted, null as sharer from notes
join persons on persons.id = notes.author
where notes.author = $1
where notes.author = $1 and notes.deleted = 0
union all
select notes.id, authors.actor, notes.object, shares.inserted, sharers.actor as by from shares
join notes on notes.id = shares.note
join persons authors on authors.id = notes.author
join persons sharers on sharers.id = $1
where shares.by = $1
where shares.by = $1 and notes.deleted = 0
)
group by id
order by max(inserted) desc limit $2 offset $3`,
Expand All @@ -178,11 +181,11 @@ func (h *Handler) userOutbox(w text.Writer, r *Request, args ...string) {
`select json(object), json(actor), json(sharer), max(inserted) from (
select notes.id, persons.actor, notes.object, notes.inserted, null as sharer from notes
join persons on persons.id = $1
where notes.author = $1 and notes.public = 1
where notes.author = $1 and notes.public = 1 and notes.deleted = 0
union
select notes.id, persons.actor, notes.object, notes.inserted, null as sharer from notes
join persons on persons.id = $1
where (
where notes.deleted = 0 and (
notes.author = $1 and (
$2 in (notes.cc0, notes.to0, notes.cc1, notes.to1, notes.cc2, notes.to2) or
(notes.to2 is not null and exists (select 1 from json_each(notes.object->'$.to') where value = $2)) or
Expand All @@ -197,6 +200,7 @@ func (h *Handler) userOutbox(w text.Writer, r *Request, args ...string) {
(notes.cc2 is not null and exists (select 1 from json_each(notes.object->'$.cc') where value = persons.actor->>'$.followers'))
join persons authors on authors.id = $1
where notes.public = 0 and
notes.deleted = 0 and
notes.author = $1 and
persons.id = $1 and
exists (select 1 from follows where follower = $2 and followed = $1 and accepted = 1)
Expand All @@ -206,7 +210,7 @@ func (h *Handler) userOutbox(w text.Writer, r *Request, args ...string) {
join notes on notes.id = shares.note
join persons authors on authors.id = notes.author
join persons sharers on sharers.id = $1
where shares.by = $1 and notes.public = 1
where shares.by = $1 and notes.public = 1 and notes.deleted = 0
)
group by id
order by max(inserted) desc limit $3 offset $4`,
Expand Down Expand Up @@ -242,6 +246,11 @@ func (h *Handler) userOutbox(w text.Writer, r *Request, args ...string) {
w.Title(displayName)
}

if deleted == 1 {
w.Text("[Deleted]")
w.Empty()
}

if offset == 0 && len(actor.Icon) > 0 && actor.Icon[0].URL != "" {
w.Link(actor.Icon[0].URL, "Avatar")
} else if offset == 0 {
Expand Down
Loading
Loading