Create user last connection datetime

pull/935/head
CamilleLegeron 5 months ago
parent fe9cc80ccc
commit 643dacafbf

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

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

@ -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');
}
}

@ -501,6 +501,10 @@ export class Client {
const user = this._profile ? await this._fetchUser(dbManager) : dbManager.getAnonymousUser(); const user = this._profile ? await this._fetchUser(dbManager) : dbManager.getAnonymousUser();
this._user = user ? dbManager.makeFullUser(user) : undefined; this._user = user ? dbManager.makeFullUser(user) : undefined;
this._firstLoginAt = user?.firstLoginAt || null; 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> { private async _onMessage(message: string): Promise<void> {

@ -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. // Database fields that we permit in entities but don't want to cross the api.
const INTERNAL_FIELDS = new Set([ const INTERNAL_FIELDS = new Set([
'apiKey', 'billingAccountId', 'firstLoginAt', 'filteredOut', 'ownerId', 'gracePeriodStart', 'stripeCustomerId', 'apiKey', 'billingAccountId', 'firstLoginAt', 'lastConnectionAt', 'filteredOut', 'ownerId', 'gracePeriodStart',
'stripeSubscriptionId', 'stripePlanId', 'stripeProductId', 'userId', 'isFirstTimeUser', 'allowGoogleLogin', 'stripeCustomerId', 'stripeSubscriptionId', 'stripePlanId', 'stripeProductId', 'userId', 'isFirstTimeUser',
'authSubject', 'usage', 'createdBy' 'allowGoogleLogin', 'authSubject', 'usage', 'createdBy'
]); ]);
/** /**

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

Loading…
Cancel
Save