Storing last seen timestamp - is this code correct?

I was looking at storing a ‘last seen’ timestamp in my phoenix project and came across this blog post: How to store ‘last seen’ for users in Phoenix | Plausible Analytics, however it doesn’t appear correct and I wanted to sanity check my understanding.

Here’s the key bit of code:

def call(conn, _opts) do
    last_seen = get_session(conn, :last_seen)
    user = conn.assigns[:current_user]

    cond do
      user && last_seen && last_seen < (unix_now() - @one_hour) ->
        persist_last_seen(user) # Saves last_seen to DB
        put_session(conn, :last_seen, unix_now())
      user && !last_seen ->
        put_session(conn, :last_seen, unix_now())
      true ->
        conn
    end
  end

A few questions:

  1. This doesn’t look like it will update last_seen for the first hour in a new session?
  2. Why do we need to store last_seen in the session at all?

It feels like I can replace the above with much simpler code:

  def call(conn, _opts) do
    user = conn.assigns[:current_user]

    if user && DateTime.diff(unix_now(), user.last_seen) > @one_hour do
      persist_last_seen(user, unix_now())
    end

    conn
  end

NB. Both plugs assume the user was fetched in a previous plug.

Is my understanding correct on this? Thanks for the help.

1 Like

I think that you’re right @tyr0 - with this code, the timestamp won’t be updated until the session is longer than one hour. It may have gone unnoticed because the migration sets a default timestamp.

The missing piece would be to also persist the timestamp in the second condition, that is, the first time you remember last_seen in the session:

cond do
  user && last_seen && last_seen < (unix_now() - @one_hour) ->
    persist_last_seen(user)
    put_session(conn, :last_seen, unix_now())
  user && !last_seen ->
    persist_last_seen(user)
    put_session(conn, :last_seen, unix_now())
  true ->
    conn
end

That is, we want to persist_last_seen(user) in both cases: on the first visit (second clause) and on subsequent visits after the first hour (first clause).

Now, both clauses do the same, persist to DB and remember in session, so what I did is just this:

  def call(conn, _opts) do
    last_seen = get_session(conn, :last_seen)
    user = conn.assigns[:current_user]

    if user && (!last_seen || (last_seen && last_seen < unix_now() - @five_minutes)) do
      persist_last_seen(user)
      put_session(conn, :last_seen, unix_now())
    else
      conn
    end
  end

Like “if there’s a user and it’s the first time seen in this session or was seen earlier than 5 minutes ago…”

Regarding session variables, I think it’s a good idea at least in my case, as I use Pow and it has a cache, so conn.assigns[:current_user] won’t be as up to date as the session variable.

1 Like

Ha. Didn’t expect a reply to this after so long! And I didn’t realise that Pow has a cache, so yes, makes sense to persist the data to session in that case. :+1: