Migrate member fields to snake_case naming convention
All checks were successful
Build And Push Image / docker (push) Successful in 3m11s
All checks were successful
Build And Push Image / docker (push) Successful in 3m11s
Convert field names from space-separated format to snake_case across member API endpoints and validation logic. Add migration guide for reference.
This commit is contained in:
@@ -20,8 +20,8 @@ export default defineEventHandler(async (event) => {
|
||||
// Add computed fields
|
||||
const processedMember = {
|
||||
...member,
|
||||
FullName: `${member['First Name'] || ''} ${member['Last Name'] || ''}`.trim(),
|
||||
FormattedPhone: formatPhoneNumber(member.Phone)
|
||||
FullName: `${member.first_name || ''} ${member.last_name || ''}`.trim(),
|
||||
FormattedPhone: formatPhoneNumber(member.phone)
|
||||
};
|
||||
|
||||
console.log('[api/members/[id].get] ✅ Successfully retrieved member:', id);
|
||||
|
||||
@@ -65,8 +65,8 @@ export default defineEventHandler(async (event) => {
|
||||
// Return processed member
|
||||
const processedMember = {
|
||||
...updatedMember,
|
||||
FullName: `${updatedMember['First Name'] || ''} ${updatedMember['Last Name'] || ''}`.trim(),
|
||||
FormattedPhone: formatPhoneNumber(updatedMember.Phone)
|
||||
FullName: `${updatedMember.first_name || ''} ${updatedMember.last_name || ''}`.trim(),
|
||||
FormattedPhone: formatPhoneNumber(updatedMember.phone)
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -85,34 +85,34 @@ function validateMemberUpdateData(data: any): string[] {
|
||||
const errors: string[] = [];
|
||||
|
||||
// Only validate fields that are provided (partial updates allowed)
|
||||
if (data['First Name'] !== undefined) {
|
||||
if (!data['First Name'] || typeof data['First Name'] !== 'string' || data['First Name'].trim().length < 2) {
|
||||
if (data.first_name !== undefined) {
|
||||
if (!data.first_name || typeof data.first_name !== 'string' || data.first_name.trim().length < 2) {
|
||||
errors.push('First Name must be at least 2 characters');
|
||||
}
|
||||
}
|
||||
|
||||
if (data['Last Name'] !== undefined) {
|
||||
if (!data['Last Name'] || typeof data['Last Name'] !== 'string' || data['Last Name'].trim().length < 2) {
|
||||
if (data.last_name !== undefined) {
|
||||
if (!data.last_name || typeof data.last_name !== 'string' || data.last_name.trim().length < 2) {
|
||||
errors.push('Last Name must be at least 2 characters');
|
||||
}
|
||||
}
|
||||
|
||||
if (data.Email !== undefined) {
|
||||
if (!data.Email || typeof data.Email !== 'string' || !isValidEmail(data.Email)) {
|
||||
if (data.email !== undefined) {
|
||||
if (!data.email || typeof data.email !== 'string' || !isValidEmail(data.email)) {
|
||||
errors.push('Valid email address is required');
|
||||
}
|
||||
}
|
||||
|
||||
// Optional field validation
|
||||
if (data.Phone !== undefined && data.Phone && typeof data.Phone === 'string' && data.Phone.trim()) {
|
||||
if (data.phone !== undefined && data.phone && typeof data.phone === 'string' && data.phone.trim()) {
|
||||
const phoneRegex = /^[\+]?[1-9][\d]{0,15}$/;
|
||||
const cleanPhone = data.Phone.replace(/\D/g, '');
|
||||
const cleanPhone = data.phone.replace(/\D/g, '');
|
||||
if (!phoneRegex.test(cleanPhone)) {
|
||||
errors.push('Phone number format is invalid');
|
||||
}
|
||||
}
|
||||
|
||||
if (data['Membership Status'] !== undefined && !['Active', 'Inactive', 'Pending', 'Expired'].includes(data['Membership Status'])) {
|
||||
if (data.membership_status !== undefined && !['Active', 'Inactive', 'Pending', 'Expired'].includes(data.membership_status)) {
|
||||
errors.push('Invalid membership status');
|
||||
}
|
||||
|
||||
@@ -123,25 +123,25 @@ function sanitizeMemberUpdateData(data: any): Partial<Member> {
|
||||
const sanitized: any = {};
|
||||
|
||||
// Only include fields that are provided (partial updates)
|
||||
if (data['First Name'] !== undefined) sanitized['First Name'] = data['First Name'].trim();
|
||||
if (data['Last Name'] !== undefined) sanitized['Last Name'] = data['Last Name'].trim();
|
||||
if (data.Email !== undefined) sanitized['Email'] = data.Email.trim().toLowerCase();
|
||||
if (data.Phone !== undefined) sanitized.Phone = data.Phone ? data.Phone.trim() : null;
|
||||
if (data.Nationality !== undefined) sanitized.Nationality = data.Nationality ? data.Nationality.trim() : null;
|
||||
if (data.Address !== undefined) sanitized.Address = data.Address ? data.Address.trim() : null;
|
||||
if (data['Date of Birth'] !== undefined) sanitized['Date of Birth'] = data['Date of Birth'];
|
||||
if (data['Member Since'] !== undefined) sanitized['Member Since'] = data['Member Since'];
|
||||
if (data['Membership Date Paid'] !== undefined) sanitized['Membership Date Paid'] = data['Membership Date Paid'];
|
||||
if (data['Payment Due Date'] !== undefined) sanitized['Payment Due Date'] = data['Payment Due Date'];
|
||||
if (data.first_name !== undefined) sanitized.first_name = data.first_name.trim();
|
||||
if (data.last_name !== undefined) sanitized.last_name = data.last_name.trim();
|
||||
if (data.email !== undefined) sanitized.email = data.email.trim().toLowerCase();
|
||||
if (data.phone !== undefined) sanitized.phone = data.phone ? data.phone.trim() : null;
|
||||
if (data.nationality !== undefined) sanitized.nationality = data.nationality ? data.nationality.trim() : null;
|
||||
if (data.address !== undefined) sanitized.address = data.address ? data.address.trim() : null;
|
||||
if (data.date_of_birth !== undefined) sanitized.date_of_birth = data.date_of_birth;
|
||||
if (data.member_since !== undefined) sanitized.member_since = data.member_since;
|
||||
if (data.membership_date_paid !== undefined) sanitized.membership_date_paid = data.membership_date_paid;
|
||||
if (data.payment_due_date !== undefined) sanitized.payment_due_date = data.payment_due_date;
|
||||
|
||||
// Boolean fields
|
||||
if (data['Current Year Dues Paid'] !== undefined) {
|
||||
sanitized['Current Year Dues Paid'] = Boolean(data['Current Year Dues Paid']);
|
||||
if (data.current_year_dues_paid !== undefined) {
|
||||
sanitized.current_year_dues_paid = Boolean(data.current_year_dues_paid) ? 'true' : 'false';
|
||||
}
|
||||
|
||||
// Enum fields
|
||||
if (data['Membership Status'] !== undefined) {
|
||||
sanitized['Membership Status'] = data['Membership Status'];
|
||||
if (data.membership_status !== undefined) {
|
||||
sanitized.membership_status = data.membership_status;
|
||||
}
|
||||
|
||||
return sanitized;
|
||||
|
||||
@@ -33,33 +33,33 @@ export default defineEventHandler(async (event) => {
|
||||
if (searchTerm) {
|
||||
const search = searchTerm.toLowerCase();
|
||||
members = members.filter(member =>
|
||||
member['First Name']?.toLowerCase().includes(search) ||
|
||||
member['Last Name']?.toLowerCase().includes(search) ||
|
||||
member.Email?.toLowerCase().includes(search)
|
||||
member.first_name?.toLowerCase().includes(search) ||
|
||||
member.last_name?.toLowerCase().includes(search) ||
|
||||
member.email?.toLowerCase().includes(search)
|
||||
);
|
||||
console.log('[api/members.get] After search filter:', members.length);
|
||||
}
|
||||
|
||||
if (nationality) {
|
||||
members = members.filter(member => member.Nationality === nationality);
|
||||
members = members.filter(member => member.nationality === nationality);
|
||||
console.log('[api/members.get] After nationality filter:', members.length);
|
||||
}
|
||||
|
||||
if (membershipStatus) {
|
||||
members = members.filter(member => member['Membership Status'] === membershipStatus);
|
||||
members = members.filter(member => member.membership_status === membershipStatus);
|
||||
console.log('[api/members.get] After status filter:', members.length);
|
||||
}
|
||||
|
||||
if (duesPaid === 'true' || duesPaid === 'false') {
|
||||
members = members.filter(member => member['Current Year Dues Paid'] === duesPaid);
|
||||
members = members.filter(member => member.current_year_dues_paid === duesPaid);
|
||||
console.log('[api/members.get] After dues filter:', members.length);
|
||||
}
|
||||
|
||||
// Add computed fields
|
||||
const processedMembers = members.map(member => ({
|
||||
...member,
|
||||
FullName: `${member['First Name'] || ''} ${member['Last Name'] || ''}`.trim(),
|
||||
FormattedPhone: formatPhoneNumber(member.Phone)
|
||||
FullName: `${member.first_name || ''} ${member.last_name || ''}`.trim(),
|
||||
FormattedPhone: formatPhoneNumber(member.phone)
|
||||
}));
|
||||
|
||||
console.log('[api/members.get] ✅ Successfully processed', processedMembers.length, 'members');
|
||||
|
||||
@@ -56,8 +56,8 @@ export default defineEventHandler(async (event) => {
|
||||
// Return processed member
|
||||
const processedMember = {
|
||||
...newMember,
|
||||
FullName: `${newMember['First Name'] || ''} ${newMember['Last Name'] || ''}`.trim(),
|
||||
FormattedPhone: formatPhoneNumber(newMember.Phone)
|
||||
FullName: `${newMember.first_name || ''} ${newMember.last_name || ''}`.trim(),
|
||||
FormattedPhone: formatPhoneNumber(newMember.phone)
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -76,28 +76,28 @@ function validateMemberData(data: any): string[] {
|
||||
const errors: string[] = [];
|
||||
|
||||
// Required fields
|
||||
if (!data['First Name'] || typeof data['First Name'] !== 'string' || data['First Name'].trim().length < 2) {
|
||||
if (!data.first_name || typeof data.first_name !== 'string' || data.first_name.trim().length < 2) {
|
||||
errors.push('First Name is required and must be at least 2 characters');
|
||||
}
|
||||
|
||||
if (!data['Last Name'] || typeof data['Last Name'] !== 'string' || data['Last Name'].trim().length < 2) {
|
||||
if (!data.last_name || typeof data.last_name !== 'string' || data.last_name.trim().length < 2) {
|
||||
errors.push('Last Name is required and must be at least 2 characters');
|
||||
}
|
||||
|
||||
if (!data.Email || typeof data.Email !== 'string' || !isValidEmail(data.Email)) {
|
||||
if (!data.email || typeof data.email !== 'string' || !isValidEmail(data.email)) {
|
||||
errors.push('Valid email address is required');
|
||||
}
|
||||
|
||||
// Optional field validation
|
||||
if (data.Phone && typeof data.Phone === 'string' && data.Phone.trim()) {
|
||||
if (data.phone && typeof data.phone === 'string' && data.phone.trim()) {
|
||||
const phoneRegex = /^[\+]?[1-9][\d]{0,15}$/;
|
||||
const cleanPhone = data.Phone.replace(/\D/g, '');
|
||||
const cleanPhone = data.phone.replace(/\D/g, '');
|
||||
if (!phoneRegex.test(cleanPhone)) {
|
||||
errors.push('Phone number format is invalid');
|
||||
}
|
||||
}
|
||||
|
||||
if (data['Membership Status'] && !['Active', 'Inactive', 'Pending', 'Expired'].includes(data['Membership Status'])) {
|
||||
if (data.membership_status && !['Active', 'Inactive', 'Pending', 'Expired'].includes(data.membership_status)) {
|
||||
errors.push('Invalid membership status');
|
||||
}
|
||||
|
||||
@@ -108,24 +108,24 @@ function sanitizeMemberData(data: any): Partial<Member> {
|
||||
const sanitized: any = {};
|
||||
|
||||
// Required fields
|
||||
sanitized['First Name'] = data['First Name'].trim();
|
||||
sanitized['Last Name'] = data['Last Name'].trim();
|
||||
sanitized['Email'] = data.Email.trim().toLowerCase();
|
||||
sanitized.first_name = data.first_name.trim();
|
||||
sanitized.last_name = data.last_name.trim();
|
||||
sanitized.email = data.email.trim().toLowerCase();
|
||||
|
||||
// Optional fields
|
||||
if (data.Phone) sanitized.Phone = data.Phone.trim();
|
||||
if (data.Nationality) sanitized.Nationality = data.Nationality.trim();
|
||||
if (data.Address) sanitized.Address = data.Address.trim();
|
||||
if (data['Date of Birth']) sanitized['Date of Birth'] = data['Date of Birth'];
|
||||
if (data['Member Since']) sanitized['Member Since'] = data['Member Since'];
|
||||
if (data['Membership Date Paid']) sanitized['Membership Date Paid'] = data['Membership Date Paid'];
|
||||
if (data['Payment Due Date']) sanitized['Payment Due Date'] = data['Payment Due Date'];
|
||||
if (data.phone) sanitized.phone = data.phone.trim();
|
||||
if (data.nationality) sanitized.nationality = data.nationality.trim();
|
||||
if (data.address) sanitized.address = data.address.trim();
|
||||
if (data.date_of_birth) sanitized.date_of_birth = data.date_of_birth;
|
||||
if (data.member_since) sanitized.member_since = data.member_since;
|
||||
if (data.membership_date_paid) sanitized.membership_date_paid = data.membership_date_paid;
|
||||
if (data.payment_due_date) sanitized.payment_due_date = data.payment_due_date;
|
||||
|
||||
// Boolean fields
|
||||
sanitized['Current Year Dues Paid'] = Boolean(data['Current Year Dues Paid']);
|
||||
sanitized.current_year_dues_paid = Boolean(data.current_year_dues_paid) ? 'true' : 'false';
|
||||
|
||||
// Enum fields
|
||||
sanitized['Membership Status'] = data['Membership Status'] || 'Pending';
|
||||
sanitized.membership_status = data.membership_status || 'Pending';
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
@@ -189,18 +189,18 @@ export const createMember = async (data: Partial<Member>): Promise<Member> => {
|
||||
|
||||
// Only include fields that are part of the member schema
|
||||
const allowedFields = [
|
||||
"First Name",
|
||||
"Last Name",
|
||||
"Email",
|
||||
"Phone",
|
||||
"Current Year Dues Paid",
|
||||
"Nationality",
|
||||
"Date of Birth",
|
||||
"Membership Date Paid",
|
||||
"Payment Due Date",
|
||||
"Membership Status",
|
||||
"Address",
|
||||
"Member Since"
|
||||
"first_name",
|
||||
"last_name",
|
||||
"email",
|
||||
"phone",
|
||||
"current_year_dues_paid",
|
||||
"nationality",
|
||||
"date_of_birth",
|
||||
"membership_date_paid",
|
||||
"payment_due_date",
|
||||
"membership_status",
|
||||
"address",
|
||||
"member_since"
|
||||
];
|
||||
|
||||
// Filter the data to only include allowed fields
|
||||
@@ -218,14 +218,14 @@ export const createMember = async (data: Partial<Member>): Promise<Member> => {
|
||||
delete cleanData.FormattedPhone;
|
||||
|
||||
// Fix date formatting for PostgreSQL
|
||||
if (cleanData['Date of Birth']) {
|
||||
cleanData['Date of Birth'] = convertDateFormat(cleanData['Date of Birth']);
|
||||
if (cleanData['date_of_birth']) {
|
||||
cleanData['date_of_birth'] = convertDateFormat(cleanData['date_of_birth']);
|
||||
}
|
||||
if (cleanData['Membership Date Paid']) {
|
||||
cleanData['Membership Date Paid'] = convertDateFormat(cleanData['Membership Date Paid']);
|
||||
if (cleanData['membership_date_paid']) {
|
||||
cleanData['membership_date_paid'] = convertDateFormat(cleanData['membership_date_paid']);
|
||||
}
|
||||
if (cleanData['Payment Due Date']) {
|
||||
cleanData['Payment Due Date'] = convertDateFormat(cleanData['Payment Due Date']);
|
||||
if (cleanData['payment_due_date']) {
|
||||
cleanData['payment_due_date'] = convertDateFormat(cleanData['payment_due_date']);
|
||||
}
|
||||
|
||||
console.log('[nocodb.createMember] Clean data fields:', Object.keys(cleanData));
|
||||
@@ -271,18 +271,18 @@ export const updateMember = async (id: string, data: Partial<Member>, retryCount
|
||||
|
||||
// Only include fields that are part of the member schema
|
||||
const allowedFields = [
|
||||
"First Name",
|
||||
"Last Name",
|
||||
"Email",
|
||||
"Phone",
|
||||
"Current Year Dues Paid",
|
||||
"Nationality",
|
||||
"Date of Birth",
|
||||
"Membership Date Paid",
|
||||
"Payment Due Date",
|
||||
"Membership Status",
|
||||
"Address",
|
||||
"Member Since"
|
||||
"first_name",
|
||||
"last_name",
|
||||
"email",
|
||||
"phone",
|
||||
"current_year_dues_paid",
|
||||
"nationality",
|
||||
"date_of_birth",
|
||||
"membership_date_paid",
|
||||
"payment_due_date",
|
||||
"membership_status",
|
||||
"address",
|
||||
"member_since"
|
||||
];
|
||||
|
||||
// Filter the data to only include allowed fields
|
||||
@@ -301,14 +301,14 @@ export const updateMember = async (id: string, data: Partial<Member>, retryCount
|
||||
}
|
||||
|
||||
// Fix date formatting for PostgreSQL
|
||||
if (cleanData['Date of Birth']) {
|
||||
cleanData['Date of Birth'] = convertDateFormat(cleanData['Date of Birth']);
|
||||
if (cleanData['date_of_birth']) {
|
||||
cleanData['date_of_birth'] = convertDateFormat(cleanData['date_of_birth']);
|
||||
}
|
||||
if (cleanData['Membership Date Paid']) {
|
||||
cleanData['Membership Date Paid'] = convertDateFormat(cleanData['Membership Date Paid']);
|
||||
if (cleanData['membership_date_paid']) {
|
||||
cleanData['membership_date_paid'] = convertDateFormat(cleanData['membership_date_paid']);
|
||||
}
|
||||
if (cleanData['Payment Due Date']) {
|
||||
cleanData['Payment Due Date'] = convertDateFormat(cleanData['Payment Due Date']);
|
||||
if (cleanData['payment_due_date']) {
|
||||
cleanData['payment_due_date'] = convertDateFormat(cleanData['payment_due_date']);
|
||||
}
|
||||
|
||||
console.log('[nocodb.updateMember] Clean data fields:', Object.keys(cleanData));
|
||||
|
||||
Reference in New Issue
Block a user