dimkr / tootik

A federated nanoblogging service with a Gemini frontend.
gemini://hd.206267.xyz
Apache License 2.0
107 stars 4 forks source link
activitypub activitypub-server fediverse finger finger-protocol gemini gemini-protocol gemini-server go golang gopher gopher-protocol social social-media social-network sqlite
          ..    .        ..                                        ..
 ...            .       ....                     ...
 ...   .     .   .   .   ... ..      .  .     .  . ...                 ...
 ..    .  .    ..   .         .    .. .       .. ..     .                   .
 .,     .               . . . ..             .  ..                  .        .
 .                .     . .. .. .... .       .  .  .               ..  ..
            .              ..   ...  .       .    .   .   .        ..  .   .
 .   .        .   ..        .    ..             ...' .. .          .   .    . .
 . .              . .    .  __     .     .__  _ __ ,; .'. .  .     ....
    . .          .         / /____  ___  /./_(_) /__  .'  ..         . .  . .
  ..      ... .    .  .   /.__/ _ \/ _ \/ __/./  '_/.   .       .. .   .    .
  .'   ...  .             \__/\___/\___/\__/_/_/\_\              .  .  . .
      .         .    .    . .    .    ...     ... .           .      ..
 ..  .. .   . .... ..  .  .         ..   .  .     .          .  .  ... ....' .
   ...  .      .   .  .. .  ... ...      . ..   ..         .,..    .....
 .   ..   ......             . .''.  .  ..          .         . .  ...
 ' .      .. ..  ..     . . ... ......::.   ..       .,.       .  .. ....    ..
 . ....  . .....     .  .. .  . ... . .,'.   .        ..          ,..  ..
 . .    .  .  . ..   .  .   .. .  .     ..     ..  .  . .       . . .        .'
   .  ....   '...                ...    . .  ..  .     ...     . '.   '     ...

# localhost.localdomain:8443

Welcome, fedinaut! localhost.localdomain:8443 is an instance of tootik, a federated nanoblogging service.

────

📻 My feed
📞 Mentions
⚡️ Followed users
😈 My profile
📡 Local feed
🏕️ Communities
🔥 Hashtags
🔭 View profile
🔎 Search posts
📣 New post
⚙️ Settings
📊 Status
🛟 Help

Latest release Build status Go Reference

Overview

tootik is a text-based social network.

tootik is federated: users can join an existing server or set up their own instance. A tootik user can interact with others on the same instance, users on other tootik instances, Mastodon users, Lemmy users and users of other ActivityPub-compatible server.

Unlike other social networks, tootik doesn't have a browser-based interface or an app: instead, its minimalistic, text-based interface is served over Gemini:

                         Gemini           ActivityPub (HTTPS)
                           →                     ⇄
 ┏━━━━━━━━━━━━━━━━━━━━━━━┓   ┏━━━━━━━━━━━━━━━━━┓   ┌─────────────────────────┐
 ┃  Bob's Gemini client  ┣━━━┫ tootik instance ┠─┬─┤ Another tootik instance │
 ┣━━━━━━━━━━━━━━━━━━━━━━━┫   ┣━━━━━━━━━━━━━━━━━┫ │ └─────────────────────────┘
 ┃2024-01-01 alice       ┃   ┃$ ./tootik ...   ┃ │ ┌────────────────┐
 ┃> Hi @bob and @carol!  ┃   ┗━┳━━━━━━━━━━━━━━━┛ ├─┤ Something else │
 ┃...                    ┃     ┃                 │ └────────────────┘
 ┗━━━━━━━━━━━━━━━━━━━━━━━┛     ┃                 │ ┌───────────────────┐
               ┏━━━━━━━━━━━━━━━┻━━━━━━━┓         └─┤ Mastodon instance ├─┐
               ┃ Alice's Gemini client ┃           └───────────────────┘ │
               ┣━━━━━━━━━━━━━━━━━━━━━━━┫              ┌──────────────────┴────┐
               ┃2024-01-01 bob         ┃              │  Carol's web browser  │
               ┃> Hi @alice!           ┃              ├───────────────────────┤
               ┃...                    ┃              │╔═╗ alice              │
               ┗━━━━━━━━━━━━━━━━━━━━━━━┛              │╚═╝             17h ago│
                                                      │Hi @bob and @carol!    │
                                                      │                       │
                                                      │  ╔═╗ bob              │
                                                      │  ╚═╝           16h ago│
                                                      │  Hi @alice!           │
                                                      ├───────────────────────┤
                                                      │┌────────────┐┌───────┐│
                                                      ││ Hola       ││Publish││
                                                      │└────────────┘└───────┘│
                                                      └───────────────────────┘

