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,
};