diff --git a/packages/api/db/migration/20260316000000_add_user_deleted_and_google_sub.js b/packages/api/db/migration/20260316000000_add_user_deleted_and_google_sub.js new file mode 100644 index 0000000000..720049b1cd --- /dev/null +++ b/packages/api/db/migration/20260316000000_add_user_deleted_and_google_sub.js @@ -0,0 +1,45 @@ +/* + * Copyright 2026 LiteFarm.org + * This file is part of LiteFarm. + * + * LiteFarm is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiteFarm is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details, see . + */ + +// As of this migration, new SSO users receive a UUID user_id. The Google sub is stored in google_sub. + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +export const up = async function (knex) { + await knex.schema.alterTable('users', function (table) { + table.boolean('deleted').notNullable().defaultTo(false); + table.string('google_sub').nullable(); + }); + + await knex.raw(`UPDATE users SET google_sub = user_id WHERE user_id ~ '^\\d+$'`); + + await knex.schema.alterTable('users', function (table) { + table.index('google_sub'); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +export const down = async function (knex) { + await knex.schema.alterTable('users', function (table) { + table.dropIndex('google_sub'); + table.dropColumn('deleted'); + table.dropColumn('google_sub'); + }); +}; diff --git a/packages/api/src/models/userModel.js b/packages/api/src/models/userModel.js index 0d974f5c6a..dc9b1f14d4 100644 --- a/packages/api/src/models/userModel.js +++ b/packages/api/src/models/userModel.js @@ -31,19 +31,19 @@ class User extends Model { async $beforeUpdate(opt, queryContext) { await super.$beforeUpdate(opt, queryContext); this.updated_at = new Date().toISOString(); - !queryContext.shouldUpdateEmail && delete this.email; + if (!queryContext.shouldUpdateEmail) delete this.email; } async $beforeInsert(context) { await super.$beforeInsert(context); - this.email && (this.email = this.email.toLowerCase()); - this.first_name && (this.first_name = this.first_name.trim()); - this.last_name && (this.last_name = this.last_name.trim()); + if (this.email) this.email = this.email.toLowerCase(); + if (this.first_name) this.first_name = this.first_name.trim(); + if (this.last_name) this.last_name = this.last_name.trim(); } static async beforeFind(args) { await super.beforeFind(args); - this.email && (this.email = this.email.toLowerCase()); + if (this.email) this.email = this.email.toLowerCase(); } static get tableName() { @@ -55,7 +55,7 @@ class User extends Model { } static get hidden() { - return ['created_at', 'updated_at']; + return ['created_at', 'updated_at', 'deleted', 'google_sub']; } static get hiddenFromOtherUsers() { @@ -130,6 +130,8 @@ class User extends Model { do_not_email: { type: 'boolean' }, created_at: { type: 'string', format: 'date-time' }, updated_at: { type: 'string', format: 'date-time' }, + deleted: { type: 'boolean' }, + google_sub: { type: ['string', 'null'] }, }, additionalProperties: false, };