This makes tootik lightweight, private and accessible:

Features

Using tootik

You can join an existing instance or set up your own.

Building

go generate ./migrations

Then:

go build ./cmd/tootik -tags fts5

or, to build a static executable:

go build -tags netgo,sqlite_omit_load_extension,fts5 -ldflags "-linkmode external -extldflags -static" ./cmd/tootik

Architecture

┏━━━━━━━┓ ┏━━━━━━━━┓ ┏━━━━━━━━━┓ ┏━━━━━━━━━┓
┃ notes ┃ ┃ shares ┃ ┃ persons ┃ ┃ follows ┃
┣━━━━━━━┫ ┣━━━━━━━━┫ ┣━━━━━━━━━┫ ┣━━━━━━━━━┫
┃object ┃ ┃note    ┃ ┃actor    ┃ ┃follower ┃
┃author ┃ ┃by      ┃ ┃...      ┃ ┃followed ┃
┃...    ┃ ┃...     ┃ ┃         ┃ ┃...      ┃
┗━━━━━━━┛ ┗━━━━━━━━┛ ┗━━━━━━━━━┛ ┗━━━━━━━━━┛

Most user-visible data is stored in 4 tables in tootik's database:

  1. notes, which contains Object objects that represent posts
  2. shares, which records "user A shared post B" relationships
  3. persons, which contains Actor objects that represent users
  4. follows, which records "user A follows user B" relationships

notes.author, shares.by, follows.follower and follows.followed point to rows in persons.

shares.note points to a row in notes.

┌───────┐ ┌────────┐ ┌─────────┐ ┌─────────┐ ┏━━━━━━━━┓ ┏━━━━━━━━┓
│ notes │ │ shares │ │ persons │ │ follows │ ┃ outbox ┃ ┃ inbox  ┃
├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ┣━━━━━━━━┫ ┣━━━━━━━━┫
│object │ │note    │ │actor    │ │follower │ ┃activity┃ ┃activity┃
│author │ │by      │ │...      │ │followed │ ┃sender  ┃ ┃sender  ┃
│...    │ │...     │ │         │ │...      │ ┃...     ┃ ┃...     ┃
└───────┘ └────────┘ └─────────┘ └─────────┘ ┗━━━━━━━━┛ ┗━━━━━━━━┛

Federation happens through two tables, inbox and outbox. Both contain Activity objects that represent actions performed by the users in persons.

inbox contains activities by users on other servers, while outbox contains activities of local users.

   ┏━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━━┓
   ┃ gmi.Wrap ┣━┫ gemini.Listener ┃
   ┗━━━━━━━━━━┛ ┗━━━━━━━━┳━━━━━━━━┛
                ┏━━━━━━━━┻━━━━━━━━━━┓
                ┃   front.Handler   ┃
                ┗━━━━━━━━━┳━━━━━━━━━┛
┌───────┐ ┌────────┐ ┌────┸────┐ ┌─────────┐ ┌────────┐ ┌────────┐
│ notes │ │ shares │ │ persons │ │ follows │ │ outbox │ │ inbox  │
├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ├────────┤ ├────────┤
│object │ │note    │ │actor    │ │follower │ │activity│ │activity│
│author │ │by      │ │...      │ │followed │ │sender  │ │sender  │
│...    │ │...     │ │         │ │...      │ │...     │ │...     │
└───────┘ └────────┘ └─────────┘ └─────────┘ └────────┘ └────────┘

gemini.Listener is a Gemini server that handles requests using Handler. It adds rows to persons during new user registration and changes rows when users change properties like their display name.

gemini.Listener provides Handler with a writer that builds a Gemini response and asynchronously sends it to the client in chunks, while Handler continues to handle the request and append more lines to the page.

   ┌──────────┐ ┌─────────────────┐
   │ gmi.Wrap ├─┤ gemini.Listener │
   └──────────┘ └────────┬────────┘
                ┌────────┴──────────┐
    ┏━━━━━━━━━━━┥   front.Handler   │
    ┃           └┰────────┬───────┰─┘
