Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TECH] Créer un script pour importer la date de dernière connexion (lastLoggedAt) (PIX-10728) #11814

Merged
merged 3 commits into from
Mar 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { UserLogin } from '../models/UserLogin.js';

/**
* @typedef {function} importUserLastLoggedAt
* @param {Object} params
* @param {boolean} params.dryRun
* @param {string} params.userId
* @param {Date} params.lastActivity
* @param {UserRepository} params.userRepository
* @param {UserLoginRepository} params.userLoginRepository
* @return {Promise<boolean>}
*/
export const importUserLastLoggedAt = async function ({
dryRun,
userId,
lastActivity,
userRepository,
userLoginRepository,
}) {
const user = await userRepository.findById(userId);
if (!user || user.hasBeenAnonymised) {
return false;
}

const userLogin = await userLoginRepository.findByUserId(userId);
if (!userLogin) {
const newUserLogin = new UserLogin({ userId, lastLoggedAt: lastActivity });
if (!dryRun) await userLoginRepository.create(newUserLogin);
return true;
}

if (userLogin.lastLoggedAt) {
return false;
}

userLogin.lastLoggedAt = lastActivity;
if (!dryRun) await userLoginRepository.update(userLogin, { preventUpdatedAt: true });
return true;
};
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,15 @@ const findAnotherUserByUsername = async function (userId, username) {
return anotherUsers.map((anotherUser) => new User(anotherUser));
};

/**
* @param {string} userId
* @return {Promise<User>}
*/
const findById = async function (userId) {
const user = await knex('users').where({ id: userId }).first();
return user ? new User(user) : null;
};

