mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-23 13:46:45 +02:00
chore: amend user-created-missing events (#10333)
## About the changes Users could have been created in Unleash without a corresponding event (a.k.a. audit log), due to a non transactional user insert ([fix](https://github.com/Unleash/unleash/pull/10327)). This could have happened because of providing the wrong role id or some other causes we're not aware of. This amends the situation by inserting an event for each user that exists in the instance (not deleted) and doesn't have it's corresponding user-created event. The event is inserted as already announced because this happened in the past. The event log will look like this (simulated the situation in local dev): ```json { "id": 11, "type": "user-created", "createdBy": "unleash_system_user", "createdAt": "2025-07-08T16:06:17.428Z", "createdByUserId": null, "data": { "id": "6", "email": "xyz@three.com" }, "preData": null, "tags": [], "featureName": null, "project": null, "environment": null, "label": "User created", "summary": "**unleash_system_user** created user ****" } ``` The main problem is we can't create the event in the past, so this will have to do it
This commit is contained in:
parent
d7be47609d
commit
902845bf82
42
src/migrations/20250708173000-amend-users-created-events.js
Normal file
42
src/migrations/20250708173000-amend-users-created-events.js
Normal file
@ -0,0 +1,42 @@
|
||||
exports.up = function(db, cb) {
|
||||
db.runSql(`
|
||||
INSERT INTO events (created_at, created_by, type, data, announced)
|
||||
SELECT
|
||||
u.created_at AS created_at,
|
||||
'unleash_system_user' AS created_by,
|
||||
'user-created' AS type,
|
||||
json_build_object(
|
||||
'id', u.id,
|
||||
'name', u.name,
|
||||
'email', u.email
|
||||
)::jsonb AS data,
|
||||
true AS announced
|
||||
FROM users u
|
||||
WHERE
|
||||
is_system = false
|
||||
AND is_service = false
|
||||
AND deleted_at IS NULL
|
||||
AND email IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM events
|
||||
WHERE type = 'user-created'
|
||||
AND (data ->> 'id')::int = u.id
|
||||
LIMIT 1
|
||||
)
|
||||
ON CONFLICT DO NOTHING;
|
||||
`, (err, results) => {
|
||||
if (err) {
|
||||
console.log('Error inserting user-created events:', err);
|
||||
return cb(err);
|
||||
}
|
||||
if (results.rowCount){
|
||||
console.log('Amended user-created event log. Number of new records:', results.rowCount);
|
||||
}
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function(db, cb) {
|
||||
cb();
|
||||
// No down migration needed as this is a data backfill.
|
||||
};
|
@ -1,3 +1,5 @@
|
||||
import postgresPkg from 'pg';
|
||||
const { Client } = postgresPkg;
|
||||
import {
|
||||
type IUnleashTest,
|
||||
setupAppWithCustomConfig,
|
||||
@ -19,6 +21,11 @@ import { omitKeys } from '../../../../lib/util/omit-keys.js';
|
||||
import type { ISessionStore } from '../../../../lib/types/stores/session-store.js';
|
||||
import type { IUnleashStores } from '../../../../lib/types/index.js';
|
||||
import { createHash } from 'crypto';
|
||||
import { createDb } from '../../../../lib/db/db-pool.js';
|
||||
import { migrateDb } from '../../../../migrator.js';
|
||||
import { createTestConfig } from '../../../config/test-config.js';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { getDbConfig } from '../../../../lib/server-impl.js';
|
||||
|
||||
let stores: IUnleashStores;
|
||||
let db: ITestDb;
|
||||
@ -31,6 +38,67 @@ let sessionStore: ISessionStore;
|
||||
let editorRole: IRole;
|
||||
let adminRole: IRole;
|
||||
|
||||
describe('Users created without an event are amended', () => {
|
||||
const testDbName = `migration_test_${uuidv4().replace(/-/g, '')}`;
|
||||
const dbConnConfig = getDbConfig();
|
||||
beforeAll(async () => {
|
||||
// create a new empty database for this test
|
||||
const client = new Client(dbConnConfig);
|
||||
await client.connect();
|
||||
await client.query(`CREATE DATABASE ${testDbName}`);
|
||||
await client.end();
|
||||
});
|
||||
|
||||
test('should amend users created without an event', async () => {
|
||||
// connect to the new database
|
||||
const config = createTestConfig({
|
||||
db: {
|
||||
...dbConnConfig,
|
||||
ssl: false,
|
||||
database: testDbName,
|
||||
},
|
||||
getLogger,
|
||||
});
|
||||
const db = createDb(config);
|
||||
// migrate up to the migration we want to test
|
||||
await migrateDb(config, '20250707153020-unknown-flags-environment.js');
|
||||
|
||||
const eventsBefore = await db('events').select('*');
|
||||
const insertedUser = (
|
||||
await db('users')
|
||||
.insert({
|
||||
created_at: new Date('2023-01-01T00:00:00Z'),
|
||||
email: 'some@getunelash.io',
|
||||
name: 'Some Name',
|
||||
})
|
||||
.returning('*')
|
||||
)[0];
|
||||
|
||||
expect(insertedUser).toBeDefined();
|
||||
expect(insertedUser.name).toBe('Some Name');
|
||||
expect(insertedUser.created_at).toBeDefined();
|
||||
const eventsAfter = await db('events').select('*');
|
||||
expect(eventsAfter.length).toBe(eventsBefore.length);
|
||||
|
||||
// apply the rest of migrations
|
||||
await migrateDb(config);
|
||||
const eventsPostMigrations = await db('events').select('*');
|
||||
expect(eventsPostMigrations.length).toBe(eventsBefore.length + 1);
|
||||
const userCreatedEvent = eventsPostMigrations.find(
|
||||
(e) => e.type === USER_CREATED && e.data.id === insertedUser.id,
|
||||
);
|
||||
expect(userCreatedEvent).toMatchObject({
|
||||
type: 'user-created',
|
||||
created_at: new Date('2023-01-01T00:00:00Z'),
|
||||
data: {
|
||||
id: insertedUser.id,
|
||||
email: 'some@getunelash.io',
|
||||
name: 'Some Name',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('User Admin API with email configuration', () => {
|
||||
beforeAll(async () => {
|
||||
db = await dbInit('user_admin_api_serial', getLogger);
|
||||
|
Loading…
Reference in New Issue
Block a user