┌───┸───┐ ┌──────┸─┐ ┌────┴────┐ ┌┸────────┐ ┌────────┐ ┌────────┐
│ notes │ │ shares │ │ persons │ │ follows │ │ outbox │ │ inbox  │
├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ├────────┤ ├────────┤
│object │ │note    │ │actor    │ │follower │ │activity│ │activity│
│author │ │by      │ │...      │ │followed │ │sender  │ │sender  │
│...    │ │...     │ │         │ │...      │ │...     │ │...     │
└───────┘ └────────┘ └─────────┘ └─────────┘ └────────┘ └────────┘

In addition, Gemini requests can:

   ┌──────────┐ ┌─────────────────┐
   │ gmi.Wrap ├─┤ gemini.Listener │
   └──────────┘ └────────┬────────┘
                ┌────────┴──────────┐
    ┌───────────┤   front.Handler   ┝━━━━━━━━━━┓
    │           └┬────────┬───────┬─┘          ┃
┌───┴───┐ ┌──────┴─┐ ┌────┴────┐ ┌┴────────┐ ┌─┸──────┐ ┌────────┐
│ notes │ │ shares │ │ persons │ │ follows │ │ outbox │ │ inbox  │
├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ├────────┤ ├────────┤
│object │ │note    │ │actor    │ │follower │ │activity│ │activity│
│author │ │by      │ │...      │ │followed │ │sender  │ │sender  │
│...    │ │...     │ │         │ │...      │ │...     │ │...     │
└───────┘ └────────┘ └─────────┘ └─────────┘ └────────┘ └────────┘

User actions like post creation or deletion are recorded as Activity objects written to outbox.

                                      ┏━━━━━━━━━━━━━━━┓
   ┌──────────┐ ┌─────────────────┐   ┃ outbox.Mover  ┃
   │ gmi.Wrap ├─┤ gemini.Listener │   ┃ outbox.Poller ┃
   └──────────┘ └────────┬────────┘   ┃ fed.Syncer    ┃
                ┌────────┴──────────┐ ┗━━━┳━━━━━┳━━━━━┛
    ┌───────────┤   front.Handler   ├─────╂────┐┃
    │           └┬────────┬───────┬─┘     ┃    │┃
┌───┴───┐ ┌──────┴─┐ ┌────┴────┐ ┌┴───────┸┐ ┌─┴┸─────┐ ┌────────┐
│ notes │ │ shares │ │ persons │ │ follows │ │ outbox │ │ inbox  │
├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ├────────┤ ├────────┤
│object │ │note    │ │actor    │ │follower │ │activity│ │activity│
│author │ │by      │ │...      │ │followed │ │sender  │ │sender  │
│...    │ │...     │ │         │ │...      │ │...     │ │...     │
└───────┘ └────────┘ └─────────┘ └─────────┘ └────────┘ └────────┘

tootik may perform automatic actions and push additional activities to outbox, on behalf of the user:

  1. Follow the new account and unfollow the old one, if a followed user moved their account
  2. Update poll results for polls published by the user, and send the new results to followers
  3. Handle disagreement between follows rows for this user and what other servers know
                                      ┌───────────────┐
   ┌──────────┐ ┌─────────────────┐   │ outbox.Mover  │
   │ gmi.Wrap ├─┤ gemini.Listener │   │ outbox.Poller │
   └──────────┘ └────────┬────────┘   │ fed.Syncer    │
                ┌────────┴──────────┐ └───┬─────┬─────┘
    ┌───────────┤   front.Handler   ├─────┼────┐│
    │           └┬────────┬───────┬─┘     │    ││
┌───┴───┐ ┌──────┴─┐ ┌────┴────┐ ┌┴───────┴┐ ┌─┴┴─────┐ ┌────────┐
│ notes │ │ shares │ │ persons │ │ follows │ │ outbox │ │ inbox  │
├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ├────────┤ ├────────┤
│object │ │note    │ │actor    │ │follower │ │activity│ │activity│
│author │ │by      │ │...      │ │followed │ │sender  │ │sender  │
│...    │ │...     │ │         │ │...      │ │...     │ │...     │
└───────┘ └────────┘ └─────────┘ └───────┰─┘ └┰───────┘ └────────┘
                                        ┏┻━━━━┻━━━━━┓
                                        ┃ fed.Queue ┃
                                        ┗━━━━━━━━━━━┛

