(core) Adding new column in users table "ref" with unique identifier.

Summary:
There is a new column in users table called ref (user reference).
It holds user's unique reference number that can be used for features
that require some kind of ownership logic (like comments).

Test Plan: Updated tests

Reviewers: georgegevoian, paulfitz

Reviewed By: georgegevoian, paulfitz

Differential Revision: https://phab.getgrist.com/D3641
This commit is contained in:
Jarosław Sadziński
2022-10-03 16:45:44 +02:00
parent 356090abae
commit 9628253fd8
16 changed files with 189 additions and 33 deletions

View File

@@ -1,6 +1,7 @@
import {UserOptions} from 'app/common/UserAPI';
import {nativeValues} from 'app/gen-server/lib/values';
import {BaseEntity, Column, Entity, JoinTable, ManyToMany, OneToMany, OneToOne,
import {makeId} from 'app/server/lib/idUtils';
import {BaseEntity, BeforeInsert, Column, Entity, JoinTable, ManyToMany, OneToMany, OneToOne,
PrimaryGeneratedColumn} from "typeorm";
import {Group} from "./Group";
@@ -50,6 +51,19 @@ export class User extends BaseEntity {
@Column({name: 'connect_id', type: String, nullable: true})
public connectId: string | null;
/**
* Unique reference for this user. Primarily used as an ownership key in a cell metadata (comments).
*/
@Column({name: 'ref', type: String, nullable: false})
public ref: string;
@BeforeInsert()
public async beforeInsert() {
if (!this.ref) {
this.ref = makeId();
}
}
/**
* Get user's email. Returns undefined if logins has not been joined, or no login
* is available

View File

@@ -419,6 +419,15 @@ export class HomeDBManager extends EventEmitter {
throw new Error(`Cannot testGetId(${name})`);
}
/**
* For tests only. Get user's unique reference by name.
*/
public async testGetRef(name: string): Promise<string> {
const user = await User.findOne({where: {name}});
if (user) { return user.ref; }
throw new Error(`Cannot testGetRef(${name})`);
}
/**
* Clear all user preferences associated with the given email addresses.
* For use in tests.
@@ -2537,6 +2546,7 @@ export class HomeDBManager extends EventEmitter {
const login = new Login();
login.displayEmail = login.email = ANONYMOUS_USER_EMAIL;
user.logins = [login];
user.ref = '';
return user;
}
@@ -3917,6 +3927,9 @@ export class HomeDBManager extends EventEmitter {
cond = cond.orWhere(`gu3.user_id = ${users}`);
// Support the special "everyone" user.
const everyoneId = this._specialUserIds[EVERYONE_EMAIL];
if (everyoneId === undefined) {
throw new Error("Special user id for EVERYONE_EMAIL not found");
}
cond = cond.orWhere(`gu0.user_id = ${everyoneId}`);
cond = cond.orWhere(`gu1.user_id = ${everyoneId}`);
cond = cond.orWhere(`gu2.user_id = ${everyoneId}`);
@@ -3925,6 +3938,9 @@ export class HomeDBManager extends EventEmitter {
// Support also the special anonymous user. Currently, by convention, sharing a
// resource with anonymous should make it listable.
const anonId = this._specialUserIds[ANONYMOUS_USER_EMAIL];
if (anonId === undefined) {
throw new Error("Special user id for ANONYMOUS_USER_EMAIL not found");
}
cond = cond.orWhere(`gu0.user_id = ${anonId}`);
cond = cond.orWhere(`gu1.user_id = ${anonId}`);
cond = cond.orWhere(`gu2.user_id = ${anonId}`);

View File

@@ -0,0 +1,33 @@
import {User} from 'app/gen-server/entity/User';
import {makeId} from 'app/server/lib/idUtils';
import {MigrationInterface, QueryRunner, TableColumn} from "typeorm";
export class UserUUID1663851423064 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> {
// Add ref column, for now make it nullable and not unique, we
// first need to put a value in it for all existing users.
await queryRunner.addColumn("users", new TableColumn({
name: "ref",
type: 'varchar',
isNullable: true,
isUnique: false,
}));
// Updating so many rows in a multiple queries is not ideal. We will send updates in chunks.
// 300 seems to be a good number, for 24k rows we have 80 queries.
const userList = await queryRunner.manager.createQueryBuilder()
.select("users")
.from(User, "users")
.getMany();
userList.forEach(u => u.ref = makeId());
await queryRunner.manager.save(userList, { chunk: 300 });
// We are not making this column unique yet, because it can fail
// if there are some old workers still running, and any new user
// is created. We will make it unique in a later migration.
}
public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.dropColumn("users", "ref");
}
}

View File

@@ -0,0 +1,37 @@
import {User} from 'app/gen-server/entity/User';
import {makeId} from 'app/server/lib/idUtils';
import {MigrationInterface, QueryRunner} from "typeorm";
export class UserRefUnique1664528376930 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// This is second part of migration 1663851423064-UserUUID, that makes
// the ref column unique.
// Update users that don't have unique ref set.
const userList = await queryRunner.manager.createQueryBuilder()
.select("users")
.from(User, "users")
.where("ref is null")
.getMany();
userList.forEach(u => u.ref = makeId());
await queryRunner.manager.save(userList, {chunk: 300});
// Mark column as unique and non-nullable.
const users = (await queryRunner.getTable('users'))!;
const oldRef = users.findColumnByName('ref')!;
const newRef = oldRef.clone();
newRef.isUnique = true;
newRef.isNullable = false;
await queryRunner.changeColumn('users', oldRef, newRef);
}
public async down(queryRunner: QueryRunner): Promise<void> {
// Mark column as non unique and nullable.
const users = (await queryRunner.getTable('users'))!;
const oldRef = users.findColumnByName('ref')!;
const newRef = oldRef.clone();
newRef.isUnique = false;
newRef.isNullable = true;
await queryRunner.changeColumn('users', oldRef, newRef);
}
}