This article deals with a simple web application, that has been implemented in both PHP nad Go languages. It serves as a very simple microblogging (nanoblogging even) platform. The first part describes the legacy version developed in years 2015–2017 (circa).

The following part deals with the current PWA (progressive web application) is being developed in Golang since 2022. At the moment, it is developed as the prove-of-concept (PoC) — to show that it’s possible to write a social network from scratch using Go and some core primitives like go-app or beercss frameworks.

litter (legacy PHP webapp)

litter (lower-cased) is a small microblogging platform serving as another Twitter clone. In its beginnings it was meant to serve as a simple messaging/blogging platform for my classmates at the time.

Fig. 1: Original green-on-black-styled custom litter main page.

Later on as other social media found its way to my classmates, no one was interested in specialized tiny social-blogging service anymore, so I turned it into a nanoblogging platform (contra to microblogging, as this is a very light way to write short messages). It had some users, spamming their mindflow to the flow of others.

implementation

litter was originally written in PHP 5.6 (even promoting support back to 4.8) as a single file for everything. Inside the file, one can find system functions, form actions, CSS styling, HTML templating, MySQL table creating if not exists (terminus technicus) and more. The original file has more than 1300 lines of code.

Pictures posted in the flow were uploaded to the app root’s directory litterpic/; autocreated if not existed.

Fig. 2: Original litter main menu.

litter has a few pages to be accessed via simple header-menu links. >n0p links to old swis entry, exit logouts the actual user.

flow

Fig. 3: Original litter flow trimmed example with an image post.

flow is the main system page showing the raw post feed — flow —, that can be moderated by “add user to flow” toggle on users page. Originally even raw images were allowed to be posted (as that time-machine meme above). Even very simple polls were allowed, where each user could vote only once.

By clicking on the post date, users could write a simple reply to that very post. Recursive replies were blocked, as no one could reply to a reply. After a post is sent, no one is allowed to delete it (it has to be manually deleted from MySQL database/table).

At the bottom of the flow page there were also other posts pages, as the pagination were hardset to 100 posts per page.

Fig. 4: Original litter footer showing numerous posts-and-pages instance.

users

Fig. 5: Original litter users page with to-flow toggle link/button per user.

This page shows registered and active litter users. One can add/remove others to their flow only by clicking on flow/unflow link. Below, something like following/followers logic is shown as well.

Moreover, user’s online status is shown (users has to be somehow active on the site within 10 or 15 minutes range).

settings

Fig. 6: Original litter settings page example showing various options of user customization.

settings page was “designed” to be very simple — one can modify styling colour (4 colours on black background), can change their password and their bio shown in the users page, and can change the site’s header at the top.

stats

Fig. 7: Original litter statistics page showing posts count per user and per month.

stats page shows the current site’s statistics — the most active users sorted DESC, the most active months sorted DESC — both as post counts. One can imagine, how active that site used to be in 2015… :D

login

Fig. 8: Original litter login page with default white background colour.

Login page is again very simple, one just enters their credentials and enters the flow. If wrong credentials are entered, an error message is generated under the actual form.

If the current user’s IP address differs from the one previously used to access the site, the password is regenerated and sent to user’s e-mail address and the access is disallowed until the user gets the new password from their mail.

registration

Registration itself has three different modes:

  • off — registration is disabled for newcomers
  • on — simple and basic registration for newcommers
  • shared/poll — after basic registration is done, there is a system poll added to every one’s flow

Registration poll is autoset to 24-hour time range, as other users are allowed to vote. After this period of time, the system checks for results and sends another e-mail to the newcomer regarding the poll’s results — new account is deleted, or the user is activated and allowed to come in.


littr PWA (litter in Go)

Link to git repo

littr (aka litter-go) is a Golang-ized derivative from the original litter. At the moment it is being developed in terms of a PoC (prove of concept) project. Work still in progress.

the stack

Below, the main primitives (“core stones”) are listed:

  • beercss (CSS Material implementation, FE)
  • go-app (Go PWA framework, FE)
  • go-chi (Go light HTTP router, BE)
  • go-sse (Go SSE streamer, BE)
  • go-thumbnail (Go image processing lib, BE)
  • golang-jwt (Go JWT implementation, BE)
  • swis-api (Go minimal database concept, BE)
  • webpush-go (Go Webpush notifications lib, BE)