/**
* @param {{
* userId: string
Expand All @@ -429,6 +438,7 @@ const updateLastDataProtectionPolicySeenAt = async function ({ userId }) {
* @property {function} findAnotherUserByEmail
* @property {function} findAnotherUserByUsername
* @property {function} findByExternalIdentifier
* @property {function} findById
* @property {function} findPaginatedFiltered
* @property {function} get
* @property {function} getByEmail
Expand Down Expand Up @@ -461,6 +471,7 @@ export {
findAnotherUserByEmail,
findAnotherUserByUsername,
findByExternalIdentifier,
findById,
findPaginatedFiltered,
get,
getByEmail,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import Joi from 'joi';

import { csvFileParser } from '../../shared/application/scripts/parsers.js';
import { Script } from '../../shared/application/scripts/script.js';
import { ScriptRunner } from '../../shared/application/scripts/script-runner.js';
import { usecases } from '../domain/usecases/index.js';

export const csvSchemas = [
{ name: 'userId', schema: Joi.number().required() },
{ name: 'last_activity', schema: Joi.date().required() },
];

export class ImportUserLastLogeedAtScript extends Script {
constructor() {
super({
description: 'This script allows to update user last logged at',
permanent: false,
options: {
file: {
type: 'string',
describe: 'CSV file path',
demandOption: true,
coerce: csvFileParser(csvSchemas),
},
dryRun: {
type: 'boolean',
describe: 'Executes the script in dry run mode',
default: false,
},
},
});
}
async handle({ options, logger, importUserLastLoggedAt = usecases.importUserLastLoggedAt }) {
const { file, dryRun } = options;

const total = file.length;

if (dryRun) logger.info('DRY RUN mode enabled - data updates are not effective');

logger.info(`${total} users must be processed for last logged date update.`);
let count = 0;
let updatedCount = 0;
let notUpdatedCount = 0;

for (const row of file) {
const { userId, last_activity } = row;
count += 1;
const result = await importUserLastLoggedAt({ dryRun, userId, lastActivity: last_activity });
if (result) {
logger.info(`${count}/${total} - user updated`);
updatedCount += 1;
} else {
logger.info(`${count}/${total} - user not updated`);
notUpdatedCount += 1;
}
}

logger.info(`${count} users processed.`);
logger.info(`${updatedCount} users with last logged at updated.`);
logger.info(`${notUpdatedCount} users with last logged at NOT updated.`);
}
}

await ScriptRunner.execute(import.meta.url, ImportUserLastLogeedAtScript);
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { usecases } from '../../../../../src/identity-access-management/domain/usecases/index.js';
import { databaseBuilder, expect, sinon } from '../../../../test-helper.js';

describe('Integration | Identity Access Management | Domain | UseCase | import user last logged at', function () {
let clock;
const now = new Date('2025-01-01');

beforeEach(function () {
clock = sinon.useFakeTimers({ now, toFake: ['Date'] });
});

afterEach(function () {
clock.restore();
});

context('when dryRun is false', function () {
let dryRun;

beforeEach(function () {
dryRun = false;
});

context('when the user exists', function () {
context('when the user has not been anonymized', function () {
context('when the user login has no lastloggedAt', function () {
it('updates the user login lastLoggedAt', async function () {
// given
const userId = databaseBuilder.factory.buildUser({
hasBeenAnonymised: false,
}).id;
databaseBuilder.factory.buildUserLogin({
userId,
lastLoggedAt: null,
});

await databaseBuilder.commit();

// when
await usecases.importUserLastLoggedAt({
dryRun,
userId,
lastActivity: new Date(),
});

// then
const userLoginUpdated = await databaseBuilder.knex('user-logins').where({ userId }).first();
expect(userLoginUpdated.lastLoggedAt).to.deep.equal(now);
});
});
});

context('when there is no user login', function () {
it('creates a new user login', async function () {
// given
const userId = databaseBuilder.factory.buildUser({
hasBeenAnonymised: false,
}).id;

await databaseBuilder.commit();

// when
await usecases.importUserLastLoggedAt({
dryRun,
userId,
lastActivity: new Date(),
});

// then
const userLoginCreated = await databaseBuilder.knex('user-logins').where({ userId }).first();
expect(userLoginCreated.lastLoggedAt).to.deep.equal(now);
});
});

context('when the user login has a lastLoggedAt', function () {
it('does nothing', async function () {
// given
const userId = databaseBuilder.factory.buildUser({
hasBeenAnonymised: false,
}).id;
databaseBuilder.factory.buildUserLogin({
userId,
lastLoggedAt: new Date('2020-01-01'),
});
await databaseBuilder.commit();

// when
const result = await usecases.importUserLastLoggedAt({
dryRun,
userId,
lastActivity: new Date(),
});

// then
const userLoginUpdated = await databaseBuilder.knex('user-logins').where({ userId }).first();
expect(userLoginUpdated.lastLoggedAt).to.deep.equal(new Date('2020-01-01'));
expect(result).to.be.false;
});
});
});

context('when the user has been anonymized', function () {
it('does nothing', async function () {
// given
const userId = databaseBuilder.factory.buildUser({
hasBeenAnonymised: true,
}).id;
databaseBuilder.factory.buildUserLogin({
userId,
lastLoggedAt: null,
});
await databaseBuilder.commit();

// when
const result = await usecases.importUserLastLoggedAt({
dryRun,
userId,
lastActivity: new Date(),
});

// then
const userLoginUpdated = await databaseBuilder.knex('user-logins').where({ userId }).first();
expect(userLoginUpdated.lastLoggedAt).to.be.null;
expect(result).to.be.false;
});
});

context('when the user does not exist', function () {
it('does nothing', async function () {
// when
const result = await usecases.importUserLastLoggedAt({
dryRun,
userId: 789,
lastActivity: new Date(),
});

// then
expect(result).to.be.false;
});
});
});

context('when dryRun is true', function () {
it('does nothing', async function () {
// given
const userId = databaseBuilder.factory.buildUser().id;
databaseBuilder.factory.buildUserLogin({
userId,
lastLoggedAt: null,
});
await databaseBuilder.commit();

// when
await usecases.importUserLastLoggedAt({
dryRun: true,
userId,
lastActivity: new Date(),
});

// then
const userLoginUpdated = await databaseBuilder.knex('user-logins').where({ userId }).first();
expect(userLoginUpdated.lastLoggedAt).to.be.null;
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,35 @@ describe('Integration | Identity Access Management | Infrastructure | Repository
});
});

describe('#findById', function () {
describe('when user exists', function () {
it('returns the user', async function () {
// given
const userInDB = databaseBuilder.factory.buildUser();
await databaseBuilder.commit();

// when
const foundUser = await userRepository.findById(userInDB.id);

// then
expect(foundUser).to.deepEqualInstance(new User(userInDB));
});
});

describe('when user does not exist', function () {
it('returns null', async function () {
// given
const userId = 123456;

// when
const foundUser = await userRepository.findById(userId);

// then
expect(foundUser).to.be.null;
});
});
});

describe('#findPaginatedFiltered', function () {
context('when there are users in the database', function () {
it('returns an array of users', async function () {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
userId,last_activity
1234,2017-09-05 14:00:08+0000
4567,2018-03-02 15:26:16+0000
Loading