ghostguild-org/server/models/event.js
Jennie Robinson Faber 3fea484585 Switch UI components to new design system tokens
Standardizes color values and styling using the new tokens:
- Replaces hardcoded colors with semantic variables
- Updates background/text/border classes for light/dark mode
- Migrates inputs to UInput/USelect/UTextarea components
- Removes redundant style declarations
2025-10-13 15:05:29 +01:00

157 lines
4.9 KiB
JavaScript

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);