// server/models/member.js import mongoose from "mongoose"; import { resolve } from "path"; import { fileURLToPath } from "url"; const __dirname = fileURLToPath(new URL(".", import.meta.url)); // Import configs using dynamic imports to avoid build issues const getValidCircleValues = () => ["community", "founder", "practitioner"]; const memberSchema = new mongoose.Schema({ email: { type: String, required: true, unique: true }, emailHistory: [ { email: { type: String, required: true }, changedAt: { type: Date, default: Date.now }, }, ], name: { type: String, required: true }, circle: { type: String, enum: getValidCircleValues(), required: true, }, contributionAmount: { type: Number, required: true, min: 0, validate: { validator: Number.isInteger, message: 'contributionAmount must be a whole number', }, }, role: { type: String, enum: ["member", "admin"], default: "member", }, status: { type: String, enum: ["pending_payment", "active", "suspended", "cancelled", "guest"], default: "pending_payment", }, helcimCustomerId: String, helcimSubscriptionId: String, billingCadence: { type: String, enum: ['monthly', 'annual'], default: 'monthly', }, paymentMethod: { type: String, enum: ["card", "bank", "none"], default: "none", }, subscriptionStartDate: Date, subscriptionEndDate: Date, nextBillingDate: Date, slackInvited: { type: Boolean, default: false }, slackInviteStatus: { type: String, enum: ["pending", "sent", "failed", "accepted", "joined"], default: "pending", }, slackUserId: String, // Profile fields pronouns: String, timeZone: String, avatar: String, studio: String, bio: String, location: String, socialLinks: { mastodon: String, linkedin: String, website: String, other: String, }, showInDirectory: { type: Boolean, default: true }, craftTags: [String], board: { slackHandle: String, }, notifications: { events: { type: Boolean, default: true }, }, inviteEmailSent: { type: Boolean, default: false }, inviteEmailSentAt: Date, agreement: { acceptedAt: Date, }, taxReceiptPreferences: { filesCanadianTaxes: { type: Boolean, default: false }, middleInitial: { type: String, default: null }, confirmedAddress: { street: { type: String, default: null }, city: { type: String, default: null }, province: { type: String, default: null }, country: { type: String, default: null }, postalCode: { type: String, default: null }, }, setupCompletedAt: { type: Date, default: null }, }, // Magic link single-use enforcement magicLinkJti: String, magicLinkJtiUsed: { type: Boolean, default: false }, // Session revocation via token versioning tokenVersion: { type: Number, default: 0 }, memberNumber: { type: Number, unique: true, sparse: true }, onboarding: { completedAt: { type: Date, default: null }, eventPageVisited: { type: Boolean, default: false }, boardPageVisited: { type: Boolean, default: false }, wikiClicked: { type: Boolean, default: false }, skipped: { profileTags: { type: Boolean, default: false }, visitEvent: { type: Boolean, default: false }, board: { type: Boolean, default: false }, wiki: { type: Boolean, default: false }, }, }, createdAt: { type: Date, default: Date.now }, lastLogin: Date, }); // Check if model already exists to prevent re-compilation in development export default mongoose.models.Member || mongoose.model("Member", memberSchema);