Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Foundation
import Fluent

final class EventDayMigrationV1: AsyncMigration {
Expand All @@ -10,14 +11,26 @@ final class EventDayMigrationV1: AsyncMigration {
.field("end_time", .string, .required)
.field("name", .string, .required)
.create()


// We use this local-only model (instead of the 'real' Slot) to solve a migration step problem,
// whilst also allowing us to drop event and date from Slot
final class MigrationSlot: Model {
static let schema = Schema.slot

@ID(key: .id) var id: UUID?
@Field(key: "date") var date: Date?
@Parent(key: "event_id") var event: Event

init() {}
}

// Custom migratory code to automatically seed the `event_days` table with previous years information
let events = try await Event.query(on: database).all()
let slots = try await Slot.query(on: database).with(\.$event).all()
let slots = try await MigrationSlot.query(on: database).with(\.$event).all()

for event in events {
print("[Migrator] Processing event: \(event.name)")
let eventSlots = slots.filter { $0.event?.id == event.id }
let eventSlots = slots.filter { $0.event.id == event.id }
let uniqueDays = Set(eventSlots.compactMap { $0.date?.withoutTime }).sorted()
print("[Migrator] Found \(eventSlots.count) slots over \(uniqueDays.count) days")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,7 @@ struct SlotRouteController: RouteCollection {

let mutableSlot = slot ?? Slot()
mutableSlot.startDate = input.startTime
mutableSlot.date = inputDate
mutableSlot.duration = duration
mutableSlot.$event.id = try event.requireID()
mutableSlot.$day.id = try eventDay.requireID()

if let activity {
Expand Down
19 changes: 15 additions & 4 deletions Sources/App/Features/Slots/Migrations/Slot+Migration+v3.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,21 @@ struct SlotMigrationV3: AsyncMigration {
try await database.schema(Schema.slot)
.field("day_id", .uuid, .references(Schema.eventDay, "id"))
.update()

let slots = try await Slot.query(on: database).all()

// Define a local-only model to access the old structure
final class MigrationSlot: Model {
static let schema = Schema.slot

@ID(key: .id) var id: UUID?
@Field(key: "date") var date: Date?
@OptionalParent(key: "day_id") var day: EventDay?

init() {}
}

let slots = try await MigrationSlot.query(on: database).all()
let days = try await EventDay.query(on: database).all()

for slot in slots {
if let day = days.first(where: { $0.date.withoutTime == slot.date?.withoutTime }) {
slot.$day.id = try day.requireID()
Expand All @@ -19,7 +30,7 @@ struct SlotMigrationV3: AsyncMigration {
}
}
}

func revert(on database: Database) async throws {
try await database.schema(Schema.slot)
.deleteField("day_id")
Expand Down
58 changes: 41 additions & 17 deletions Sources/App/Features/Slots/Migrations/Slot+Migration+v4.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,38 @@ import Fluent

struct SlotMigrationV4: AsyncMigration {
func prepare(on database: Database) async throws {
fatalError("Read comment from Aug 2024 below")

final class MigrationSlot: Model {
static let schema = Schema.slot

@ID(key: .id) var id: UUID?
@OptionalField(key: "duration") var duration: Double?
@OptionalParent(key: "presentation_id") var presentation: Presentation?
@OptionalParent(key: "activity_id") var activity: Activity?
}

final class MigrationPresentation: Model {
static let schema = Schema.presentation

@ID(key: .id) var id: UUID?
@OptionalField(key: "duration") var duration: Double?
@OptionalField(key: "slot_id") var slotID: UUID?
}

final class MigrationActivity: Model {
static let schema = Schema.activity

@ID(key: .id) var id: UUID?
@OptionalField(key: "duration") var duration: Double?
@OptionalField(key: "slot_id") var slotID: UUID?
}

// fatalError("Read comment from Aug 2024 below")
// Aug 2024: This migrator has been partially commented out as the field it relies on has been removed (as it was no longer needed).
// In order to migrate past this point, you need to go to an earlier commit, migrate, and then come to a more recent commit before finishing
// the migration.
// Or, alternatively, just take a backup of production and apply that locally so you're up to date without playing git games.

/*

try await database.schema(Schema.slot)
.field("presentation_id", .uuid, .references(Schema.presentation, "id"))
.field("activity_id", .uuid, .references(Schema.activity, "id"))
Expand All @@ -25,36 +50,35 @@ struct SlotMigrationV4: AsyncMigration {

// Data Migrator

let slots = try await Slot.query(on: database).all()
let presentations = try await Presentation.query(on: database).with(\.$slot).all()
let activities = try await Activity.query(on: database).with(\.$slot).all()
let slots = try await MigrationSlot.query(on: database).all()
let presentations = try await MigrationPresentation.query(on: database).all()
let activities = try await MigrationActivity.query(on: database).all()

for presentation in presentations {
if let slot = slots.first(where: { $0.id == presentation.slot?.id }) {
if let slot = slots.first(where: { $0.id == presentation.slotID }) {
let slotDuration = slot.duration
slot.$presentation.id = try presentation.requireID()
slot.duration = 0 // easy to set to nil in future cleanup
slot.duration = 0
try await slot.update(on: database)
presentation.$slot.id = nil

presentation.slotID = nil
presentation.duration = slotDuration ?? 0
try await presentation.update(on: database)
}
}

for activity in activities {
if let slot = slots.first(where: { $0.id == activity.slot?.id }) {
if let slot = slots.first(where: { $0.id == activity.slotID }) {
let slotDuration = slot.duration
slot.$activity.id = try activity.requireID()
slot.duration = 0 // easy to set to nil in future cleanup
slot.duration = 0
try await slot.update(on: database)
activity.$slot.id = nil

activity.slotID = nil
activity.duration = slotDuration ?? 0
try await activity.update(on: database)
}
}
*/
}

func revert(on database: Database) async throws {
Expand Down
19 changes: 19 additions & 0 deletions Sources/App/Features/Slots/Migrations/Slot+Migration+v6.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Foundation
import Fluent

struct SlotMigrationV6: AsyncMigration {
func prepare(on db: Database) async throws {
try await db.schema(Schema.slot)
.deleteField("date")
.deleteField("event_id")
.update()
}

func revert(on db: Database) async throws {
try await db.schema(Schema.slot)
.field("date", .datetime)
.field("event_id", .uuid, .references(Schema.event, "id"))
.update()

}
}
21 changes: 7 additions & 14 deletions Sources/App/Features/Slots/Models/Slot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,9 @@ final class Slot: Codable, Model, Content, @unchecked Sendable {

@Field(key: "start_date")
var startDate: String

// DO NOT USE (June 2024)
// TODO: This will be removed in a future PR as part of a cleanup
@Field(key: "date")
var date: Date?

@Field(key: "duration")
var duration: Double?

// DO NOT USE (June 2024)
// TODO: This will be removed in a future PR as part of a cleanup - it needs to be done this way for safe migrations.
@OptionalParent(key: "event_id")
var event: Event?

@OptionalParent(key: "day_id")
var day: EventDay?
Expand All @@ -40,23 +30,26 @@ final class Slot: Codable, Model, Content, @unchecked Sendable {
init(
id: IDValue?,
startDate: String,
date: Date,
duration: Double?
) {
self.id = id
self.startDate = startDate
self.date = date
self.duration = duration
}
}

extension Array where Element == Slot {
var schedule: [[Slot]] {
let dates = Set(compactMap { $0.date?.withoutTime }).sorted(by: (<))
let dates = Set(compactMap { $0.day?.date.withoutTime }).sorted()
var slots: [[Slot]] = []

for date in dates {
slots.append(filter { Calendar.current.compare(date, to: $0.date ?? Date(), toGranularity: .day) == .orderedSame })
slots.append(
filter {
guard let slotDate = $0.day?.date else { return false }
return Calendar.current.isDate(slotDate, inSameDayAs: date)
}
)
}

return slots
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ enum SlotTransformer: Transformer {
return .init(
id: id,
startTime: entity.startDate,
date: entity.date,
date: entity.day?.date,
duration: entity.presentation?.duration ?? entity.activity?.duration ?? entity.duration ?? 0,
presentation: presentation,
activity: activity
Expand Down
1 change: 1 addition & 0 deletions Sources/App/Migrations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class Migrations {
app.migrations.add(SpeakerMigrationV2()) // Add more social link options
app.migrations.add(SlotMigrationV5()) // Remove unneeded slot_id params
app.migrations.add(PresentationMigrationV6()) // Add video visibility
app.migrations.add(SlotMigrationV6()) // Remove legacy date and event_id fields

do {
guard let url = Environment.get("DATABASE_URL") else {
Expand Down
5 changes: 4 additions & 1 deletion Sources/App/routes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,15 @@ func routes(_ app: Application) throws {
.all()
let slots = try await Slot
.query(on: request.db)
.sort(\.$date)
.sort(\.$startDate)
.with(\.$day)
.with(\.$presentation)
.with(\.$activity)
.all()
.sorted {
guard let d1 = $0.day?.date, let d2 = $1.day?.date else { return false }
return d1 < d2
}
let activities = try await Activity
.query(on: request.db)
.sort(\.$event.$id, .descending) // This moves 'Reusable' events to the top of the filtered view
Expand Down
Loading