mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
feat: add terms acceptance timestamp tracking and migration script
This commit is contained in:
parent
3e8ef24cfa
commit
44f7aad007
10 changed files with 134 additions and 4 deletions
|
|
@ -77,7 +77,10 @@ const getTermsStatusController = async (req, res) => {
|
|||
if (!user) {
|
||||
return res.status(404).json({ message: 'User not found' });
|
||||
}
|
||||
res.status(200).json({ termsAccepted: !!user.termsAccepted });
|
||||
res.status(200).json({
|
||||
termsAccepted: !!user.termsAccepted,
|
||||
termsAcceptedAt: user.termsAcceptedAt || null,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error fetching terms acceptance status:', error);
|
||||
res.status(500).json({ message: 'Error fetching terms acceptance status' });
|
||||
|
|
@ -86,7 +89,14 @@ const getTermsStatusController = async (req, res) => {
|
|||
|
||||
const acceptTermsController = async (req, res) => {
|
||||
try {
|
||||
const user = await User.findByIdAndUpdate(req.user.id, { termsAccepted: true }, { new: true });
|
||||
const user = await User.findByIdAndUpdate(
|
||||
req.user.id,
|
||||
{
|
||||
termsAccepted: true,
|
||||
termsAcceptedAt: new Date(),
|
||||
},
|
||||
{ new: true },
|
||||
);
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'User not found' });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1068,6 +1068,7 @@ export const useAcceptTermsMutation = (
|
|||
onSuccess: (data, variables, context) => {
|
||||
queryClient.setQueryData<t.TUserTermsResponse>([QueryKeys.userTerms], {
|
||||
termsAccepted: true,
|
||||
termsAcceptedAt: new Date().toISOString(),
|
||||
});
|
||||
options?.onSuccess?.(data, variables, context);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,7 +7,10 @@ const connect = require('./connect');
|
|||
const listUsers = async () => {
|
||||
try {
|
||||
await connect();
|
||||
const users = await User.find({}, 'email provider avatar username name createdAt');
|
||||
const users = await User.find(
|
||||
{},
|
||||
'email provider avatar username name createdAt termsAccepted termsAcceptedAt',
|
||||
);
|
||||
|
||||
console.log('\nUser List:');
|
||||
console.log('----------------------------------------');
|
||||
|
|
@ -18,6 +21,10 @@ const listUsers = async () => {
|
|||
console.log(`Name: ${user.name || 'N/A'}`);
|
||||
console.log(`Provider: ${user.provider || 'email'}`);
|
||||
console.log(`Created: ${user.createdAt}`);
|
||||
console.log(`Terms Accepted: ${user.termsAccepted ? 'Yes' : 'No'}`);
|
||||
console.log(
|
||||
`Terms Accepted At: ${user.termsAcceptedAt ? user.termsAcceptedAt.toISOString() : 'N/A'}`,
|
||||
);
|
||||
console.log('----------------------------------------');
|
||||
});
|
||||
|
||||
|
|
|
|||
100
config/migrate-terms-timestamp.js
Normal file
100
config/migrate-terms-timestamp.js
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
const path = require('path');
|
||||
const mongoose = require('mongoose');
|
||||
const { User } = require('@librechat/data-schemas').createModels(mongoose);
|
||||
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
||||
const { askQuestion, silentExit } = require('./helpers');
|
||||
const connect = require('./connect');
|
||||
|
||||
/**
|
||||
* Migration script for Terms Acceptance Timestamp Tracking
|
||||
*
|
||||
* This script migrates existing users who have termsAccepted: true but no termsAcceptedAt timestamp.
|
||||
* For these users, it sets termsAcceptedAt to their account creation date (createdAt) as a fallback.
|
||||
*
|
||||
* Usage: npm run migrate:terms-timestamp
|
||||
*/
|
||||
(async () => {
|
||||
await connect();
|
||||
|
||||
console.purple('--------------------------');
|
||||
console.purple('Migrate Terms Acceptance Timestamps');
|
||||
console.purple('--------------------------');
|
||||
|
||||
// Count users that need migration
|
||||
const usersToMigrate = await User.countDocuments({
|
||||
termsAccepted: true,
|
||||
$or: [{ termsAcceptedAt: null }, { termsAcceptedAt: { $exists: false } }],
|
||||
});
|
||||
|
||||
if (usersToMigrate === 0) {
|
||||
console.green(
|
||||
'No users need migration. All users with termsAccepted: true already have a termsAcceptedAt timestamp.',
|
||||
);
|
||||
silentExit(0);
|
||||
}
|
||||
|
||||
console.yellow(
|
||||
`Found ${usersToMigrate} user(s) with termsAccepted: true but no termsAcceptedAt timestamp.`,
|
||||
);
|
||||
console.yellow(
|
||||
'These users will have their termsAcceptedAt set to their account creation date (createdAt).',
|
||||
);
|
||||
|
||||
const confirm = await askQuestion('Are you sure you want to proceed? (y/n): ');
|
||||
|
||||
if (confirm.toLowerCase() !== 'y') {
|
||||
console.yellow('Operation cancelled.');
|
||||
silentExit(0);
|
||||
}
|
||||
|
||||
try {
|
||||
// Find all users that need migration and update them
|
||||
const cursor = User.find({
|
||||
termsAccepted: true,
|
||||
$or: [{ termsAcceptedAt: null }, { termsAcceptedAt: { $exists: false } }],
|
||||
}).cursor();
|
||||
|
||||
let migratedCount = 0;
|
||||
let errorCount = 0;
|
||||
|
||||
for await (const user of cursor) {
|
||||
try {
|
||||
// Use createdAt as fallback for termsAcceptedAt
|
||||
const termsAcceptedAt = user.createdAt || new Date();
|
||||
await User.updateOne({ _id: user._id }, { $set: { termsAcceptedAt } });
|
||||
migratedCount++;
|
||||
|
||||
if (migratedCount % 100 === 0) {
|
||||
console.yellow(`Migrated ${migratedCount} users...`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.red(`Error migrating user ${user._id}: ${error.message}`);
|
||||
errorCount++;
|
||||
}
|
||||
}
|
||||
|
||||
console.green(`Migration complete!`);
|
||||
console.green(`Successfully migrated: ${migratedCount} user(s)`);
|
||||
if (errorCount > 0) {
|
||||
console.red(`Errors encountered: ${errorCount}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.red('Error during migration:', error);
|
||||
silentExit(1);
|
||||
}
|
||||
|
||||
silentExit(0);
|
||||
})();
|
||||
|
||||
process.on('uncaughtException', (err) => {
|
||||
if (!err.message.includes('fetch failed')) {
|
||||
console.error('There was an uncaught error:');
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
if (err.message.includes('fetch failed')) {
|
||||
return;
|
||||
} else {
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
|
@ -21,7 +21,10 @@ const connect = require('./connect');
|
|||
}
|
||||
|
||||
try {
|
||||
const result = await User.updateMany({}, { $set: { termsAccepted: false } });
|
||||
const result = await User.updateMany(
|
||||
{},
|
||||
{ $set: { termsAccepted: false, termsAcceptedAt: null } },
|
||||
);
|
||||
console.green(`Updated ${result.modifiedCount} user(s).`);
|
||||
} catch (error) {
|
||||
console.red('Error resetting terms acceptance:', error);
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@
|
|||
"b:balance": "bun config/add-balance.js",
|
||||
"b:list-balances": "bun config/list-balances.js",
|
||||
"reset-terms": "node config/reset-terms.js",
|
||||
"migrate:terms-timestamp": "node config/migrate-terms-timestamp.js",
|
||||
"flush-cache": "node config/flush-cache.js",
|
||||
"migrate:agent-permissions:dry-run": "node config/migrate-agent-permissions.js --dry-run",
|
||||
"migrate:agent-permissions": "node config/migrate-agent-permissions.js",
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ const ALLOWED_USER_FIELDS = [
|
|||
'emailVerified',
|
||||
'twoFactorEnabled',
|
||||
'termsAccepted',
|
||||
'termsAcceptedAt',
|
||||
] as const;
|
||||
|
||||
type AllowedUserField = (typeof ALLOWED_USER_FIELDS)[number];
|
||||
|
|
|
|||
|
|
@ -623,6 +623,7 @@ export type TCustomConfigSpeechResponse = { [key: string]: string };
|
|||
|
||||
export type TUserTermsResponse = {
|
||||
termsAccepted: boolean;
|
||||
termsAcceptedAt: Date | string | null;
|
||||
};
|
||||
|
||||
export type TAcceptTermsResponse = {
|
||||
|
|
|
|||
|
|
@ -132,6 +132,10 @@ const userSchema = new Schema<IUser>(
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
termsAcceptedAt: {
|
||||
type: Date,
|
||||
default: null,
|
||||
},
|
||||
personalization: {
|
||||
type: {
|
||||
memories: {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ export interface IUser extends Document {
|
|||
}>;
|
||||
expiresAt?: Date;
|
||||
termsAccepted?: boolean;
|
||||
termsAcceptedAt?: Date | null;
|
||||
personalization?: {
|
||||
memories?: boolean;
|
||||
};
|
||||
|
|
@ -68,6 +69,7 @@ export interface UpdateUserRequest {
|
|||
plugins?: string[];
|
||||
twoFactorEnabled?: boolean;
|
||||
termsAccepted?: boolean;
|
||||
termsAcceptedAt?: Date | null;
|
||||
personalization?: {
|
||||
memories?: boolean;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue