diff --git a/.env.testing b/.env.testing new file mode 100644 index 00000000..c74aacf1 --- /dev/null +++ b/.env.testing @@ -0,0 +1,4 @@ +PUSH_SECURITY_CODE=1234 +S3_BUCKET_NAME=swiftleeds-speakers +CHECKIN_SECRET=1234 +ENABLE_AASA=true \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 27565f95..fd7570a7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,8 +11,16 @@ concurrency: cancel-in-progress: true jobs: + lint: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: SwiftFormat + run: swiftformat --lint . --reporter github-actions-log + build: if: github.repository_owner == 'SwiftLeeds' + needs: lint runs-on: ubuntu-latest container: swift:6.0-jammy diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..dc5061dc --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,56 @@ +name: Test Application + +on: + pull_request: + push: + branches: [main] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + test: + if: github.repository_owner == 'SwiftLeeds' + runs-on: ubuntu-latest + container: swift:6.0-jammy + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + # actions/cache will detect zstd and will become much faster. + - name: Install zstd + run: | + apt-get update -y + apt-get install -y zstd + + - name: Restore .build + if: ${{ github.run_attempt == 1 }} # Because maybe the cache is causing issues + id: "restore-cache" + uses: actions/cache/restore@v4 + with: + path: .build + key: "test-${{ runner.os }}-${{ github.event.pull_request.base.sha || github.event.after }}" + restore-keys: "test-${{ runner.os }}-" + + - name: Prebuild Application + run: | + apt-get -q update \ + && apt-get -q dist-upgrade -y \ + && apt-get install -y libjemalloc-dev \ + && apt-get install -y libssl-dev \ + && rm -rf /var/lib/apt/lists/* + + swift build --build-tests --enable-code-coverage --force-resolved-versions + + - name: Cache .build + if: steps.restore-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 + with: + path: .build + key: "test-${{ runner.os }}-${{ github.event.pull_request.base.sha || github.event.after }}" + + - name: Run unit tests + run: swift test --skip-build --enable-code-coverage --parallel diff --git a/.gitignore b/.gitignore index 9fd67bf7..df767000 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ DerivedData/ Tests/LinuxMain.swift .vscode .swiftpm/xcode/ -.env.* \ No newline at end of file +.env.* +!.env.testing \ No newline at end of file diff --git a/.swift-version b/.swift-version index 95ee81a4..50495384 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -5.9 +6.0 \ No newline at end of file diff --git a/.swiftformat b/.swiftformat index 698c9700..2b6adf78 100644 --- a/.swiftformat +++ b/.swiftformat @@ -1,5 +1,4 @@ --patternlet inline ---swiftversion 5.6 --trimwhitespace nonblank-lines --ifdef no-indent @@ -9,11 +8,21 @@ --disable redundantReturn --enable isEmpty ---enable sortedSwitchCases +--enable sortSwitchCases --enable wrapEnumCases --enable wrapSwitchCases --enable wrapConditionalBodies --enable blankLinesBetweenImports +--enable blankLineAfterSwitchCase +--enable blankLinesBetweenImports --enable blockComments +--enable emptyExtensions +--enable preferSwiftTesting +--enable singlePropertyPerLine +--enable redundantMemberwiseInit +--enable redundantAsync +--enable redundantEquatable +--enable redundantProperty +--enable redundantThrows --header "" \ No newline at end of file diff --git a/Sources/App/Context/HomeContext.swift b/Sources/App/Context/HomeContext.swift index 40bee650..755b6be2 100644 --- a/Sources/App/Context/HomeContext.swift +++ b/Sources/App/Context/HomeContext.swift @@ -17,8 +17,8 @@ struct HomeContext: Content { var regularDropInSessions: [DropInSession] = [] var groupDropInSessions: [DropInSession] = [] var schedule: [ScheduleDay] = [] - var phase: PhaseContext? = nil - var event: EventContext? = nil + var phase: PhaseContext? + var event: EventContext? } struct EventContext: Codable { @@ -37,33 +37,32 @@ struct EventContext: Codable { let isHidden: Bool init(event: Event) { - self.name = event.name + name = event.name self.event = event.name.components(separatedBy: " ").first ?? "SwiftLeeds" - self.year = event.name.components(separatedBy: " ").last ?? "" + year = event.name.components(separatedBy: " ").last ?? "" // This is a total hack, but means if we set the date of an event to something earlier than 2015 then the event is hidden. // This prevents people accessing that year, useful for development and preparing. // TODO: just make event.date optional in database let isKnownDate = event.date.timeIntervalSince1970 > 1420074000 - self.date = isKnownDate ? event.date : nil - self.dateFormatted = isKnownDate ? Self.buildConferenceDateString(for: event) : nil + date = isKnownDate ? event.date : nil + dateFormatted = isKnownDate ? Self.buildConferenceDateString(for: event) : nil - self.location = event.location + location = event.location - self.isCurrent = event.isCurrent - self.isFuture = event.date > Date() && !isKnownDate - self.isPast = event.date <= Date() && isKnownDate - self.isHidden = isKnownDate != true + isCurrent = event.isCurrent + isFuture = event.date > Date() && !isKnownDate + isPast = event.date <= Date() && isKnownDate + isHidden = isKnownDate != true } private static func buildConferenceDateString(for event: Event) -> String? { let date = event.date let days = event.days - .filter { $0.name.contains("Talkshow") == false } - .count - + .count(where: { $0.name.contains("Talkshow") == false }) + let formatter = DateFormatter() formatter.locale = .init(identifier: "en_US_POSIX") @@ -107,8 +106,8 @@ struct CfpContext: Content { let stage: Stage var faqs: [Question] = [] - var phase: PhaseContext? = nil - var event: EventContext? = nil + var phase: PhaseContext? + var event: EventContext? } struct TeamContext: Content { @@ -122,5 +121,5 @@ struct TeamContext: Content { } var teamMembers: [TeamMember] = [] - var event: EventContext? = nil + var event: EventContext? } diff --git a/Sources/App/Features/Activities/Controllers/ActivityRouteController.swift b/Sources/App/Features/Activities/Controllers/ActivityRouteController.swift index fb183e36..195ca3d6 100644 --- a/Sources/App/Features/Activities/Controllers/ActivityRouteController.swift +++ b/Sources/App/Features/Activities/Controllers/ActivityRouteController.swift @@ -27,7 +27,9 @@ struct ActivityRouteController: RouteCollection { } @Sendable private func onDelete(request: Request) async throws -> Response { - guard let activity = try await Activity.find(request.parameters.get("id"), on: request.db) else { throw Abort(.notFound) } + guard let activity = try await Activity.find(request.parameters.get("id"), on: request.db) else { + throw Abort(.notFound) + } try await activity.delete(on: request.db) return Response(status: .ok, body: .init(string: "OK")) } @@ -62,7 +64,7 @@ struct ActivityRouteController: RouteCollection { return event }() - if let activity = activity { + if let activity { var fileName = activity.image if !image.activityImage.filename.isEmpty { @@ -116,12 +118,14 @@ struct ActivityRouteController: RouteCollection { } // MARK: - ActivityContext + private struct ActivityContext: Content { let activity: Activity? let events: [Event] } // MARK: - FormInput + private struct FormInput: Content { let eventID: String let title: String @@ -132,6 +136,7 @@ struct ActivityRouteController: RouteCollection { } // MARK: - ImageUpload + struct ImageUpload: Content { var activityImage: File } diff --git a/Sources/App/Features/Activities/Transformers/ActivityTransformer.swift b/Sources/App/Features/Activities/Transformers/ActivityTransformer.swift index ab7aa62c..4b32fae6 100644 --- a/Sources/App/Features/Activities/Transformers/ActivityTransformer.swift +++ b/Sources/App/Features/Activities/Transformers/ActivityTransformer.swift @@ -2,7 +2,7 @@ import Foundation enum ActivityTransformer: Transformer { static func transform(_ entity: Activity?) -> ActivityResponse? { - guard let entity = entity, let id = entity.id else { + guard let entity, let id = entity.id else { return nil } diff --git a/Sources/App/Features/Auth/Models/Permissions.swift b/Sources/App/Features/Auth/Models/Permissions.swift index fea0896d..4da56750 100644 --- a/Sources/App/Features/Auth/Models/Permissions.swift +++ b/Sources/App/Features/Auth/Models/Permissions.swift @@ -1,10 +1,3 @@ -// -// Permissions.swift -// swift-leeds -// -// Created by James Sherlock on 12/10/2025. -// - import Vapor enum Permission: String, CaseIterable { diff --git a/Sources/App/Features/Auth/Models/User.swift b/Sources/App/Features/Auth/Models/User.swift index 648aaa7a..fa3c6e1d 100644 --- a/Sources/App/Features/Auth/Models/User.swift +++ b/Sources/App/Features/Auth/Models/User.swift @@ -65,19 +65,19 @@ final class User: Authenticatable, ModelAuthenticatable, Content, ModelSessionAu ) } - public static func credentialsAuthenticator( + static func credentialsAuthenticator( database _: DatabaseID? = nil ) -> any Authenticator { return CustomCredentialsAuthenticator() } - public static func sessionAuthenticator( + static func sessionAuthenticator( _: DatabaseID? = nil ) -> any Authenticator { return SessionAuthenticator() } - public static func authenticator( + static func authenticator( database _: DatabaseID? = nil ) -> any Authenticator { return BearerAuthenticatable() diff --git a/Sources/App/Features/DropIns/Controllers/DropInRouteController.swift b/Sources/App/Features/DropIns/Controllers/DropInRouteController.swift index 8048bf95..20c6eb69 100644 --- a/Sources/App/Features/DropIns/Controllers/DropInRouteController.swift +++ b/Sources/App/Features/DropIns/Controllers/DropInRouteController.swift @@ -50,10 +50,12 @@ struct DropInRouteController: RouteCollection { .filter(\.$id == id) .with(\.$slots) .first() - })?.get() else { throw Abort(.notFound) } + })?.get() else { + throw Abort(.notFound) + } await withThrowingTaskGroup(of: Void.self) { group in - session.slots.forEach { slot in + for slot in session.slots { group.addTask { try await slot.delete(on: request.db) } @@ -99,7 +101,7 @@ struct DropInRouteController: RouteCollection { return (tuple.0.0, date, duration) } - if Set(slotValues.map { $0.date }).count != slotValues.count { + if Set(slotValues.map(\.date)).count != slotValues.count { throw Abort(.badRequest, reason: "Duplicate Slots at Same Time") } @@ -109,7 +111,8 @@ struct DropInRouteController: RouteCollection { // If a slot ID is provided, then query that. Otherwise, create a new slot. guard let slot = slotTuple.id == "" ? DropInSessionSlot() : - try await DropInSessionSlot.find(UUID(uuidString: slotTuple.id), on: request.db).get() else { + try await DropInSessionSlot.find(UUID(uuidString: slotTuple.id), on: request.db).get() + else { throw Abort(.badRequest, reason: "Failed to find existing slot") } @@ -212,7 +215,9 @@ struct DropInRouteController: RouteCollection { } func uploadAndReturnImage(_ image: File?) async throws -> String? { - guard let image = image, image.filename != "" else { return nil } + guard let image, image.filename != "" else { + return nil + } let fileName = "\(UUID.generateRandom().uuidString)-\(image.filename)" try await ImageService.uploadFile(data: Data(image.data.readableBytesView), filename: fileName) @@ -220,6 +225,7 @@ struct DropInRouteController: RouteCollection { } // MARK: - SessionContext + private struct SessionContext: Content { let session: DropInSession? let events: [Event] @@ -227,12 +233,14 @@ struct DropInRouteController: RouteCollection { } // MARK: - ImageInput + private struct ImageInput: Content { let ownerImage: File? let companyImage: File? } // MARK: - FormInput + private struct FormInput: Content { let title: String let description: String @@ -287,7 +295,7 @@ struct DropInRouteController: RouteCollection { .sorted(by: { $0.key < $1.key }) // enumerate them to turn into conference day number .enumerated() - .map { (offset, result) in + .map { offset, result in let eventDay = session.event.days.first(where: { $0.date.withoutTime == result.value[0].date.withoutTime }) return DropInSessionSlotsContext.SlotGroup(title: eventDay?.name ?? "Day \(offset + 1)", slots: result.value) } @@ -304,10 +312,10 @@ struct DropInSessionViewModel: Codable { let owner: String init(model: DropInSession) { - self.id = model.id?.uuidString ?? "" - self.title = model.title - self.description = model.description - self.owner = model.owner + id = model.id?.uuidString ?? "" + title = model.title + description = model.description + owner = model.owner } } diff --git a/Sources/App/Features/DropIns/Models/DropInSession.swift b/Sources/App/Features/DropIns/Models/DropInSession.swift index 7dc3f588..05a6adf1 100644 --- a/Sources/App/Features/DropIns/Models/DropInSession.swift +++ b/Sources/App/Features/DropIns/Models/DropInSession.swift @@ -50,7 +50,7 @@ final class DropInSession: Model, Content, @unchecked Sendable { var isPublic: Bool @Parent(key: "event_id") - public var event: Event + var event: Event @Children(for: \.$session) var slots: [DropInSessionSlot] diff --git a/Sources/App/Features/DropIns/Models/DropInSessionSlot.swift b/Sources/App/Features/DropIns/Models/DropInSessionSlot.swift index 09bd8f8b..292aa911 100644 --- a/Sources/App/Features/DropIns/Models/DropInSessionSlot.swift +++ b/Sources/App/Features/DropIns/Models/DropInSessionSlot.swift @@ -25,7 +25,7 @@ final class DropInSessionSlot: Model, Content, @unchecked Sendable { var duration: Int // in minutes @Parent(key: "session_id") - public var session: DropInSession + var session: DropInSession init() {} } diff --git a/Sources/App/Features/Event Day/Controllers/EventDayRouteController.swift b/Sources/App/Features/Event Day/Controllers/EventDayRouteController.swift index 0a839769..e7c4bce2 100644 --- a/Sources/App/Features/Event Day/Controllers/EventDayRouteController.swift +++ b/Sources/App/Features/Event Day/Controllers/EventDayRouteController.swift @@ -1,5 +1,4 @@ import Fluent -import Fluent import Foundation import Leaf import LeafKit @@ -28,7 +27,9 @@ struct EventDayRouteController: RouteCollection { } @Sendable private func onDelete(request: Request) async throws -> Response { - guard let event = try await EventDay.find(request.parameters.get("id"), on: request.db) else { throw Abort(.notFound) } + guard let event = try await EventDay.find(request.parameters.get("id"), on: request.db) else { + throw Abort(.notFound) + } try await event.delete(on: request.db) return Response(status: .ok, body: .init(string: "OK")) } @@ -74,12 +75,14 @@ struct EventDayRouteController: RouteCollection { } // MARK: - EventContext + private struct EventDayContext: Content { let day: EventDay? let events: [Event] } // MARK: - FormInput + private struct FormInput: Content { let name: String let date: String diff --git a/Sources/App/Features/Event Day/Migrations/EventDay+Migration+V1.swift b/Sources/App/Features/Event Day/Migrations/EventDay+Migration+V1.swift index b4b6ce99..96a7dd97 100644 --- a/Sources/App/Features/Event Day/Migrations/EventDay+Migration+V1.swift +++ b/Sources/App/Features/Event Day/Migrations/EventDay+Migration+V1.swift @@ -1,5 +1,5 @@ -import Foundation import Fluent +import Foundation final class EventDayMigrationV1: AsyncMigration { func prepare(on database: any Database) async throws { diff --git a/Sources/App/Features/Events/Controllers/EventRouteController.swift b/Sources/App/Features/Events/Controllers/EventRouteController.swift index 652c4552..c4225172 100644 --- a/Sources/App/Features/Events/Controllers/EventRouteController.swift +++ b/Sources/App/Features/Events/Controllers/EventRouteController.swift @@ -1,5 +1,4 @@ import Fluent -import Fluent import Foundation import Leaf import LeafKit @@ -24,7 +23,9 @@ struct EventRouteController: RouteCollection { } @Sendable private func onDelete(request: Request) async throws -> Response { - guard let event = try await Event.find(request.parameters.get("id"), on: request.db) else { throw Abort(.notFound) } + guard let event = try await Event.find(request.parameters.get("id"), on: request.db) else { + throw Abort(.notFound) + } try await event.delete(on: request.db) return Response(status: .ok, body: .init(string: "OK")) } @@ -52,7 +53,7 @@ struct EventRouteController: RouteCollection { throw Abort(.badRequest, reason: "Invalid Date Format") } - if let event = event { + if let event { event.name = input.name event.date = date event.location = input.location @@ -90,11 +91,13 @@ struct EventRouteController: RouteCollection { } // MARK: - EventContext + private struct EventContext: Content { let event: Event? } // MARK: - FormInput + private struct FormInput: Content { let name: String let date: String diff --git a/Sources/App/Features/Events/Transformer/EventTransformer.swift b/Sources/App/Features/Events/Transformer/EventTransformer.swift index ded89ea9..c3f854de 100644 --- a/Sources/App/Features/Events/Transformer/EventTransformer.swift +++ b/Sources/App/Features/Events/Transformer/EventTransformer.swift @@ -2,7 +2,9 @@ import Foundation enum EventTransformer: Transformer { static func transform(_ entity: Event?) -> EventResponse? { - guard let entity = entity, let id = entity.id else { return nil } + guard let entity, let id = entity.id else { + return nil + } return .init( id: id, diff --git a/Sources/App/Features/Home/CallForPaperFAQs.swift b/Sources/App/Features/Home/CallForPaperFAQs.swift index 189dae23..1673d572 100644 --- a/Sources/App/Features/Home/CallForPaperFAQs.swift +++ b/Sources/App/Features/Home/CallForPaperFAQs.swift @@ -3,14 +3,14 @@ let callForPaperFAQs: [CfpContext.Question] = [ question: "What topics can I talk about?", answer: [ "It's a long list, but any talks surrounding the Swift language, the ecosystem (such as tooling, Swift Package Manager, Server-side Swift, or other non-Apple platforms), or any Apple operating system are all fair game. We also allow talks on design, architecture, working in software, indie development, testing, and more.", - "If in doubt, we would urge you to submit a proposal or discuss it with our team on Slack." + "If in doubt, we would urge you to submit a proposal or discuss it with our team on Slack.", ] ), .init( question: "I've not spoken before, can I apply?", answer: [ "Absolutely. We welcome all speakers, whether it's their first time or their hundreth time. We provide optional speaker training for all successful applicants, and will work with you to answer any questions or concerns. We're here to help.", - "SwiftLeeds is known for allowing first-time speakers to be on stage and present a talk; many have then gone on to speak at many other conferences worldwide." + "SwiftLeeds is known for allowing first-time speakers to be on stage and present a talk; many have then gone on to speak at many other conferences worldwide.", ] ), .init( @@ -18,26 +18,26 @@ let callForPaperFAQs: [CfpContext.Question] = [ answer: [ "You do not need to pay to talk at SwiftLeeds; successful speakers will receive a ticket for both days at SwiftLeeds free of charge. We also pay for two days of accommodation and breakfast and lunch at the venue on both days.", "As a community conference and a not-for-profit organisation, we do everything we can to keep ticket prices low and deliver the best conference experience.", - "Therefore, we always ask speakers if they can contribute to their travel or ask their employers if they want to sponsor the trip. Still, otherwise, we can and will do everything we can to cover travel up to £1,200 for international travellers, up to £500 for EU-based travellers and £200 for UK-based travellers." + "Therefore, we always ask speakers if they can contribute to their travel or ask their employers if they want to sponsor the trip. Still, otherwise, we can and will do everything we can to cover travel up to £1,200 for international travellers, up to £500 for EU-based travellers and £200 for UK-based travellers.", ] ), .init( question: "Will I get a refund if my talk is accepted?", answer: [ - "Absolutely! We know that some people would like to attend SwiftLeeds regardless of whether their talk is accepted; therefore, we offer a FULL refund if your talk is accepted, no quibbles!" + "Absolutely! We know that some people would like to attend SwiftLeeds regardless of whether their talk is accepted; therefore, we offer a FULL refund if your talk is accepted, no quibbles!", ] ), .init( question: "Are talks recorded or streamed?", answer: [ "Yes! All talks at SwiftLeeds are recorded, and streamed live to remote attendees. Videos are uploaded to our YouTube channel after the event.", - "We also photograph every talk too, which are made available on our Flickr." + "We also photograph every talk too, which are made available on our Flickr.", ] ), .init( question: "I'm deaf, blind, or have other accessibility requirements. Can I apply?", answer: [ - "Absolutely. Let us know by emailing info@swiftleeds.co.uk, and we'll work with you to ensure your needs are met throughout the process and at the venue. Contacting us with your needs will not impact the review of your talk." + "Absolutely. Let us know by emailing info@swiftleeds.co.uk, and we'll work with you to ensure your needs are met throughout the process and at the venue. Contacting us with your needs will not impact the review of your talk.", ] ), .init( @@ -54,7 +54,7 @@ let callForPaperFAQs: [CfpContext.Question] = [ answer: [ "Absolutely! We review all talks anonymously and whether you were unsuccessful previously does not have any impact on your proposal this time.", "SwiftLeeds receives a lot of talk submissions and we're constantly balancing the types of talks to ensure the best experience for everybody.", - "You might find it beneficial to ask a friend to review your proposal. We only see the title and description when reviewing, so it's important to give us all the facts and demonstrate why your talk would be perfect this year." + "You might find it beneficial to ask a friend to review your proposal. We only see the title and description when reviewing, so it's important to give us all the facts and demonstrate why your talk would be perfect this year.", ] ), .init( @@ -62,7 +62,7 @@ let callForPaperFAQs: [CfpContext.Question] = [ answer: [ "Absolutely! We have had speakers from across the globe. In 2023, we had speakers from Senegal, the US and more. Therefore, we are used to dealing with VISA applications to visit the UK.", "This is no problem. You'll be given an invitation letter to upload for the UK VISA application, and we have had substantial success rates with this.", - "We have also called the UK embassy to help proceed with VISA applications. However, sometimes we do get the odd rejection, but we'll support you through the process." + "We have also called the UK embassy to help proceed with VISA applications. However, sometimes we do get the odd rejection, but we'll support you through the process.", ] ), .init( @@ -70,7 +70,7 @@ let callForPaperFAQs: [CfpContext.Question] = [ answer: [ "As you can imagine, reviewing over 150 proposals is incredibly time-consuming, and we make sure that everyone is given a fair evaluation so that this process can take time.", "However, after CfP closes (at the end of April), it's our mission to start the evaluation, and we'll usually begin letting speakers know in May & June.", - "This is based on how long it takes us, so if you've not heard anything, let us know, and we'll update you on where we are in the process." + "This is based on how long it takes us, so if you've not heard anything, let us know, and we'll update you on where we are in the process.", ] - ) + ), ] diff --git a/Sources/App/Features/Home/HomeRouteController.swift b/Sources/App/Features/Home/HomeRouteController.swift index dd62f6ee..c51c7115 100644 --- a/Sources/App/Features/Home/HomeRouteController.swift +++ b/Sources/App/Features/Home/HomeRouteController.swift @@ -92,7 +92,7 @@ struct HomeRouteController: RouteCollection { let eventContext = EventContext(event: event) // Add some protection against unannounced years - if eventContext.isHidden && req.user?.role != .admin { + if eventContext.isHidden, req.user?.role != .admin { throw Abort(.notFound, reason: "Unable to find event") } @@ -185,7 +185,7 @@ struct HomeRouteController: RouteCollection { .filter { $0.day?.event.name == event.name } } - private func getPhase(req: Request, event: Event) throws -> Phase { + private func getPhase(req _: Request, event: Event) throws -> Phase { // This is a little trick where we can set the date to anything older than 2015 to hide date and tickets let isHiddenEvent = event.date.timeIntervalSince1970 < 1420074000 let isPreviousEvent = event.date <= Date() diff --git a/Sources/App/Features/Jobs/Controllers/JobRouteController.swift b/Sources/App/Features/Jobs/Controllers/JobRouteController.swift index d81931a7..0dd2cc56 100644 --- a/Sources/App/Features/Jobs/Controllers/JobRouteController.swift +++ b/Sources/App/Features/Jobs/Controllers/JobRouteController.swift @@ -27,7 +27,9 @@ struct JobRouteController: RouteCollection { } @Sendable private func onDelete(request: Request) async throws -> Response { - guard let job = try await Job.find(request.parameters.get("id"), on: request.db) else { throw Abort(.notFound) } + guard let job = try await Job.find(request.parameters.get("id"), on: request.db) else { + throw Abort(.notFound) + } try await job.delete(on: request.db) return Response(status: .ok, body: .init(string: "OK")) } @@ -51,7 +53,7 @@ struct JobRouteController: RouteCollection { throw Abort(.badRequest, reason: "Could not find sponsor") } - if let job = job { + if let job { job.title = input.title job.location = input.location job.details = input.details @@ -60,11 +62,10 @@ struct JobRouteController: RouteCollection { try await job.update(on: request.db) } else { let job = Job(id: .generateRandom(), - title: input.title, - location: input.location, - details: input.details, - url: input.url - ) + title: input.title, + location: input.location, + details: input.details, + url: input.url) job.$sponsor.id = try sponsor.requireID() try await job.create(on: request.db) @@ -74,6 +75,7 @@ struct JobRouteController: RouteCollection { } // MARK: - FormInput + private struct FormInput: Content { let title: String let location: String diff --git a/Sources/App/Features/Jobs/Model/Middleware/JobMiddleware.swift b/Sources/App/Features/Jobs/Model/Middleware/JobMiddleware.swift index b9c397c7..bfd0118b 100644 --- a/Sources/App/Features/Jobs/Model/Middleware/JobMiddleware.swift +++ b/Sources/App/Features/Jobs/Model/Middleware/JobMiddleware.swift @@ -1,5 +1,5 @@ -import Foundation import Fluent +import Foundation struct JobMiddleware: AsyncModelMiddleware { func create(model: Job, on db: any Database, next: any AnyAsyncModelResponder) async throws { @@ -21,7 +21,9 @@ struct JobMiddleware: AsyncModelMiddleware { guard let lastUpdated = try await LastUpdated .query(on: db) .first() - else { throw ModelMiddlewareError.couldNotUpdateTimestamp } + else { + throw ModelMiddlewareError.couldNotUpdateTimestamp + } let updatedDate = Date() print("Job \(title) was updated - \(updatedDate)") diff --git a/Sources/App/Features/Jobs/Transformers/JobTransformer.swift b/Sources/App/Features/Jobs/Transformers/JobTransformer.swift index 0b1cd608..c7670a1a 100644 --- a/Sources/App/Features/Jobs/Transformers/JobTransformer.swift +++ b/Sources/App/Features/Jobs/Transformers/JobTransformer.swift @@ -1,9 +1,11 @@ -//import Foundation +// import Foundation import Vapor enum JobTransformer: Transformer { static func transform(_ entity: Job?) -> JobResponse? { - guard let entity else { return nil } + guard let entity else { + return nil + } return .init( id: entity.id, diff --git a/Sources/App/Features/Location Categories/Transformers/LocationCategoryTransformer.swift b/Sources/App/Features/Location Categories/Transformers/LocationCategoryTransformer.swift index c76470b5..3ad92831 100644 --- a/Sources/App/Features/Location Categories/Transformers/LocationCategoryTransformer.swift +++ b/Sources/App/Features/Location Categories/Transformers/LocationCategoryTransformer.swift @@ -3,7 +3,7 @@ import Vapor enum LocationCategoryTransformer: Transformer { static func transform(_ entity: LocationCategory?) -> LocationCategoryResponse? { - guard let entity = entity, let id = entity.id else { + guard let entity, let id = entity.id else { return nil } diff --git a/Sources/App/Features/Locations/Transformers/LocationTransformer.swift b/Sources/App/Features/Locations/Transformers/LocationTransformer.swift index fc1b7ff6..87caf05b 100644 --- a/Sources/App/Features/Locations/Transformers/LocationTransformer.swift +++ b/Sources/App/Features/Locations/Transformers/LocationTransformer.swift @@ -2,7 +2,7 @@ import Foundation enum LocationTransformer: Transformer { static func transform(_ entity: Location?) -> LocationResponse? { - guard let entity = entity, let id = entity.id else { + guard let entity, let id = entity.id else { return nil } diff --git a/Sources/App/Features/Presentations/Controllers/PresentationRouteController.swift b/Sources/App/Features/Presentations/Controllers/PresentationRouteController.swift index 8e0b0ef2..bc21647c 100644 --- a/Sources/App/Features/Presentations/Controllers/PresentationRouteController.swift +++ b/Sources/App/Features/Presentations/Controllers/PresentationRouteController.swift @@ -23,7 +23,9 @@ struct PresentationRouteController: RouteCollection { } @Sendable private func onDelete(request: Request) async throws -> Response { - guard let presentation = try await Presentation.find(request.parameters.get("id"), on: request.db) else { throw Abort(.notFound) } + guard let presentation = try await Presentation.find(request.parameters.get("id"), on: request.db) else { + throw Abort(.notFound) + } try await presentation.delete(on: request.db) return Response(status: .ok, body: .init(string: "OK")) } @@ -50,7 +52,7 @@ struct PresentationRouteController: RouteCollection { throw Abort(.badRequest, reason: "Could not find speaker or event") } - if let presentation = presentation { + if let presentation { presentation.title = input.title presentation.synopsis = input.synopsis presentation.isTBA = !(input.isAnnounced == "on") @@ -103,6 +105,7 @@ struct PresentationRouteController: RouteCollection { } // MARK: - PresentationContext + private struct PresentationContext: Content { let presentation: Presentation? let speakers: [Speaker] @@ -111,6 +114,7 @@ struct PresentationRouteController: RouteCollection { } // MARK: - FormInput + private struct FormInput: Content { let speakerID: String let secondSpeakerID: String? diff --git a/Sources/App/Features/Presentations/Models/Presentation.swift b/Sources/App/Features/Presentations/Models/Presentation.swift index e9b2075f..ca62e137 100644 --- a/Sources/App/Features/Presentations/Models/Presentation.swift +++ b/Sources/App/Features/Presentations/Models/Presentation.swift @@ -26,7 +26,7 @@ final class Presentation: Model, Content, @unchecked Sendable { var secondSpeaker: Speaker? @Parent(key: "event_id") - public var event: Event + var event: Event @Field(key: "is_tba") var isTBA: Bool @@ -52,6 +52,8 @@ final class Presentation: Model, Content, @unchecked Sendable { } enum VideoVisibility: String, Codable { - case unlisted, shared, attendee + case unlisted + case shared + case attendee } } diff --git a/Sources/App/Features/Presentations/Transformers/PresentationTransformer.swift b/Sources/App/Features/Presentations/Transformers/PresentationTransformer.swift index bea577b1..f6396dc9 100644 --- a/Sources/App/Features/Presentations/Transformers/PresentationTransformer.swift +++ b/Sources/App/Features/Presentations/Transformers/PresentationTransformer.swift @@ -2,7 +2,7 @@ import Foundation enum PresentationTransformer: Transformer { static func transform(_ entity: Presentation?) -> PresentationResponse? { - guard let entity = entity, let id = entity.id else { + guard let entity, let id = entity.id else { return nil } @@ -25,7 +25,7 @@ enum PresentationTransformer: Transformer { id: id, title: entity.title, synopsis: entity.synopsis, - speakers: [speaker, secondSpeaker].compactMap { $0 }, + speakers: [speaker, secondSpeaker].compactMap(\.self), slidoURL: entity.slidoURL, videoURL: entity.videoVisibility == .shared ? entity.videoURL : nil ) diff --git a/Sources/App/Features/Push/Controllers/PushController.swift b/Sources/App/Features/Push/Controllers/PushController.swift index c07b5199..dc0df7c9 100644 --- a/Sources/App/Features/Push/Controllers/PushController.swift +++ b/Sources/App/Features/Push/Controllers/PushController.swift @@ -1,7 +1,7 @@ import APNSCore -import VaporAPNS import Fluent import Vapor +import VaporAPNS struct PushController: RouteCollection { func boot(routes: any RoutesBuilder) throws { diff --git a/Sources/App/Features/Schedule/Transformer/ScheduleTransformer+V2.swift b/Sources/App/Features/Schedule/Transformer/ScheduleTransformer+V2.swift index 8c1617db..06fc9aab 100644 --- a/Sources/App/Features/Schedule/Transformer/ScheduleTransformer+V2.swift +++ b/Sources/App/Features/Schedule/Transformer/ScheduleTransformer+V2.swift @@ -3,7 +3,9 @@ import Foundation // A unique transformer as it doesn't actually represent an entity enum ScheduleTransformerV2 { static func transform(event: Event, events: [Event], slots: [Slot]) -> ScheduleResponseV2? { - guard let eventResponse = EventTransformer.transform(event) else { return nil } + guard let eventResponse = EventTransformer.transform(event) else { + return nil + } let eventsResponse = events .sorted(by: { $0.date < $1.date }) @@ -27,7 +29,8 @@ enum ScheduleTransformerV2 { return $0.startTime < $1.startTime } - )) + ) + ) } .filter { !$0.slots.isEmpty } .sorted(by: { $0.date < $1.date }) diff --git a/Sources/App/Features/Schedule/Transformer/ScheduleTransformer.swift b/Sources/App/Features/Schedule/Transformer/ScheduleTransformer.swift index 88f6d90f..07569ea9 100644 --- a/Sources/App/Features/Schedule/Transformer/ScheduleTransformer.swift +++ b/Sources/App/Features/Schedule/Transformer/ScheduleTransformer.swift @@ -3,7 +3,9 @@ import Foundation // A unique transformer as it doesn't actually represent an entity enum ScheduleTransformer { static func transform(event: Event, events: [Event], slots: [Slot]) -> ScheduleResponse? { - guard let eventResponse = EventTransformer.transform(event) else { return nil } + guard let eventResponse = EventTransformer.transform(event) else { + return nil + } let eventsResponse = events .filter { $0.date.timeIntervalSince1970 > 1420074000 } // 2015 date is used to hide unannounced events diff --git a/Sources/App/Features/Schema.swift b/Sources/App/Features/Schema.swift index 29f4ddc1..45a599b9 100644 --- a/Sources/App/Features/Schema.swift +++ b/Sources/App/Features/Schema.swift @@ -1,6 +1,6 @@ enum Schema { static let activity = "activity" - static let dropInSessions = "dropin_sessions" + static let dropInSessions = "dropin_sessions" static let dropInSessionSlots = "dropin_session_slots" static let event = "events" static let eventDay = "event_days" diff --git a/Sources/App/Features/Sessionize/Controllers/SessionizeSyncRouteController.swift b/Sources/App/Features/Sessionize/Controllers/SessionizeSyncRouteController.swift index 7fe542f0..965f8418 100644 --- a/Sources/App/Features/Sessionize/Controllers/SessionizeSyncRouteController.swift +++ b/Sources/App/Features/Sessionize/Controllers/SessionizeSyncRouteController.swift @@ -7,9 +7,13 @@ struct SessionizeSyncRouteController: RouteCollection { } @Sendable private func modal(request: Request) async throws -> View { - guard let event = try await Event.find(request.parameters.get("id"), on: request.db) else { throw Abort(.notFound) } + guard let event = try await Event.find(request.parameters.get("id"), on: request.db) else { + throw Abort(.notFound) + } - guard let key = event.sessionizeKey else { throw Abort(.badRequest, reason: "No Sessionize Key for Event") } + guard let key = event.sessionizeKey else { + throw Abort(.badRequest, reason: "No Sessionize Key for Event") + } let sessionizeResponse = try await request.client.get("https://sessionize.com/api/v2/\(key)/view/All") let sessionize = try sessionizeResponse.content.decode(SessionizeResponse.self) let changes = try await identifyChanges(sessionize: sessionize, request: request, commit: [], event: event) @@ -19,12 +23,16 @@ struct SessionizeSyncRouteController: RouteCollection { } @Sendable private func commit(request: Request) async throws -> Response { - guard let event = try await Event.find(request.parameters.get("id"), on: request.db) else { throw Abort(.notFound) } + guard let event = try await Event.find(request.parameters.get("id"), on: request.db) else { + throw Abort(.notFound) + } let input = try request.content.decode(FormInput.self) - let commitIds = zip(input.ids, input.statuses).filter { $0.1 == "enabled" }.map { $0.0 } + let commitIds = zip(input.ids, input.statuses).filter { $0.1 == "enabled" }.map(\.0) - guard let key = event.sessionizeKey else { throw Abort(.badRequest, reason: "No Sessionize Key for Event") } + guard let key = event.sessionizeKey else { + throw Abort(.badRequest, reason: "No Sessionize Key for Event") + } let sessionizeResponse = try await request.client.get("https://sessionize.com/api/v2/\(key)/view/All") let sessionize = try sessionizeResponse.content.decode(SessionizeResponse.self) _ = try await identifyChanges(sessionize: sessionize, request: request, commit: commitIds, event: event) @@ -40,10 +48,10 @@ struct SessionizeSyncRouteController: RouteCollection { let speakerQuery = try await Speaker .query(on: request.db) - .group(.or, { $0 + .group(.or) { $0 .filter(\.$name, .equal, sessionizeSpeaker.fullName) .filter(\.$id, .equal, UUID(uuidString: sessionizeSpeaker.id)!) - }) + } .first() let twitter = sessionizeSpeaker.links.first(where: { $0.linkType == "Twitter" })?.url @@ -188,7 +196,7 @@ struct SessionizeSyncRouteController: RouteCollection { let speakers = sessionize.speakers .filter { sessionizeSession.speakers.contains($0.id) } - .map { $0.fullName } + .map(\.fullName) changes.append(.init( id: sessionizeSession.id, @@ -197,7 +205,7 @@ struct SessionizeSyncRouteController: RouteCollection { pairs: [ .init(key: "Title", oldValue: nil, newValue: sessionizeSession.title), .init(key: "Description", oldValue: nil, newValue: sessionizeSession.description), - .init(key: "Speakers", oldValue: nil, newValue: speakers.joined(separator: " and ")) + .init(key: "Speakers", oldValue: nil, newValue: speakers.joined(separator: " and ")), ] )) @@ -237,11 +245,13 @@ struct SessionizeSyncRouteController: RouteCollection { struct SyncContext: Content { enum ModelType: String, Codable { - case speaker, presentation + case speaker + case presentation } enum OperationType: String, Codable { - case create, update + case create + case update } struct Pair: Codable { diff --git a/Sources/App/Features/Slots/Controllers/SlotRouteController.swift b/Sources/App/Features/Slots/Controllers/SlotRouteController.swift index e607630b..e94ccbb1 100644 --- a/Sources/App/Features/Slots/Controllers/SlotRouteController.swift +++ b/Sources/App/Features/Slots/Controllers/SlotRouteController.swift @@ -36,7 +36,9 @@ struct SlotRouteController: RouteCollection { } @Sendable private func onDelete(request: Request) async throws -> Response { - guard let slot = try await Slot.find(request.parameters.get("id"), on: request.db) else { throw Abort(.notFound) } + guard let slot = try await Slot.find(request.parameters.get("id"), on: request.db) else { + throw Abort(.notFound) + } try await slot.delete(on: request.db) return Response(status: .ok, body: .init(string: "OK")) } @@ -113,12 +115,14 @@ struct SlotRouteController: RouteCollection { } // MARK: - SlotTypes + enum SlotTypes: String, CaseIterable { case presentation case activity } // MARK: - PresentationContext + private struct PresentationContext: Content { let slot: Slot? let days: [EventDay] @@ -129,6 +133,7 @@ struct SlotRouteController: RouteCollection { } // MARK: - FormInput + private struct FormInput: Content { let presentationID: String? let activityID: String? @@ -139,6 +144,7 @@ struct SlotRouteController: RouteCollection { } // MARK: - FormError + enum FormError: Error { case didNotProvideActivityOrPresentation } diff --git a/Sources/App/Features/Slots/Migrations/Slot+Migration+v3.swift b/Sources/App/Features/Slots/Migrations/Slot+Migration+v3.swift index 7d512216..00e3a764 100644 --- a/Sources/App/Features/Slots/Migrations/Slot+Migration+v3.swift +++ b/Sources/App/Features/Slots/Migrations/Slot+Migration+v3.swift @@ -1,5 +1,5 @@ -import Foundation import Fluent +import Foundation struct SlotMigrationV3: AsyncMigration { func prepare(on database: any Database) async throws { diff --git a/Sources/App/Features/Slots/Migrations/Slot+Migration+v4.swift b/Sources/App/Features/Slots/Migrations/Slot+Migration+v4.swift index 5271f0fe..3fd3090f 100644 --- a/Sources/App/Features/Slots/Migrations/Slot+Migration+v4.swift +++ b/Sources/App/Features/Slots/Migrations/Slot+Migration+v4.swift @@ -1,9 +1,8 @@ -import Foundation import Fluent +import Foundation struct SlotMigrationV4: AsyncMigration { func prepare(on database: any Database) async throws { - final class MigrationSlot: Model, @unchecked Sendable { static let schema = Schema.slot diff --git a/Sources/App/Features/Slots/Migrations/Slot+Migration+v5.swift b/Sources/App/Features/Slots/Migrations/Slot+Migration+v5.swift index add1d006..32ccd891 100644 --- a/Sources/App/Features/Slots/Migrations/Slot+Migration+v5.swift +++ b/Sources/App/Features/Slots/Migrations/Slot+Migration+v5.swift @@ -1,5 +1,5 @@ -import Foundation import Fluent +import Foundation struct SlotMigrationV5: AsyncMigration { func prepare(on database: any Database) async throws { diff --git a/Sources/App/Features/Slots/Migrations/Slot+Migration+v6.swift b/Sources/App/Features/Slots/Migrations/Slot+Migration+v6.swift index a156ceb2..887455c0 100644 --- a/Sources/App/Features/Slots/Migrations/Slot+Migration+v6.swift +++ b/Sources/App/Features/Slots/Migrations/Slot+Migration+v6.swift @@ -1,5 +1,5 @@ -import Foundation import Fluent +import Foundation struct SlotMigrationV6: AsyncMigration { func prepare(on db: any Database) async throws { @@ -14,6 +14,5 @@ struct SlotMigrationV6: AsyncMigration { .field("date", .datetime) .field("event_id", .uuid, .references(Schema.event, "id")) .update() - } } diff --git a/Sources/App/Features/Slots/Models/Slot.swift b/Sources/App/Features/Slots/Models/Slot.swift index 1d392bdc..01010998 100644 --- a/Sources/App/Features/Slots/Models/Slot.swift +++ b/Sources/App/Features/Slots/Models/Slot.swift @@ -38,7 +38,7 @@ final class Slot: Codable, Model, Content, @unchecked Sendable { } } -extension Array where Element == Slot { +extension [Slot] { var schedule: [[Slot]] { let dates = Set(compactMap { $0.day?.date.withoutTime }).sorted() var slots: [[Slot]] = [] @@ -46,7 +46,9 @@ extension Array where Element == Slot { for date in dates { slots.append( filter { - guard let slotDate = $0.day?.date else { return false } + guard let slotDate = $0.day?.date else { + return false + } return Calendar.current.isDate(slotDate, inSameDayAs: date) } ) diff --git a/Sources/App/Features/Slots/Transformers/SlotTransformer.swift b/Sources/App/Features/Slots/Transformers/SlotTransformer.swift index 2e068395..e3bca4dd 100644 --- a/Sources/App/Features/Slots/Transformers/SlotTransformer.swift +++ b/Sources/App/Features/Slots/Transformers/SlotTransformer.swift @@ -2,7 +2,7 @@ import Foundation enum SlotTransformer: Transformer { static func transform(_ entity: Slot?) -> SlotResponse? { - guard let entity = entity, let id = entity.id else { + guard let entity, let id = entity.id else { return nil } @@ -21,7 +21,7 @@ enum SlotTransformer: Transformer { activity = nil } - if activity == nil && presentation == nil { + if activity == nil, presentation == nil { return nil } diff --git a/Sources/App/Features/Speakers/Controllers/SpeakerRouteController.swift b/Sources/App/Features/Speakers/Controllers/SpeakerRouteController.swift index ee927d83..252519f1 100644 --- a/Sources/App/Features/Speakers/Controllers/SpeakerRouteController.swift +++ b/Sources/App/Features/Speakers/Controllers/SpeakerRouteController.swift @@ -22,7 +22,9 @@ struct SpeakerRouteController: RouteCollection { } @Sendable private func onDelete(request: Request) async throws -> Response { - guard let speaker = try await Speaker.find(request.parameters.get("id"), on: request.db) else { throw Abort(.notFound) } + guard let speaker = try await Speaker.find(request.parameters.get("id"), on: request.db) else { + throw Abort(.notFound) + } try await speaker.delete(on: request.db) return Response(status: .ok, body: .init(string: "OK")) } @@ -32,7 +34,9 @@ struct SpeakerRouteController: RouteCollection { } @Sendable private func onUpdate(request: Request) async throws -> Response { - guard let speaker = try await Speaker.find(request.parameters.get("id"), on: request.db) else { throw Abort(.notFound) } + guard let speaker = try await Speaker.find(request.parameters.get("id"), on: request.db) else { + throw Abort(.notFound) + } return try await update(request: request, speaker: speaker) } @@ -76,16 +80,19 @@ struct SpeakerRouteController: RouteCollection { } // MARK: - ImageUpload + struct ImageUpload: Content { var profileImage: File } // MARK: - SpeakerContext + private struct SpeakerContext: Content { let speaker: Speaker? } // MARK: - FormInput + private struct FormInput: Content { let name: String let biography: String diff --git a/Sources/App/Features/Speakers/Transformers/SpeakerTransformer.swift b/Sources/App/Features/Speakers/Transformers/SpeakerTransformer.swift index b026f2f2..7dbd47ec 100644 --- a/Sources/App/Features/Speakers/Transformers/SpeakerTransformer.swift +++ b/Sources/App/Features/Speakers/Transformers/SpeakerTransformer.swift @@ -2,16 +2,14 @@ import Foundation enum SpeakerTransformer: Transformer { static func transform(_ entity: Speaker?) -> SpeakerResponse? { - guard let entity = entity else { + guard let entity else { return nil } - let presentations: [PresentationResponse] - - if let presentationEntities = entity.$presentations.value { - presentations = presentationEntities.compactMap(PresentationTransformer.transform(_:)) + let presentations: [PresentationResponse] = if let presentationEntities = entity.$presentations.value { + presentationEntities.compactMap(PresentationTransformer.transform(_:)) } else { - presentations = [] + [] } return .init( diff --git a/Sources/App/Features/Sponsors/Controllers/SponsorAPIController.swift b/Sources/App/Features/Sponsors/Controllers/SponsorAPIController.swift index 75de9724..d748343d 100644 --- a/Sources/App/Features/Sponsors/Controllers/SponsorAPIController.swift +++ b/Sources/App/Features/Sponsors/Controllers/SponsorAPIController.swift @@ -41,7 +41,8 @@ struct SponsorAPIController: RouteCollection { private func generateETag(on request: Request, lastModified: Date) async throws -> String { if let hash = request.application.storage[SponsorsHashes.self], - hash.lastModified == lastModified { + hash.lastModified == lastModified + { return hash.eTag } @@ -63,6 +64,7 @@ struct SponsorAPIController: RouteCollection { }() // MARK: - SponsorsHashes + struct SponsorsHashes: StorageKey { typealias Value = Hash diff --git a/Sources/App/Features/Sponsors/Controllers/SponsorRouteController.swift b/Sources/App/Features/Sponsors/Controllers/SponsorRouteController.swift index daab1171..4b8d3d4d 100644 --- a/Sources/App/Features/Sponsors/Controllers/SponsorRouteController.swift +++ b/Sources/App/Features/Sponsors/Controllers/SponsorRouteController.swift @@ -22,7 +22,9 @@ struct SponsorRouteController: RouteCollection { } @Sendable private func onDelete(request: Request) async throws -> Response { - guard let sponsor = try await Sponsor.find(request.parameters.get("id"), on: request.db) else { throw Abort(.notFound) } + guard let sponsor = try await Sponsor.find(request.parameters.get("id"), on: request.db) else { + throw Abort(.notFound) + } try await sponsor.delete(on: request.db) return Response(status: .ok, body: .init(string: "OK")) } @@ -64,7 +66,7 @@ struct SponsorRouteController: RouteCollection { throw Abort(.badRequest, reason: "Failed to map sponsor level") } - if let sponsor = sponsor { + if let sponsor { sponsor.name = input.name sponsor.subtitle = input.subtitle sponsor.image = imageFilename @@ -91,6 +93,7 @@ struct SponsorRouteController: RouteCollection { } // MARK: - SponsorContext + private struct SponsorContext: Content { let sponsor: Sponsor? let sponsorLevels: [Sponsor.SponsorLevel] @@ -98,11 +101,13 @@ struct SponsorRouteController: RouteCollection { } // MARK: - ImageInput + private struct ImageInput: Content { let sponsorImage: File? } // MARK: - FormInput + private struct FormInput: Content { let name: String let subtitle: String @@ -112,5 +117,6 @@ struct SponsorRouteController: RouteCollection { } // MARK: - sponsorLevels + private let sponsorLevels: [Sponsor.SponsorLevel] = [.silver, .gold, .platinum] } diff --git a/Sources/App/Features/Sponsors/Models/Middleware/SponsorMiddleware.swift b/Sources/App/Features/Sponsors/Models/Middleware/SponsorMiddleware.swift index 20c9be9e..096434dd 100644 --- a/Sources/App/Features/Sponsors/Models/Middleware/SponsorMiddleware.swift +++ b/Sources/App/Features/Sponsors/Models/Middleware/SponsorMiddleware.swift @@ -1,5 +1,5 @@ -import Foundation import Fluent +import Foundation struct SponsorMiddleware: AsyncModelMiddleware { func create(model: Sponsor, on db: any Database, next: any AnyAsyncModelResponder) async throws { @@ -21,7 +21,9 @@ struct SponsorMiddleware: AsyncModelMiddleware { guard let lastUpdated = try await LastUpdated .query(on: db) .first() - else { throw SponsorMiddlewareError.couldNotUpdateTimestamp } + else { + throw SponsorMiddlewareError.couldNotUpdateTimestamp + } let updatedDate = Date() db.logger.info("Sponsor \(name) was updated - \(updatedDate)") diff --git a/Sources/App/Features/Sponsors/Models/Sponsor.swift b/Sources/App/Features/Sponsors/Models/Sponsor.swift index df1a5581..ae3fef83 100644 --- a/Sources/App/Features/Sponsors/Models/Sponsor.swift +++ b/Sources/App/Features/Sponsors/Models/Sponsor.swift @@ -26,7 +26,7 @@ final class Sponsor: Model, Content, @unchecked Sendable { var sponsorLevel: SponsorLevel @Parent(key: "event_id") - public var event: Event + var event: Event @Children(for: \.$sponsor) var jobs: [Job] @@ -43,6 +43,7 @@ final class Sponsor: Model, Content, @unchecked Sendable { } // MARK: - SponsorLevel + enum SponsorLevel: String, Codable { case silver case gold diff --git a/Sources/App/Features/Sponsors/Transformers/SponsorTransformer.swift b/Sources/App/Features/Sponsors/Transformers/SponsorTransformer.swift index 849751f1..745dea4b 100644 --- a/Sources/App/Features/Sponsors/Transformers/SponsorTransformer.swift +++ b/Sources/App/Features/Sponsors/Transformers/SponsorTransformer.swift @@ -3,7 +3,9 @@ import Vapor enum SponsorTransformer: Transformer { static func transform(_ sponsor: Sponsor?) -> SponsorResponse? { - guard let sponsor = sponsor else { return nil } + guard let sponsor else { + return nil + } guard let bucketName = Environment.get("S3_BUCKET_NAME") else { fatalError("Missing 'S3_BUCKET_NAME' environment variable") diff --git a/Sources/App/Features/Talks/TalkRouteController.swift b/Sources/App/Features/Talks/TalkRouteController.swift index f1eee2d1..13b31e0f 100644 --- a/Sources/App/Features/Talks/TalkRouteController.swift +++ b/Sources/App/Features/Talks/TalkRouteController.swift @@ -32,7 +32,7 @@ struct TalkRouteController: RouteCollection { videoReference: "J4s3mraKcag", imageLink: "https://live.staticflickr.com/65535/53300388151_07124551b2_c_d.jpg", resources: [ - "https://github.com/jessielinden/ICYMI-EnumsAreTheSh-t" + "https://github.com/jessielinden/ICYMI-EnumsAreTheSh-t", ] ) diff --git a/Sources/App/Features/Team/Controllers/TeamAPIController.swift b/Sources/App/Features/Team/Controllers/TeamAPIController.swift index 5c8123d7..3e7d27bd 100644 --- a/Sources/App/Features/Team/Controllers/TeamAPIController.swift +++ b/Sources/App/Features/Team/Controllers/TeamAPIController.swift @@ -5,7 +5,7 @@ struct TeamAPIController: RouteCollection { routes.get(use: getTeam) } - @Sendable func getTeam(req: Request) async throws -> TeamResponse { + @Sendable func getTeam(req _: Request) async throws -> TeamResponse { let teamMembers = [ TeamMember( name: "Adam Rush", diff --git a/Sources/App/Features/Ticket Hub/Controllers/TicketHubRouteController.swift b/Sources/App/Features/Ticket Hub/Controllers/TicketHubRouteController.swift index 8797fe41..2237f693 100644 --- a/Sources/App/Features/Ticket Hub/Controllers/TicketHubRouteController.swift +++ b/Sources/App/Features/Ticket Hub/Controllers/TicketHubRouteController.swift @@ -12,7 +12,7 @@ struct TicketHubRouteController: RouteCollection { // Refund Period let refundDays: Int = Environment.get("REFUND_PERIOD").flatMap { Int($0) } ?? 30 - let refundDeadline = Date(timeIntervalSince1970: currentEvent.date.timeIntervalSince1970 - Double((60 * 60 * 24 * refundDays))) + let refundDeadline = Date(timeIntervalSince1970: currentEvent.date.timeIntervalSince1970 - Double(60 * 60 * 24 * refundDays)) let formatter = DateFormatter() formatter.locale = .init(identifier: "en_US_POSIX") @@ -30,9 +30,8 @@ struct TicketHubRouteController: RouteCollection { .with(\.$slots) .sort(.id) .all() - .filter { $0.isPublic } + .filter(\.isPublic) - let regularDropInSessions = sessions.filter { $0.maxTicketsPerSlot == 1 }.map { convertDropInSessionToViewModel($0, slug: ticket.slug) } let groupDropInSessions = sessions.filter { $0.maxTicketsPerSlot > 1 }.map { convertDropInSessionToViewModel($0, slug: ticket.slug) } @@ -49,7 +48,9 @@ struct TicketHubRouteController: RouteCollection { .all() let videos: [TicketHubContext.VideoPresentation] = presentations.compactMap { - guard let url = $0.videoURL, url != "", $0.videoVisibility != .unlisted else { return nil } + guard let url = $0.videoURL, url != "", $0.videoVisibility != .unlisted else { + return nil + } return TicketHubContext.VideoPresentation( title: $0.title, @@ -176,7 +177,8 @@ struct TicketHubRouteController: RouteCollection { .all() if existingSlots.filter({ $0.ticket.contains(ticket.slug) }) - .contains(where: { $0.session.exclusivityKey == slot.session.exclusivityKey }) { + .contains(where: { $0.session.exclusivityKey == slot.session.exclusivityKey }) + { // they already have a slot booked with same exclusivity key let message = "You already have a session of this type booked, please cancel it first before booking another." .addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) @@ -201,7 +203,7 @@ struct TicketHubRouteController: RouteCollection { let slotsWithDay: [TicketHubContext.SessionSlot] = model.slots.grouped(by: { $0.date.dayNumberOfWeek() ?? -1 }) .sorted(by: { $0.key < $1.key }) .enumerated() - .map { (offset, result) -> [TicketHubContext.SessionSlot] in + .map { offset, result -> [TicketHubContext.SessionSlot] in result.value.map { slot in let eventDay = model.event.days.first(where: { $0.date.withoutTime == slot.date.withoutTime }) @@ -218,15 +220,15 @@ struct TicketHubRouteController: RouteCollection { ) } } - .flatMap { $0 } + .flatMap(\.self) .sorted(by: { if $0.isParticipant { // Always push slots the user is a participant of to the front (no matter what) return true - } else if !$0.isFullyBooked && $1.isFullyBooked { + } else if !$0.isFullyBooked, $1.isFullyBooked { // Push fully booked sessions to the front return true - } else if $0.isFullyBooked && !$1.isFullyBooked { + } else if $0.isFullyBooked, !$1.isFullyBooked { // Push fully booked sessions to the front return false } else { @@ -246,7 +248,7 @@ struct TicketHubRouteController: RouteCollection { companyImageUrl: model.companyImageUrl, companyLink: model.companyLink, maximumAttendance: model.maxTicketsPerSlot, - remainingSlots: slotsWithDay.map({ model.maxTicketsPerSlot - $0.participantCount }).reduce(0, +), + remainingSlots: slotsWithDay.map { model.maxTicketsPerSlot - $0.participantCount }.reduce(0, +), slots: slotsWithDay, slotsOrdered: slotsWithDay.sorted(by: { $0.date < $1.date }) ) diff --git a/Sources/App/Features/Tickets/Controllers/TicketLoginController.swift b/Sources/App/Features/Tickets/Controllers/TicketLoginController.swift index 887a3269..45d7aad6 100644 --- a/Sources/App/Features/Tickets/Controllers/TicketLoginController.swift +++ b/Sources/App/Features/Tickets/Controllers/TicketLoginController.swift @@ -1,5 +1,5 @@ -import Vapor import Fluent +import Vapor struct TicketLoginController: RouteCollection { func boot(routes: any RoutesBuilder) throws { diff --git a/Sources/App/Features/Tickets/Middleware/ValidTicketMiddleware.swift b/Sources/App/Features/Tickets/Middleware/ValidTicketMiddleware.swift index c4b123da..b8444b39 100644 --- a/Sources/App/Features/Tickets/Middleware/ValidTicketMiddleware.swift +++ b/Sources/App/Features/Tickets/Middleware/ValidTicketMiddleware.swift @@ -1,5 +1,5 @@ -import Vapor import Fluent +import Vapor struct ValidTicketMiddleware: AsyncMiddleware { func respond(to request: Request, chainingTo next: any AsyncResponder) async throws -> Response { diff --git a/Sources/App/Features/Tickets/Services/TitoService.swift b/Sources/App/Features/Tickets/Services/TitoService.swift index b7f19488..1de8b51c 100644 --- a/Sources/App/Features/Tickets/Services/TitoService.swift +++ b/Sources/App/Features/Tickets/Services/TitoService.swift @@ -16,15 +16,13 @@ struct TitoService { req.logger.warning("There is an extra page of ticket results, login will not see new tickets...") } - let ticketOpt = ticketResponse.tickets.first(where: { + return ticketResponse.tickets.first(where: { // case insensitive comparisons $0.reference.lowercased() == payload.ticket.lowercased() && - $0.email?.lowercased() == payload.email.lowercased() && - // ensure unassigned tickets do not get captured here - $0.email != nil + $0.email?.lowercased() == payload.email.lowercased() && + // ensure unassigned tickets do not get captured here + $0.email != nil }) - - return ticketOpt } func ticket(stub: String, req: Request) async throws -> TitoTicket? { @@ -66,10 +64,9 @@ struct TitoService { return [ "Authorization": "Token token=\(auth)", - "Accept": "application/json" + "Accept": "application/json", ] } - } struct TitoTicketsResponse: Decodable { diff --git a/Sources/App/Helpers/RouteCollection+DateFormatter.swift b/Sources/App/Helpers/RouteCollection+DateFormatter.swift index 297cb9da..4c98a2a3 100644 --- a/Sources/App/Helpers/RouteCollection+DateFormatter.swift +++ b/Sources/App/Helpers/RouteCollection+DateFormatter.swift @@ -1,8 +1,8 @@ import Foundation import Vapor -extension RouteCollection { - public static func formDateTimeFormatter() -> DateFormatter { +public extension RouteCollection { + static func formDateTimeFormatter() -> DateFormatter { let dateFormatter = DateFormatter() dateFormatter.locale = .init(identifier: "en_US_POSIX") dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm" @@ -10,14 +10,14 @@ extension RouteCollection { return dateFormatter } - public static func formDateFormatter() -> DateFormatter { + static func formDateFormatter() -> DateFormatter { let dateFormatter = DateFormatter() dateFormatter.locale = .init(identifier: "en_US_POSIX") dateFormatter.dateFormat = "yyyy-MM-dd" return dateFormatter } - public static func timeFormatter() -> DateFormatter { + static func timeFormatter() -> DateFormatter { let dateFormatter = DateFormatter() dateFormatter.locale = .init(identifier: "en_US_POSIX") dateFormatter.dateFormat = "HH:mm" diff --git a/Sources/App/Services/ImageService.swift b/Sources/App/Services/ImageService.swift index 11644092..a7e55bab 100644 --- a/Sources/App/Services/ImageService.swift +++ b/Sources/App/Services/ImageService.swift @@ -1,7 +1,7 @@ @preconcurrency import S3 import Vapor -final class ImageService { +enum ImageService { @discardableResult static func uploadFile(data: Data, filename: String) async throws -> S3.PutObjectOutput { guard diff --git a/Sources/App/Services/SessionizeService.swift b/Sources/App/Services/SessionizeService.swift index 24d75d76..ada3ba3f 100644 --- a/Sources/App/Services/SessionizeService.swift +++ b/Sources/App/Services/SessionizeService.swift @@ -18,7 +18,7 @@ final class SessionizeService { let api: URI = "https://sessionize.com/api/universal/event?slug=\(slug)" let headers: HTTPHeaders = [ - "X-API-KEY": sessionizeKey + "X-API-KEY": sessionizeKey, ] let formatter = DateFormatter() @@ -26,10 +26,10 @@ final class SessionizeService { formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss" let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .custom({ decoder in + decoder.dateDecodingStrategy = .custom { decoder in let value = try decoder.singleValueContainer().decode(String.self) return formatter.date(from: value) ?? .init() - }) + } let data = try await req.client.get(api, headers: headers) return try data.content.decode(SessionizeEvent.self, using: decoder) diff --git a/Sources/App/Tag/CopyrightTag.swift b/Sources/App/Tag/CopyrightTag.swift index 3cdb769f..1c08ad43 100644 --- a/Sources/App/Tag/CopyrightTag.swift +++ b/Sources/App/Tag/CopyrightTag.swift @@ -20,9 +20,11 @@ struct CopyrightTag: UnsafeUnescapedLeafTag { switch ctx.parameters.count { case 1: return .string("\(copyrightText) \(year)") + case 2: let company = ctx.parameters[1].string ?? "" return .string("\(copyrightText) \(year) \(company)") + default: return .string(copyrightText) } diff --git a/Sources/App/Tag/DateFixTag.swift b/Sources/App/Tag/DateFixTag.swift index 2a3e887c..93e57348 100644 --- a/Sources/App/Tag/DateFixTag.swift +++ b/Sources/App/Tag/DateFixTag.swift @@ -8,13 +8,14 @@ struct DateFixTag: LeafTag { formatter.locale = .init(identifier: "en_US_POSIX") formatter.timeZone = .init(identifier: "UTC") switch ctx.parameters.count { - case 1: formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss" + case 2: guard let string = ctx.parameters[1].string else { throw "Unable to convert date format to string" } formatter.dateFormat = string + default: throw "invalid parameters provided for date" } diff --git a/Sources/App/Tag/MarkdownTag.swift b/Sources/App/Tag/MarkdownTag.swift index 70f50f35..2739fa62 100644 --- a/Sources/App/Tag/MarkdownTag.swift +++ b/Sources/App/Tag/MarkdownTag.swift @@ -2,17 +2,17 @@ import SwiftMarkdown struct MarkdownTag: UnsafeUnescapedLeafTag { - public enum Error: Swift.Error { + enum Error: Swift.Error { case invalidArgument(LeafData?) } private let options: MarkdownOptions? - public init(options: MarkdownOptions? = nil) { + init(options: MarkdownOptions? = nil) { self.options = options } - public func render(_ ctx: LeafContext) throws -> LeafData { + func render(_ ctx: LeafContext) throws -> LeafData { var markdown = "" if let markdownArgument = ctx.parameters.first, !markdownArgument.isNil { @@ -22,11 +22,10 @@ struct MarkdownTag: UnsafeUnescapedLeafTag { markdown = markdownArgumentValue } - let markdownHTML: String - if let options = options { - markdownHTML = try markdownToHTML(markdown, options: options) + let markdownHTML: String = if let options { + try markdownToHTML(markdown, options: options) } else { - markdownHTML = try markdownToHTML(markdown) + try markdownToHTML(markdown) } return .string(markdownHTML) diff --git a/Sources/App/Tag/SessionEndTag.swift b/Sources/App/Tag/SessionEndTag.swift index d8395a4d..57923fea 100644 --- a/Sources/App/Tag/SessionEndTag.swift +++ b/Sources/App/Tag/SessionEndTag.swift @@ -1,5 +1,5 @@ -import Leaf import Foundation +import Leaf struct SessionEndTag: LeafTag { let formatter: DateFormatter = { @@ -13,7 +13,7 @@ struct SessionEndTag: LeafTag { func render(_ ctx: LeafContext) throws -> LeafData { guard let startString = ctx.parameters[0].string, // e.g., "09:30" - let duration = ctx.parameters[1].double, // duration in minutes + let duration = ctx.parameters[1].double, // duration in minutes let startDate = formatter.date(from: startString) else { return .string("") diff --git a/Sources/App/configure.swift b/Sources/App/configure.swift index 68708ee9..8397fe81 100644 --- a/Sources/App/configure.swift +++ b/Sources/App/configure.swift @@ -1,6 +1,6 @@ -import VaporAPNS import Leaf import Vapor +import VaporAPNS public func configure(_ app: Application) async throws { // Sessions @@ -57,6 +57,7 @@ public func configure(_ app: Application) async throws { app.get { req in req.view.render("Kotlin/home") } + case .swiftleeds: try routes(app) } @@ -76,7 +77,8 @@ public func configure(_ app: Application) async throws { extension Application { enum Conference: String { - case kotlinleeds, swiftleeds + case kotlinleeds + case swiftleeds } var conference: Conference { diff --git a/Sources/App/entrypoint.swift b/Sources/App/entrypoint.swift index 6cefd4d7..10213357 100644 --- a/Sources/App/entrypoint.swift +++ b/Sources/App/entrypoint.swift @@ -1,7 +1,7 @@ -import Vapor import Logging import NIOCore import NIOPosix +import Vapor @main enum Entrypoint { diff --git a/Sources/App/routes.swift b/Sources/App/routes.swift index ac725294..495149b5 100644 --- a/Sources/App/routes.swift +++ b/Sources/App/routes.swift @@ -2,6 +2,7 @@ import Vapor func routes(_ app: Application) throws { // MARK: - Web Routes + try app.routes.register(collection: HomeRouteController()) #if DEBUG @@ -24,12 +25,12 @@ func routes(_ app: Application) throws { return try await req.view.render("Secondary/conduct", HomeContext()) } - app.get("robots.txt") { req -> String in + app.get("robots.txt") { _ -> String in let disallowedPaths = [ "/purchase", // Not intended for direct access, only redirect from tito "/admin", // Not intended for normal users "/api/", // Not intended for SEO - "/login" // Not intended for normal users (only used for admin) + "/login", // Not intended for normal users (only used for admin) ] .map { "Disallow: " + $0 } .joined(separator: "\n") @@ -60,6 +61,7 @@ func routes(_ app: Application) throws { try apiV2Routes.grouped("team").register(collection: TeamAPIController()) // MARK: - Admin Routes + let adminRoutes = app.grouped("admin").grouped(AdminMiddleware()) try adminRoutes.grouped("sponsors").register(collection: SponsorRouteController()) try adminRoutes.grouped("speakers").register(collection: SpeakerRouteController()) @@ -93,7 +95,9 @@ func routes(_ app: Application) throws { .with(\.$activity) .all() .sorted { - guard let d1 = $0.day?.date, let d2 = $1.day?.date else { return false } + guard let d1 = $0.day?.date, let d2 = $1.day?.date else { + return false + } return d1 < d2 } let activities = try await Activity diff --git a/Tests/AppTests/AppTests.swift b/Tests/AppTests/AppTests.swift index ce6eeb4d..aa0faf6b 100644 --- a/Tests/AppTests/AppTests.swift +++ b/Tests/AppTests/AppTests.swift @@ -1,10 +1,10 @@ @testable import App -import VaporTesting import Testing +import VaporTesting @Suite("Application Tests") struct AppTests { - private func withApp(_ test: (Application) async throws -> ()) async throws { + private func withApp(_ test: (Application) async throws -> Void) async throws { let app = try await Application.make(.testing) do {