From 8ee266f7b149b2a06d8c50370e5f3dba2f8bfef5 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sun, 4 Feb 2024 11:14:18 -0800 Subject: [PATCH] chore: Track `lastActiveAt` for teams (#6491) --- server/middlewares/authentication.ts | 5 +++- .../20240204185157-team-last-active-at.js | 11 +++++++ server/models/Team.ts | 30 +++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 server/migrations/20240204185157-team-last-active-at.js diff --git a/server/middlewares/authentication.ts b/server/middlewares/authentication.ts index fb3249237..35fb6e2f2 100644 --- a/server/middlewares/authentication.ts +++ b/server/middlewares/authentication.ts @@ -122,10 +122,13 @@ export default function auth(options: AuthenticationOptions = {}) { } } - // not awaiting the promise here so that the request is not blocked + // not awaiting the promises here so that the request is not blocked user.updateActiveAt(ctx).catch((err) => { Logger.error("Failed to update user activeAt", err); }); + user.team?.updateActiveAt().catch((err) => { + Logger.error("Failed to update team activeAt", err); + }); ctx.state.auth = { user, diff --git a/server/migrations/20240204185157-team-last-active-at.js b/server/migrations/20240204185157-team-last-active-at.js new file mode 100644 index 000000000..6b5b40347 --- /dev/null +++ b/server/migrations/20240204185157-team-last-active-at.js @@ -0,0 +1,11 @@ +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.addColumn("teams", "lastActiveAt", { + type: Sequelize.DATE, + allowNull: true, + }); + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.removeColumn("teams", "lastActiveAt"); + }, +}; diff --git a/server/models/Team.ts b/server/models/Team.ts index a30d9724d..4d02e4a59 100644 --- a/server/models/Team.ts +++ b/server/models/Team.ts @@ -3,6 +3,7 @@ import fs from "fs"; import path from "path"; import { URL } from "url"; import util from "util"; +import { subMinutes } from "date-fns"; import { InferAttributes, InferCreationAttributes, @@ -163,6 +164,10 @@ class Team extends ParanoidModel< @Column suspendedAt: Date | null; + @IsDate + @Column + lastActiveAt: Date | null; + // getters /** @@ -248,6 +253,27 @@ class Team extends ParanoidModel< TeamPreferenceDefaults[preference] ?? false; + /** + * Updates the lastActiveAt timestamp to the current time. + * + * @param force Whether to force the update even if the last update was recent + * @returns A promise that resolves with the updated team + */ + public updateActiveAt = async (force = false) => { + const fiveMinutesAgo = subMinutes(new Date(), 5); + + // ensure this is updated only every few minutes otherwise + // we'll be constantly writing to the DB as API requests happen + if (!this.lastActiveAt || this.lastActiveAt < fiveMinutesAgo || force) { + this.lastActiveAt = new Date(); + } + + // Save only writes to the database if there are changes + return this.save({ + hooks: false, + }); + }; + provisionFirstCollection = async (userId: string) => { await this.sequelize!.transaction(async (transaction) => { const collection = await Collection.create( @@ -356,6 +382,10 @@ class Team extends ParanoidModel< // Set here rather than in TeamPreferenceDefaults as we only want to enable by default for new // workspaces. model.setPreference(TeamPreference.MembersCanInvite, true); + + // Set last active at on creation. + model.lastActiveAt = new Date(); + return model; }