architecture

The app itself is divided into the backend (BE) and frontend (FE) parts, each serving different methods and contructions. Basically, BE part takes care of the database and the procedures (validations etc) around it; while FE part suits as a gateway interface for the client to interact with, that counts in the user interface (UI) logic and its elements displayment, taking user’s input and delivering it over the net to the BE part for processing.

                 |----------|  ==> REST API ==>  |---------|
 ( client ) <==> | frontend |                    | backend | <==> ( database )
                 |----------|  <== REST API <==  |---------|

Golang is used for both BE and FE parts. But each system part uses Go in a different way.

backend

Backend (BE) consists of the embedded database (an interlinked group of independent caches), and an application programmable interface (API, REST API). It receives API calls, validates its payload and headers, then it interacts with the database, and composes a response for the caller to return.

Almost all (there are around 5 exceptions) API routes are secured behind the auth wall. Client is authenticated using the credentials pair (username and passphrase). When the authentication succeeds, a pair of cookies is sent to the client with the proper response with further information (user’s whole database fingerprint.

BE also takes care of notifications (in coop with FE partly for the subscription initialization). When requested via an API call, some important fields are to be extracted —the caller, and the original post ID. From the original post ID (oPostID) now the BE is to figure out who to send the notification to. This is performed using the oPostID — the original poster is extracted from the database, with their subscription to notifications status.

frontend

Frontend (FE) is the very app’s UI (views + semi-controllers).

login page

login page suits as a gateway for the authentication process. It has a simple login form consisting of the nickname filed, passphrase field, button for the login form submission and for the register page redirection.

Navigation buttons are disabled on the login page as they would be useless for an unauthorized user.

Fig. 9: littr login page (various versions).

settings page

settings page provides the user with various UI and application setting possibility. Those settings elements contain of UI switches, form input fields and submission buttons. The very page’s bottom offers the user to delete their account (the deletion button points to an “are-you-sure” modal).

Fig. 10: littr settings page (various versions).

The page’s top offers multiple changes using UI switches. Fig 13 shows the app in so-called light mode. The page’s bottom shows the possibility for account deletion.

stats page

stats page shows various simple statistics of user/post/flow subsystem, and the system itself.

  • littr stats section shows the number of posts, received stars count, number of user’s followers (flowers), received shades (blocks), per earch user posting at least one (1) post,
  • system stats section shows the numbers of polls, posts, stars, and users.

How ratio \( R \) value is calculated from user’s stats:

$$ R = \frac{N_{stars}}{N_{posts}} \times A $$ $$ N_{posts} \gt 0, $$

where the attenuation coefficient \( A \) equals 1 if no or just one shade is given to such user. Otherwise the coefficient takes its effect and attenuates base ratio value to zero logarithmically. The attenuation coefficient is then declared as:

$$ A = 1 - \log_{(N_{users})}{( N_{shades} )} $$ $$ A = 1 - \frac{\log{( N_{shades} )}}{\log{( N_{users} )}}, $$

while

$$ 1 \lt N_{shades} \le N_{users}. $$

Fig. 11: littr stats page (various versions).

users page

users page offers the view of all users registered on such instance. On the top there is a search bar for a simple user search. Each table cell holds:

  • an username (and link, is described more lower),
  • their about text/bio/status (changeable on the settings page),
  • a button for the so-called flow list modification (add to/remove from flow).

The link hidden in an user’s name shows the so-called user info modal. This modal lists user’s avatar, name, bio, and last-active time and register time.

Fig. 12: littr users (flowers) page (various versions).

post page

post page allows the user to add new

  • flow posts (optional image addition),
  • fig posts via the image URI/URL (Fig 24),
  • a generic poll.

Fig. 13: littr post page (various versions).

polls page

polls page lists all (at the moment) added survey polls by any (at the moment) user. User can vote — choose a costom option/answer — via a resposive button per each option. When already voted, the poll cell shows its results by simple bars and percentage info.

Fig. 14: littr polls page (various versions).

flow page

flow page is the main page of the authenticated realm. It shows the very littr content, filterred by the so-called user flow list (users/accounts marked as followed).

Each table cell of a post consists of:

  • top row (post’s header) — user’s avatar and name, permalink to such post (link button),
  • post — post’s content, or previous post’s content and a reply to that post (previous post yellow part moreover holds a history button — more below)
  • bottom row (post’s footer) — post’s timestamp, and reply button followed by the received star count and star/delete button (depends on the post’s name, only author can delete their post, only others can star).

special subpages

Clicking on the history button makes the flow page to be rebuilt in the so-called single page view. The view can be evoked by the link button too. This view renders only the single post requested, with possible replies ordered ascendentaly. The history button loads the very previous post in the single post view, meaning one can browse in the post tree structure therefore. Clicking on the link button also adds the URL to permalink to a system/phone clipboard.

live mode

When the live mode is enabled (by default, but still an experimental feature), the new-post snackbar/toast pops up on the page when scrolling on new post addition by other fellows. The live mode has a keepalive logic to ensure SSE (Server-Sent Events) connection stability, and is indicated by a fading dot at the app’s header (since v0.30.26). The indicator shows when a heartbeat event hits the client’s side (every 20 seconds ideally), then slowly fades away.

Fig. 15: littr flow page (various versions).

logout modal

logout modal is a dialog/modal element quering the user for the logout confirmation. It can be evoked by clicking on the logout button at the app’s top bar.

Fig. 16: logout modal (various versions).

app info modal

app info modal is a dialog/modal shows/lists:

  • app’s logo,
  • app’s name, shortcut and PWA (progressive web application) label,
  • ToS (terms of service) page link,
  • version with the link to Github git repo,
  • powered-by list naming each component with a link to its webpage/repo.
  • a tandem of small buttons — reload and close

Clicking on the ToS link redirects an user to the ToS page.

Clicking on the reload button reloads the whole web application (client-side), it is a helpful way on how to reload installed app. Also it can be used to require a new version of the web app (if exists).

Fig. 17: littr info modal (various versions).

user info modal

user info modal can be envoked by clicking on an user’s name on the users page. This modal/dialog element lists:

  • user’s avatar
  • user’s name
  • user’s about text/bio
  • last active timestamp
  • registered timestamp
  • web link (if present)
  • close button

Fig. 18: user info modal (various versions).

user data

As a generic user is concerned, littr only stores

  • nickname,
  • hashed (sha512) passphrase,
  • e-mail address,
  • web address (optional),
  • avatar URL (gravatar.com, optional),
  • about text (bio, acc’s content introduction, optional),
  • GDPR consent status,

and some system parameters:

  • so-called FlowList, and ShadeList to track the users to follow/block,
  • various timestamps (last activity time, registeration time, …),
  • some user’s settings for the frontend.

Full running User model can be seen via the link below:

User model source (repo at github.com)

backend logs

Example backend (backend only) responses to various requests:

{
   "caller_id": "system",
   "code": 200,
   "ip_address": "127.0.0.1",
   "message": "starting the server...",
   "method":"",
   "route":"",
   "time":"2023-10-24T12:06:42.890967382+02:00",
   "worker_name": "initServer"
}
{
   "caller_id": "lmao",
   "code": 200,
   "ip_address": "2a02:xxx",
   "message": "ok, dumping polls",
   "method": "GET",
   "route": "/api/polls",
   "time": "2023-10-24T14:31:03.22558191+02:00",
   "worker_name": "polls"
}
{
   "caller_id": "lmao",
   "code": 200,
   "ip_address": "2a02:xxx",
   "message": "ok, dumping posts",
   "method": "GET",
   "route": "/api/flow",
   "time":"2023-10-24T14:31:05.411609603+02:00",
   "worker_name": "flow"
}
{
   "caller_id": "lmao",
   "code": 200,
   "ip_address": "2a02:xxx",
   "message": "ok, dumping users",
   "method": "GET",
   "route": "/api/users",
   "time": "2023-10-24T14:31:05.763912438+02:00",
   "worker_name": "users"
}
{
   "caller_id": "system",
   "code": 200,
   "ip_address": "127.0.0.1",
   "message": "data dumped successfully",
   "method": "GET",
   "route": "/api/dump",
   "time": "2023-10-24T14:31:47.275915327+02:00",
   "worker_name": "dump"
}
{
   "caller_id": "lmao",
   "code": 201,
   "ip_address": "2a09:bac2:2750:126e::1d6:5c",
   "message": "ok, adding new post",
   "method": "POST",
   "route": "/api/flow",
   "time": "2023-10-24T12:01:50.384980522+02:00",
   "worker_name": "flow"
}
{
   "caller_id": "lmao",
   "code": 400,
   "ip_address": "2001:xxx",
   "message": "user not found, or wrong passphrase entered",
   "method": "POST",
   "route": "/api/auth",
   "time": "2023-10-24T12:34:14.119836301+02:00",
   "worker_name": "auth"
}
{
   "caller_id": "lmao",
   "code": 200,
   "ip_address": "2a02:xxx",
   "message": "ok, post updated",
   "method": "PUT",
   "route": "/api/flow",
   "time": "2023-10-24T14:49:27.12591076+02:00",
   "worker_name": "flow"
}

easter eggs

Those functions/elements are implemented in the app, but are kinda hidden from the user.

  • default poll’s (very first poll) values in chart are wrong on purpose
  • dynamic user’s posts flow (only one’s posts specified) link
  • stats table sorting
  • user’s info modal with last-active and registered timestamps and website links
  • user’s flow via stats nickname click

troubleshooting

Android notifications

When the app is installed (via Google Chrome), click on the icon on the home display and enter its context menu. Then go settings and enable notifications for such app.

Next, in the littr app, go to settings and tap on the slider in the notifications section to subscribe to notifications.

Fig. 19: Chrome site and littr app notifications activation.

iOS safari notifications

For iOS-equipped devices (basically just Apple iPhones) there is a feature flag in the Safari browser, that has to be enabled to allow and see notifications from any PWA (progressive web application).

Navigate yourself to the iOS Settings app, in the search bar type in “safari” and choose the very Safari application. In the Safari settings page, scroll all the way down to the bottom. Go to Advanced settings.

Next, scroll all the way down again until you hit the very bottom and see the Feature Flags option. Tap on it.

Now, there will be a huge amount of feature flags for developers to be enabled/disabled. We only want notifications to be enabled. Therefore scroll down until you see the Notifications switch, tap on it to enable it and, folks, that’s it.

Fig. 20: Safari notifications activation.

iOS app installation with notification subscription

Prerequisities:

  • iPhone device
  • iOS 17.4 and newer
  • littr PWA v0.29.4 and newer

To install the litter-go (littr) PWA on iOS devices, navigate your Safari browser to some littr instance website and hit the Share button (bottom navigation bar, the very middle, iOS Share, button), and tap Add to Home screen.

Name it whatever you want, this name will be used on the Home screen, in the Settings app and in the iOS system basically.

Open the app from Home screen, perform a log-in procedure, and tap the Settings icon/button on the top navbar (the very left button on the top). Scroll down a bit until you see the Notifications section. Read through the info boxes and click on the reply notification switch. Now, the iOS system is prompted to allow notifications from this PWA, so the system propts user too. Do allow notifications here.

After the system dialog for notification permission is gone, you should be automatically subscribed to notifications — check the Registered devices sections below the notification switch, you should see a new device registered with the web.push.apple.com endpoint specified.

Fig. 21: littr PWA installation via Safari with notification subscription shown.

iOS (brute) force ensure app version

In the littr PWA it sometimes seems impossible to ensure new app version to be running on your phone (known bug, browser resource cache problem). To force ensure new app version, you can do following:

  • open iOS Settings app
  • search for ‘safari’ app
  • scroll down to Advanced
  • click on Website Data
  • look for the littr instance hosted domain (e.g. n0p.cz)
  • swipe left on the very domain to delete cached data
  • open the littr PWA in Safari
  • check the app’s version
  • (optional) remove and reinstall the littr PWA (see above)

Fig. 22: littr PWA data flush.