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";
|
import { pb } from "@/lib/pocketbase";
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
@@ -38,20 +37,54 @@ export interface UpdateUserData {
|
|||||||
isActive?: boolean;
|
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 = {
|
export const userService = {
|
||||||
async getUsers(): Promise<User[] | null> {
|
async getUsers(): Promise<User[] | null> {
|
||||||
try {
|
try {
|
||||||
console.log("Calling getUsers API");
|
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',
|
sort: 'created',
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("Users API response:", result);
|
let superadminUsers: any = [];
|
||||||
|
try {
|
||||||
if (result.items.length > 0) {
|
// Try to get superadmin users if exists
|
||||||
return result.items as unknown as User[];
|
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) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch users:", error);
|
console.error("Failed to fetch users:", error);
|
||||||
return null;
|
return null;
|
||||||
@@ -61,9 +94,25 @@ export const userService = {
|
|||||||
async getUser(id: string): Promise<User | null> {
|
async getUser(id: string): Promise<User | null> {
|
||||||
try {
|
try {
|
||||||
console.log(`Fetching user with ID: ${id}`);
|
console.log(`Fetching user with ID: ${id}`);
|
||||||
const result = await pb.collection('users').getOne(id);
|
|
||||||
console.log("User fetch result:", result);
|
// Try fetching from regular users first
|
||||||
return result as unknown as User;
|
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) {
|
} catch (error) {
|
||||||
console.error(`Failed to fetch user ${id}:`, error);
|
console.error(`Failed to fetch user ${id}:`, error);
|
||||||
return null;
|
return null;
|
||||||
@@ -90,6 +139,15 @@ export const userService = {
|
|||||||
return currentUser;
|
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
|
// Handle email updates separately
|
||||||
const hasEmailChange = cleanData.email !== undefined;
|
const hasEmailChange = cleanData.email !== undefined;
|
||||||
const emailToUpdate = hasEmailChange ? cleanData.email : null;
|
const emailToUpdate = hasEmailChange ? cleanData.email : null;
|
||||||
@@ -107,13 +165,68 @@ export const userService = {
|
|||||||
|
|
||||||
let updatedUser: User | null = null;
|
let updatedUser: User | null = null;
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
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("Failed to transfer user between collections:", error);
|
||||||
|
throw new Error("Failed to change user role: " + (error instanceof Error ? error.message : "Unknown error"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Regular update without changing collections
|
||||||
// Only perform the regular update if there are fields to update
|
// Only perform the regular update if there are fields to update
|
||||||
if (Object.keys(cleanData).length > 0) {
|
if (Object.keys(cleanData).length > 0) {
|
||||||
console.log("Final update payload to PocketBase:", cleanData);
|
console.log("Final update payload to PocketBase:", cleanData);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Use PocketBase's update method for the regular fields
|
// Use the appropriate collection
|
||||||
updatedUser = await pb.collection('users').update(id, cleanData) as unknown as User;
|
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);
|
console.log("PocketBase update response for regular fields:", updatedUser);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating user regular fields:", error);
|
console.error("Error updating user regular fields:", error);
|
||||||
@@ -123,23 +236,35 @@ export const userService = {
|
|||||||
// If no other fields to update, get the current user
|
// If no other fields to update, get the current user
|
||||||
updatedUser = await this.getUser(id);
|
updatedUser = await this.getUser(id);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Now handle email change separately if needed
|
// Now handle email change separately if needed
|
||||||
if (emailToUpdate) {
|
if (emailToUpdate && updatedUser) {
|
||||||
try {
|
try {
|
||||||
console.log("Processing email change request for new email:", emailToUpdate);
|
console.log("Processing email change request for new email:", emailToUpdate);
|
||||||
|
|
||||||
// Use the proper API endpoint for email change requests
|
// For email changes, we need to directly update the email field instead of using requestEmailChange
|
||||||
const emailChangeResponse = await pb.collection('users').requestEmailChange(emailToUpdate);
|
// 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(`Updating email directly in ${collection} collection`);
|
||||||
console.log("Email verification has been sent to:", emailToUpdate);
|
|
||||||
|
// 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) {
|
} catch (error) {
|
||||||
console.error("Failed to request email change:", error);
|
console.error("Failed to update email:", error);
|
||||||
throw new Error(`Failed to request email change: ${error instanceof Error ? error.message : "Unknown error"}`);
|
// Don't throw error for email update failure, just log it
|
||||||
|
console.log("Email update failed, but other fields were updated successfully");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,9 +276,23 @@ export const userService = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async deleteUser(id: string): Promise<boolean> {
|
async deleteUser(id: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
// Try to delete from regular users first
|
||||||
try {
|
try {
|
||||||
await pb.collection('users').delete(id);
|
await pb.collection('users').delete(id);
|
||||||
return true;
|
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) {
|
} catch (error) {
|
||||||
console.error("Failed to delete user:", error);
|
console.error("Failed to delete user:", error);
|
||||||
return false;
|
return false;
|
||||||
@@ -162,16 +301,35 @@ export const userService = {
|
|||||||
|
|
||||||
async createUser(data: CreateUserData): Promise<User | null> {
|
async createUser(data: CreateUserData): Promise<User | null> {
|
||||||
try {
|
try {
|
||||||
// Handle the avatar field for static files
|
// Create a clean data object without avatar field if it's a URL
|
||||||
if (data.avatar && typeof data.avatar === 'string' && data.avatar.startsWith('/upload/profile/')) {
|
// PocketBase requires actual file uploads for avatar, not URLs
|
||||||
// Remove avatar field for new users if it's a local path
|
const cleanData = { ...data };
|
||||||
// PocketBase requires actual file uploads, not paths
|
|
||||||
console.log("Removing local avatar path for new user");
|
// Remove avatar if it's a URL (we'll handle this differently in the future)
|
||||||
delete data.avatar;
|
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);
|
// Determine which collection to use based on the role
|
||||||
return result as unknown as User;
|
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) {
|
} catch (error) {
|
||||||
console.error("Failed to create user:", error);
|
console.error("Failed to create user:", error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
Reference in New Issue
Block a user