Create user last connection datetime

This commit is contained in:
CamilleLegeron 2024-04-15 15:20:16 +02:00
parent fe9cc80ccc
commit 643dacafbf
6 changed files with 46 additions and 7 deletions

View File

@ -29,6 +29,9 @@ export class User extends BaseEntity {
@Column({name: 'first_login_at', type: Date, nullable: true})
public firstLoginAt: Date | null;
@Column({name: 'last_connection_at', type: Date, nullable: true})
public lastConnectionAt: Date | null;
@OneToOne(type => Organization, organization => organization.owner)
public personalOrg: Organization;

View File

@ -213,6 +213,7 @@ function isNonGuestGroup(group: Group): group is NonGuestGroup {
export interface UserProfileChange {
name?: string;
isFirstTimeUser?: boolean;
newConnection?: boolean;
}
// Identifies a request to access a document. This combination of values is also used for caching
@ -614,6 +615,14 @@ export class HomeDBManager extends EventEmitter {
// any automation for first logins
if (!props.isFirstTimeUser) { isWelcomed = true; }
}
if (props.newConnection === true) {
// set last connection time to now (remove milliseconds for compatibility with other
// timestamps in db set by typeorm, and since second level precision is fine)
const nowish = new Date();
nowish.setMilliseconds(0);
user.lastConnectionAt = nowish;
needsSave = true;
}
if (needsSave) {
await user.save();
}
@ -701,12 +710,15 @@ export class HomeDBManager extends EventEmitter {
user.name = (profile && (profile.name || email.split('@')[0])) || '';
needUpdate = true;
}
if (profile && !user.firstLoginAt) {
// set first login time to now (remove milliseconds for compatibility with other
if (profile) {
// set first login time and last connection time to now (remove milliseconds for compatibility with other
// timestamps in db set by typeorm, and since second level precision is fine)
const nowish = new Date();
nowish.setMilliseconds(0);
user.firstLoginAt = nowish;
user.lastConnectionAt = nowish;
if (!user.firstLoginAt) {
user.firstLoginAt = nowish;
}
needUpdate = true;
}
if (!user.picture && profile && profile.picture) {

View File

@ -0,0 +1,18 @@
import { MigrationInterface, QueryRunner, TableColumn} from 'typeorm';
export class UserLastConnection1713186031023 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> {
const sqlite = queryRunner.connection.driver.options.type === 'sqlite';
const datetime = sqlite ? "datetime" : "timestamp with time zone";
await queryRunner.addColumn('users', new TableColumn({
name: 'last_connection_at',
type: datetime,
isNullable: true
}));
}
public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.dropColumn('users', 'last_connection_at');
}
}

View File

@ -501,6 +501,10 @@ export class Client {
const user = this._profile ? await this._fetchUser(dbManager) : dbManager.getAnonymousUser();
this._user = user ? dbManager.makeFullUser(user) : undefined;
this._firstLoginAt = user?.firstLoginAt || null;
if (this._user) {
// Send the information to the dbManager that the user has a new activity
await dbManager.updateUser(this._user.id, {newConnection: true});
}
}
private async _onMessage(message: string): Promise<void> {

View File

@ -21,9 +21,9 @@ export const TEST_HTTPS_OFFSET = process.env.GRIST_TEST_HTTPS_OFFSET ?
// Database fields that we permit in entities but don't want to cross the api.
const INTERNAL_FIELDS = new Set([
'apiKey', 'billingAccountId', 'firstLoginAt', 'filteredOut', 'ownerId', 'gracePeriodStart', 'stripeCustomerId',
'stripeSubscriptionId', 'stripePlanId', 'stripeProductId', 'userId', 'isFirstTimeUser', 'allowGoogleLogin',
'authSubject', 'usage', 'createdBy'
'apiKey', 'billingAccountId', 'firstLoginAt', 'lastConnectionAt', 'filteredOut', 'ownerId', 'gracePeriodStart',
'stripeCustomerId', 'stripeSubscriptionId', 'stripePlanId', 'stripeProductId', 'userId', 'isFirstTimeUser',
'allowGoogleLogin', 'authSubject', 'usage', 'createdBy'
]);
/**

View File

@ -41,6 +41,8 @@ import {ForkIndexes1678737195050 as ForkIndexes} from 'app/gen-server/migration/
import {ActivationPrefs1682636695021 as ActivationPrefs} from 'app/gen-server/migration/1682636695021-ActivationPrefs';
import {AssistantLimit1685343047786 as AssistantLimit} from 'app/gen-server/migration/1685343047786-AssistantLimit';
import {Shares1701557445716 as Shares} from 'app/gen-server/migration/1701557445716-Shares';
import {UserLastConnection1713186031023
as UserLastConnection} from 'app/gen-server/migration/1713186031023-UserLastConnection';
const home: HomeDBManager = new HomeDBManager();
@ -49,7 +51,7 @@ const migrations = [Initial, Login, PinDocs, UserPicture, DisplayEmail, DisplayE
CustomerIndex, ExtraIndexes, OrgHost, DocRemovedAt, Prefs,
ExternalBilling, DocOptions, Secret, UserOptions, GracePeriodStart,
DocumentUsage, Activations, UserConnectId, UserUUID, UserUniqueRefUUID,
Forks, ForkIndexes, ActivationPrefs, AssistantLimit, Shares];
Forks, ForkIndexes, ActivationPrefs, AssistantLimit, Shares, UserLastConnection];
// Assert that the "members" acl rule and group exist (or not).
function assertMembersGroup(org: Organization, exists: boolean) {