fed.Queue polls outbox and delivers these activities to followers on other servers. It uses the deliveries table to track delivery progress and retry failed deliveries.

                                      ┌───────────────┐
   ┌──────────┐ ┌─────────────────┐   │ outbox.Mover  │
   │ gmi.Wrap ├─┤ gemini.Listener │   │ outbox.Poller │
   └──────────┘ └────────┬────────┘   │ fed.Syncer    │
                ┌────────┴──────────┐ └───┬─────┬─────┘
    ┌───────────┤   front.Handler   ├─────┼────┐│
    │           └┬────────┬───────┬─┘     │    ││
┌───┴───┐ ┌──────┴─┐ ┌────┴────┐ ┌┴───────┴┐ ┌─┴┴─────┐ ┌────────┐
│ notes │ │ shares │ │ persons │ │ follows │ │ outbox │ │ inbox  │
├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ├────────┤ ├────────┤
│object │ │note    │ │actor    │ │follower │ │activity│ │activity│
│author │ │by      │ │...      │ │followed │ │sender  │ │sender  │
│...    │ │...     │ │         │ │...      │ │...     │ │...     │
└───────┘ └────────┘ └────┰────┘ └───────┬─┘ └┬───────┘ └────────┘
                          ┃             ┌┴────┴─────┐
                  ┏━━━━━━━┻━━━━━━┓ ┏━━━━┥ fed.Queue │
                  ┃ fed.Resolver ┣━┛    └───────────┘
                  ┗━━━━━━━━━━━━━━┛

Resolver is responsible for fetching Actors that represent users of other servers, using user@domain pairs and WebFinger. The fetched objects are cached in persons, and contain properties like the user's inbox URL and public key.

fed.Queue uses Resolver to make a list of unique inbox URLs each activity should be delivered to. If this is a wide delivery (a public post or a post to followers) and two recipients share the same sharedInbox, fed.Queue delivers the activity to both recipients in a single request.

                                      ┌───────────────┐
   ┌──────────┐ ┌─────────────────┐   │ outbox.Mover  │
   │ gmi.Wrap ├─┤ gemini.Listener │   │ outbox.Poller │
   └──────────┘ └────────┬────────┘   │ fed.Syncer    │
                ┌────────┴──────────┐ └───┬─────┬─────┘ ┏━━━━━━━━━━━━━━┓
    ┌───────────┤   front.Handler   ├─────┼────┐│    ┏━━┫ fed.Listener ┣━━━━━━┓
    │           └┬────────┬───────┬─┘     │    ││    ┃  ┗━━━━━┳━━━━━━━━┛      ┃
┌───┴───┐ ┌──────┴─┐ ┌────┴────┐ ┌┴───────┴┐ ┌─┴┴────┸┐ ┌─────┸──┐            ┃
│ notes │ │ shares │ │ persons │ │ follows │ │ outbox │ │ inbox  │            ┃
├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ├────────┤ ├────────┤            ┃
│object │ │note    │ │actor    │ │follower │ │activity│ │activity│            ┃
│author │ │by      │ │...      │ │followed │ │sender  │ │sender  │            ┃
│...    │ │...     │ │         │ │...      │ │...     │ │...     │            ┃
└───────┘ └────────┘ └────┬────┘ └───────┬─┘ └┬───────┘ └────────┘            ┃
                          │             ┌┴────┴─────┐                         ┃
                  ┌───────┴──────┐ ┌────┤ fed.Queue │                         ┃
                  │ fed.Resolver ├─┘    └───────────┘                         ┃
                  └───────┰──────┘                                            ┃
                          ┃                                                   ┃
                          ┃                                                   ┃
                          ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

Requests from other servers are handled by fed.Listener, a HTTP server.

It extracts the signature and key ID from a request using httpsig.Extract, uses Resolver to fetch the public key if needed, validates the request using Verify and inserts the received Activity object into inbox.

In addition, fed.Listener allows other servers to fetch public activity (like public posts) from outbox, so they can fetch some past activity by a newly-followed user.

                                      ┌───────────────┐
   ┌──────────┐ ┌─────────────────┐   │ outbox.Mover  │
   │ gmi.Wrap ├─┤ gemini.Listener │   │ outbox.Poller │
   └──────────┘ └────────┬────────┘   │ fed.Syncer    │
                ┌────────┴──────────┐ └───┬─────┬─────┘ ┌──────────────┐
    ┌───────────┤   front.Handler   ├─────┼────┐│    ┌──┤ fed.Listener ├──────┐
    │           └┬────────┬───────┬─┘     │    ││    │  └─────┬────────┘      │
