142 lines
No EOL
4.6 KiB
JavaScript
142 lines
No EOL
4.6 KiB
JavaScript
import mongoose from 'mongoose'
|
|
|
|
const eventSchema = new mongoose.Schema({
|
|
title: { type: String, required: true },
|
|
slug: { type: String, required: true, unique: true },
|
|
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: [{
|
|
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 {
|
|
if (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) |