import mongoose from "mongoose"; const eventSchema = new mongoose.Schema({ title: { type: String, required: true }, slug: { type: String, unique: true }, // Auto-generated in pre-save hook tagline: String, description: { type: String, required: true }, content: String, featureImage: { url: String, // Cloudinary URL publicId: String, // Cloudinary public ID for transformations alt: String, // Alt text for accessibility }, startDate: { type: Date, required: true }, endDate: { type: Date, required: true }, eventType: { type: String, enum: ["community", "workshop", "social", "showcase"], default: "community", }, // Online-first location handling location: { type: String, required: true, // This will typically be a Slack channel or video conference link validate: { validator: function (v) { // Must be either a valid URL or a Slack channel reference const urlPattern = /^https?:\/\/.+/; const slackPattern = /^#[a-zA-Z0-9-_]+$/; return urlPattern.test(v) || slackPattern.test(v); }, message: "Location must be a valid URL (video conference link) or Slack channel (starting with #)", }, }, isOnline: { type: Boolean, default: true }, // Default to online-first // Visibility and status controls isVisible: { type: Boolean, default: true }, // Hide from public calendar when false isCancelled: { type: Boolean, default: false }, cancellationMessage: String, // Custom message for cancelled events membersOnly: { type: Boolean, default: false }, // Series information - embedded approach for better performance series: { id: String, // Simple string ID to group related events title: String, // Series title (e.g., "Cooperative Game Development Workshop Series") description: String, // Series description type: { type: String, enum: [ "workshop_series", "recurring_meetup", "multi_day", "course", "tournament", ], default: "workshop_series", }, position: Number, // Order within the series (e.g., 1 = first event, 2 = second, etc.) totalEvents: Number, // Total planned events in the series isSeriesEvent: { type: Boolean, default: false }, // Flag to identify series events }, // Event pricing for public attendees (deprecated - use tickets instead) pricing: { isFree: { type: Boolean, default: true }, publicPrice: { type: Number, default: 0 }, // Price for non-members currency: { type: String, default: "CAD" }, paymentRequired: { type: Boolean, default: false }, }, // Ticket configuration tickets: { enabled: { type: Boolean, default: false }, public: { available: { type: Boolean, default: false }, name: { type: String, default: "Public Ticket" }, description: String, price: { type: Number, default: 0 }, quantity: Number, // null = unlimited sold: { type: Number, default: 0 }, earlyBirdPrice: Number, earlyBirdDeadline: Date, }, }, // Circle targeting targetCircles: [ { type: String, enum: ["community", "founder", "practitioner"], required: false, }, ], maxAttendees: Number, registrationRequired: { type: Boolean, default: false }, registrationDeadline: Date, agenda: [String], speakers: [ { name: String, role: String, bio: String, }, ], registrations: [ { memberId: { type: mongoose.Schema.Types.ObjectId, ref: "Member" }, // Reference to Member model name: String, email: String, membershipLevel: String, isMember: { type: Boolean, default: false }, paymentStatus: { type: String, enum: ["pending", "completed", "failed", "not_required"], default: "not_required", }, paymentId: String, // Helcim transaction ID amountPaid: { type: Number, default: 0 }, registeredAt: { type: Date, default: Date.now }, }, ], createdBy: { type: String, required: true }, createdAt: { type: Date, default: Date.now }, updatedAt: { type: Date, default: Date.now }, }); // Generate slug from title function generateSlug(title) { return title .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-+|-+$/g, ""); } // Pre-save hook to generate slug eventSchema.pre("save", async function (next) { try { // Always generate slug if it doesn't exist or if title has changed if (!this.slug || this.isNew || this.isModified("title")) { let baseSlug = generateSlug(this.title); let slug = baseSlug; let counter = 1; // Ensure slug is unique while (await this.constructor.findOne({ slug, _id: { $ne: this._id } })) { slug = `${baseSlug}-${counter}`; counter++; } this.slug = slug; } next(); } catch (error) { console.error("Error in pre-save hook:", error); next(error); } }); export default mongoose.models.Event || mongoose.model("Event", eventSchema);