┌───┴───┐ ┌──────┴─┐ ┌────┴────┐ ┌┴───────┴┐ ┌─┴┴────┴┐ ┌─────┴──┐            │
│ notes │ │ shares │ │ persons │ │ follows │ │ outbox │ │ inbox  │            │
├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ├────────┤ ├────────┤            │
│object │ │note    │ │actor    │ │follower │ │activity│ │activity│            │
│author │ │by      │ │...      │ │followed │ │sender  │ │sender  │            │
│...    │ │...     │ │         │ │...      │ │...     │ │...     │            │
└───┰───┘ └───┰────┘ └────┬────┘ └────┰──┬─┘ └┬───────┘ └──────┰─┘            │
    ┃         ┃           │           ┃ ┌┴────┴─────┐     ┏━━━━┻━━━━━━━━┓     │
    ┃         ┃   ┌───────┴──────┐ ┌──╂─┤ fed.Queue │     ┃ inbox.Queue ┃     │
    ┃         ┃   │ fed.Resolver ├─┘  ┃ └───────────┘     ┗━┳━┳━┳━━━━━━━┛     │
    ┃         ┃   └───────┬──────┘    ┗━━━━━━━━━━━━━━━━━━━━━┛ ┃ ┃             │
    ┃         ┗━━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃             │
    ┗━━━━━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛             │
                          └───────────────────────────────────────────────────┘

Once inserted into inbox, inbox.Queue processes the received activities:

                                      ┌───────────────┐
   ┌──────────┐ ┌─────────────────┐   │ outbox.Mover  │
   │ gmi.Wrap ├─┤ gemini.Listener │   │ outbox.Poller │
   └──────────┘ └────────┬────────┘   │ fed.Syncer    │
                ┌────────┴──────────┐ └───┬─────┬─────┘ ┌──────────────┐
    ┌───────────┤   front.Handler   ├─────┼────┐│    ┌──┤ fed.Listener ├──────┐
    │           └┬────────┬───────┬─┘     │    ││    │  └─────┬────────┘      │
┌───┴───┐ ┌──────┴─┐ ┌────┴────┐ ┌┴───────┴┐ ┌─┴┴────┴┐ ┌─────┴──┐            │
│ notes │ │ shares │ │ persons │ │ follows │ │ outbox │ │ inbox  │            │
├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ├────────┤ ├────────┤            │
│object │ │note    │ │actor    │ │follower │ │activity│ │activity│            │
│author │ │by      │ │...      │ │followed │ │sender  │ │sender  │            │
│...    │ │...     │ │         │ │...      │ │...     │ │...     │            │
└───┬───┘ └───┬────┘ └────┬────┘ └────┬──┬─┘ └┬──────┰┘ └──────┬─┘            │
    │         │           │           │ ┌┴────┴─────┐┃    ┌────┴────────┐     │
    │         │   ┌───────┴──────┐ ┌──┼─┤ fed.Queue │┗━━━━┥ inbox.Queue │     │
    │         │   │ fed.Resolver ├─┘  │ └───────────┘     └─┬─┬─┬───────┘     │
    │         │   └───────┬──────┘    └─────────────────────┘ │ │             │
    │         └───────────┼───────────────────────────────────┘ │             │
    └─────────────────────┼─────────────────────────────────────┘             │
                          └───────────────────────────────────────────────────┘

Sometimes, a received or newly created local Activity is forwarded to the followers of a local user:

                                      ┌───────────────┐
   ┌──────────┐ ┌─────────────────┐   │ outbox.Mover  │
   │ gmi.Wrap ├─┤ gemini.Listener │   │ outbox.Poller │
   └──────────┘ └────────┬────────┘   │ fed.Syncer    │
                ┌────────┴──────────┐ └───┬─────┬─────┘ ┌──────────────┐
    ┌───────────┤   front.Handler   ├─────┼────┐│    ┌──┤ fed.Listener ├──────┐
    │           └┬────────┬───────┬─┘     │    ││    │  └─────┬────────┘      │
