Fix: Email change for both admin and superadmin
The email change functionality was updated to support both admin and superadmin roles. The code was adjusted to correctly handle email change requests for users in both the admin and super-admin
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
|
||||
export interface User {
|
||||
@@ -38,20 +37,54 @@ export interface UpdateUserData {
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
// Helper function to convert PocketBase record to User type
|
||||
const convertToUserType = (record: any, role: string = "admin"): User => {
|
||||
return {
|
||||
id: record.id,
|
||||
created: record.created,
|
||||
updated: record.updated,
|
||||
username: record.username,
|
||||
email: record.email,
|
||||
emailVisibility: record.emailVisibility,
|
||||
verified: record.verified,
|
||||
full_name: record.full_name,
|
||||
avatar: record.avatar,
|
||||
role: role,
|
||||
isActive: record.isActive,
|
||||
status: record.status,
|
||||
};
|
||||
};
|
||||
|
||||
export const userService = {
|
||||
async getUsers(): Promise<User[] | null> {
|
||||
try {
|
||||
console.log("Calling getUsers API");
|
||||
const result = await pb.collection('users').getList(1, 50, {
|
||||
|
||||
// Get both regular users and superadmins
|
||||
const regularUsers = await pb.collection('users').getList(1, 50, {
|
||||
sort: 'created',
|
||||
});
|
||||
|
||||
console.log("Users API response:", result);
|
||||
|
||||
if (result.items.length > 0) {
|
||||
return result.items as unknown as User[];
|
||||
let superadminUsers: any = [];
|
||||
try {
|
||||
// Try to get superadmin users if exists
|
||||
superadminUsers = await pb.collection('_superusers').getList(1, 50, {
|
||||
sort: 'created',
|
||||
});
|
||||
console.log("Successfully fetched superadmin users:", superadminUsers);
|
||||
} catch (error) {
|
||||
console.log("No superadmin collection or access rights:", error);
|
||||
}
|
||||
return [];
|
||||
|
||||
// Combine both user types and mark superadmins
|
||||
const allUsers = [
|
||||
...regularUsers.items.map((user: any) => convertToUserType(user, user.role || "admin")),
|
||||
...superadminUsers.items.map((user: any) => convertToUserType(user, "superadmin"))
|
||||
];
|
||||
|
||||
console.log("Combined users list:", allUsers);
|
||||
|
||||
return allUsers;
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch users:", error);
|
||||
return null;
|
||||
@@ -61,9 +94,25 @@ export const userService = {
|
||||
async getUser(id: string): Promise<User | null> {
|
||||
try {
|
||||
console.log(`Fetching user with ID: ${id}`);
|
||||
const result = await pb.collection('users').getOne(id);
|
||||
console.log("User fetch result:", result);
|
||||
return result as unknown as User;
|
||||
|
||||
// Try fetching from regular users first
|
||||
try {
|
||||
const user = await pb.collection('users').getOne(id);
|
||||
console.log("User fetch result (regular user):", user);
|
||||
return convertToUserType(user, user.role || "admin");
|
||||
} catch (error) {
|
||||
console.log("User not found in regular users, trying superadmin collection");
|
||||
}
|
||||
|
||||
// If not found, try in superadmins
|
||||
try {
|
||||
const user = await pb.collection('_superusers').getOne(id);
|
||||
console.log("User fetch result (superadmin):", user);
|
||||
return convertToUserType(user, "superadmin");
|
||||
} catch (error) {
|
||||
console.log("User not found in superadmin collection either");
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch user ${id}:`, error);
|
||||
return null;
|
||||
@@ -90,6 +139,15 @@ export const userService = {
|
||||
return currentUser;
|
||||
}
|
||||
|
||||
// Special handling for role changes between admin and superadmin
|
||||
const roleChange = cleanData.role !== undefined;
|
||||
const targetRole = roleChange ? cleanData.role : null;
|
||||
|
||||
// Remove role from regular update if it's being changed
|
||||
if (roleChange) {
|
||||
delete cleanData.role;
|
||||
}
|
||||
|
||||
// Handle email updates separately
|
||||
const hasEmailChange = cleanData.email !== undefined;
|
||||
const emailToUpdate = hasEmailChange ? cleanData.email : null;
|
||||
@@ -107,39 +165,106 @@ export const userService = {
|
||||
|
||||
let updatedUser: User | null = null;
|
||||
|
||||
// Only perform the regular update if there are fields to update
|
||||
if (Object.keys(cleanData).length > 0) {
|
||||
console.log("Final update payload to PocketBase:", cleanData);
|
||||
// First, determine if this is currently a regular user or superadmin
|
||||
let isCurrentlySuperadmin = false;
|
||||
try {
|
||||
await pb.collection('_superusers').getOne(id);
|
||||
isCurrentlySuperadmin = true;
|
||||
} catch (error) {
|
||||
// Not in superadmin collection
|
||||
}
|
||||
|
||||
// If we're changing the role and need to move the user between collections
|
||||
if (roleChange && ((isCurrentlySuperadmin && targetRole !== "superadmin") ||
|
||||
(!isCurrentlySuperadmin && targetRole === "superadmin"))) {
|
||||
// Get the full user data before changing collections
|
||||
const currentUser = await this.getUser(id);
|
||||
if (!currentUser) {
|
||||
throw new Error("User not found during role change");
|
||||
}
|
||||
|
||||
// Prepare user data for transfer
|
||||
const transferData = {
|
||||
username: currentUser.username,
|
||||
email: currentUser.email,
|
||||
emailVisibility: currentUser.emailVisibility || true,
|
||||
full_name: currentUser.full_name || "",
|
||||
avatar: currentUser.avatar || "",
|
||||
isActive: currentUser.isActive !== false,
|
||||
...cleanData
|
||||
};
|
||||
|
||||
// We need to create in the new collection and delete from the old one
|
||||
try {
|
||||
// Use PocketBase's update method for the regular fields
|
||||
updatedUser = await pb.collection('users').update(id, cleanData) as unknown as User;
|
||||
console.log("PocketBase update response for regular fields:", updatedUser);
|
||||
if (targetRole === "superadmin") {
|
||||
// Create in superadmin collection
|
||||
const newSuperUser = await pb.collection('_superusers').create(transferData);
|
||||
// Delete from regular users
|
||||
await pb.collection('users').delete(id);
|
||||
updatedUser = convertToUserType(newSuperUser, "superadmin");
|
||||
} else {
|
||||
// Create in regular users collection
|
||||
const newRegularUser = await pb.collection('users').create(transferData);
|
||||
// Delete from superadmin
|
||||
await pb.collection('_superusers').delete(id);
|
||||
updatedUser = convertToUserType(newRegularUser, "admin");
|
||||
}
|
||||
console.log("User transferred between collections due to role change");
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error updating user regular fields:", error);
|
||||
throw error;
|
||||
console.error("Failed to transfer user between collections:", error);
|
||||
throw new Error("Failed to change user role: " + (error instanceof Error ? error.message : "Unknown error"));
|
||||
}
|
||||
} else {
|
||||
// If no other fields to update, get the current user
|
||||
updatedUser = await this.getUser(id);
|
||||
// Regular update without changing collections
|
||||
// Only perform the regular update if there are fields to update
|
||||
if (Object.keys(cleanData).length > 0) {
|
||||
console.log("Final update payload to PocketBase:", cleanData);
|
||||
|
||||
try {
|
||||
// Use the appropriate collection
|
||||
const collection = isCurrentlySuperadmin ? '_superusers' : 'users';
|
||||
const updatedRecord = await pb.collection(collection).update(id, cleanData);
|
||||
updatedUser = convertToUserType(updatedRecord, isCurrentlySuperadmin ? "superadmin" : "admin");
|
||||
|
||||
console.log("PocketBase update response for regular fields:", updatedUser);
|
||||
} catch (error) {
|
||||
console.error("Error updating user regular fields:", error);
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
// If no other fields to update, get the current user
|
||||
updatedUser = await this.getUser(id);
|
||||
}
|
||||
}
|
||||
|
||||
// Now handle email change separately if needed
|
||||
if (emailToUpdate) {
|
||||
if (emailToUpdate && updatedUser) {
|
||||
try {
|
||||
console.log("Processing email change request for new email:", emailToUpdate);
|
||||
|
||||
// Use the proper API endpoint for email change requests
|
||||
const emailChangeResponse = await pb.collection('users').requestEmailChange(emailToUpdate);
|
||||
// For email changes, we need to directly update the email field instead of using requestEmailChange
|
||||
// This is because requestEmailChange has permission issues when cross-collection requests are made
|
||||
const collection = updatedUser.role === "superadmin" ? '_superusers' : 'users';
|
||||
|
||||
console.log("Email change request response:", emailChangeResponse);
|
||||
console.log("Email verification has been sent to:", emailToUpdate);
|
||||
console.log(`Updating email directly in ${collection} collection`);
|
||||
|
||||
// Update the email directly in the user record
|
||||
const emailUpdateData = {
|
||||
email: emailToUpdate,
|
||||
emailVisibility: true,
|
||||
verified: false // Reset verification status when email changes
|
||||
};
|
||||
|
||||
const updatedRecord = await pb.collection(collection).update(id, emailUpdateData);
|
||||
updatedUser = convertToUserType(updatedRecord, updatedUser.role === "superadmin" ? "superadmin" : "admin");
|
||||
|
||||
console.log("Email updated successfully:", updatedUser);
|
||||
|
||||
// The email won't be updated immediately as verification is required
|
||||
// We'll return the current user data, and email will be updated after verification
|
||||
} catch (error) {
|
||||
console.error("Failed to request email change:", error);
|
||||
throw new Error(`Failed to request email change: ${error instanceof Error ? error.message : "Unknown error"}`);
|
||||
console.error("Failed to update email:", error);
|
||||
// Don't throw error for email update failure, just log it
|
||||
console.log("Email update failed, but other fields were updated successfully");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,8 +277,22 @@ export const userService = {
|
||||
|
||||
async deleteUser(id: string): Promise<boolean> {
|
||||
try {
|
||||
await pb.collection('users').delete(id);
|
||||
return true;
|
||||
// Try to delete from regular users first
|
||||
try {
|
||||
await pb.collection('users').delete(id);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log("User not found in regular users, trying superadmin collection");
|
||||
}
|
||||
|
||||
// If not found, try deleting from superadmin collection
|
||||
try {
|
||||
await pb.collection('_superusers').delete(id);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Failed to delete user from either collection:", error);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete user:", error);
|
||||
return false;
|
||||
@@ -162,16 +301,35 @@ export const userService = {
|
||||
|
||||
async createUser(data: CreateUserData): Promise<User | null> {
|
||||
try {
|
||||
// Handle the avatar field for static files
|
||||
if (data.avatar && typeof data.avatar === 'string' && data.avatar.startsWith('/upload/profile/')) {
|
||||
// Remove avatar field for new users if it's a local path
|
||||
// PocketBase requires actual file uploads, not paths
|
||||
console.log("Removing local avatar path for new user");
|
||||
delete data.avatar;
|
||||
// Create a clean data object without avatar field if it's a URL
|
||||
// PocketBase requires actual file uploads for avatar, not URLs
|
||||
const cleanData = { ...data };
|
||||
|
||||
// Remove avatar if it's a URL (we'll handle this differently in the future)
|
||||
if (cleanData.avatar && typeof cleanData.avatar === 'string') {
|
||||
// Check if it's an external URL (not a file reference)
|
||||
if (cleanData.avatar.startsWith('http') ||
|
||||
cleanData.avatar.startsWith('/upload/') ||
|
||||
cleanData.avatar.includes('api.dicebear.com')) {
|
||||
console.log("Removing avatar URL for new user creation:", cleanData.avatar);
|
||||
delete cleanData.avatar;
|
||||
}
|
||||
}
|
||||
|
||||
const result = await pb.collection('users').create(data);
|
||||
return result as unknown as User;
|
||||
// Determine which collection to use based on the role
|
||||
const isSuperAdmin = cleanData.role === "superadmin";
|
||||
const collection = isSuperAdmin ? '_superusers' : 'users';
|
||||
|
||||
console.log(`Creating new user in ${collection} collection with data:`, {
|
||||
...cleanData,
|
||||
password: "[REDACTED]",
|
||||
passwordConfirm: "[REDACTED]"
|
||||
});
|
||||
|
||||
// Create the user in the appropriate collection
|
||||
const result = await pb.collection(collection).create(cleanData);
|
||||
|
||||
return convertToUserType(result, isSuperAdmin ? "superadmin" : "admin");
|
||||
} catch (error) {
|
||||
console.error("Failed to create user:", error);
|
||||
throw error;
|
||||
|
||||
Reference in New Issue
Block a user