┌───┴───┐ ┌──────┴─┐ ┌────┴────┐ ┌┴───────┴┐ ┌─┴┴────┴┐ ┌─────┴──┐            │
│ notes │ │ shares │ │ persons │ │ follows │ │ outbox │ │ inbox  │            │
├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ├────────┤ ├────────┤            │
│object │ │note    │ │actor    │ │follower │ │activity│ │activity│            │
│author │ │by      │ │...      │ │followed │ │sender  │ │sender  │            │
│...    │ │...     │ │         │ │...      │ │...     │ │...     │            │
└───┬───┘ └───┬────┘ └────┬────┘ └────┬──┬─┘ └┬──────┬┘ └──────┬─┘            │
    │         │           │           │ ┌┴────┴─────┐│    ┌────┴────────┐     │
    │         │   ┌───────┴──────┐ ┌──┼─┤ fed.Queue │└────┤ inbox.Queue │     │
    │         │   │ fed.Resolver ├─┘  │ └───────────┘     └─┬─┬─┬─┰─────┘     │
    │         │   └───────┬─┰────┘    └─────────────────────┘ │ │ ┃           │
    │         └───────────┼─╂─────────────────────────────────┘ │ ┃           │
    └─────────────────────┼─╂───────────────────────────────────┘ ┃           │
                          └─╂─────────────────────────────────────╂───────────┘
                            ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

To display details like the user's name and speed up the verification of future incoming replies, inbox.Queue uses Resolver to fetch the Actor objects of mentioned users (if needed).

                                     ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
                                     ┃┌───────────────┐                   ┃
   ┌──────────┐ ┌─────────────────┐  ┃│ outbox.Mover  │                   ┃
   │ gmi.Wrap ├─┤ gemini.Listener │  ┃│ outbox.Poller │                   ┃
   └──────────┘ └────────┬────────┘┏━┛│ fed.Syncer    │                   ┃
                ┌────────┴─────────┸┐ └───┬─────┬─────┘ ┌──────────────┐  ┃
    ┌───────────┤   front.Handler   ├─────┼────┐│    ┌──┤ fed.Listener ├──╂───┐
    │           └┬────────┬───────┬─┘     │    ││    │  └─────┬────────┘  ┃   │
┌───┴───┐ ┌──────┴─┐ ┌────┴────┐ ┌┴───────┴┐ ┌─┴┴────┴┐ ┌─────┴──┐ ┏━━━━━━┻━┓ │
│ notes │ │ shares │ │ persons │ │ follows │ │ outbox │ │ inbox  │ ┃  feed  ┃ │
├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ├────────┤ ├────────┤ ┣━━━━━━━━┫ │
│object │ │note    │ │actor    │ │follower │ │activity│ │activity│ ┃follower┃ │
│author │ │by      │ │...      │ │followed │ │sender  │ │sender  │ ┃note    ┃ │
│...    │ │...     │ │         │ │...      │ │...     │ │...     │ ┃...     ┃ │
└─┰─┬───┘ └─┰─┬────┘ └──┰─┬────┘ └──┰─┬──┬─┘ └┬──────┬┘ └──────┬─┘ ┗━━━━━━┳━┛ │
  ┃ │       ┃ │ ┏━━━━━━━┛ │         ┃ │ ┌┴────┴─────┐│    ┌────┴────────┐ ┃   │
  ┃ │       ┃ │ ┃ ┌───────┴──────┐ ┌╂─┼─┤ fed.Queue │└────┤ inbox.Queue │ ┃   │
  ┃ │       ┃ │ ┃ │ fed.Resolver ├─┘┃ │ └───────────┘     └─┬─┬─┬─┬─────┘ ┃   │
  ┃ │       ┃ │ ┃ └───────┬─┬────┘  ┃ └─────────────────────┘ │ │ │       ┃   │
  ┃ │       ┃ └─╂─────────┼─┼───────╂─────────────────────────┘ │ │       ┃   │
  ┃ └───────╂───╂─────────┼─┼───────╂───────────────────────────┘ │       ┃   │
  ┃         ┃   ┃         └─┼───────╂─────────────────────────────┼───────╂───┘
┏━┻━━━━━━━━━┻━━━┻━━━┓       └───────╂─────────────────────────────┘       ┃
┃ inbox.FeedUpdater ┣━━━━━━━━━━━━━━━┛                                     ┃
┗━━━━━━━━━┳━━━━━━━━━┛                                                     ┃
          ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

To speed up each user's feed, inbox.FeedUpdater periodically appends rows to the feed table. This table holds all information that appears in the user's feed: posts written or shared by followed users, author information and more, eliminating the need for join queries, slow filtering by post visibility, deduplication and sorting by time when a user views their feed. This table is indexed by user and time, allowing fast querying of a single feed page for a particular user.

More Documentation

Credits and Legal Information

tootik is free and unencumbered software released under the terms of the Apache License Version 2.0; see LICENSE for the license text.

The ASCII art logo at the top was made using FIGlet.