ParseServerSwift

Build Status CI codecov

Write Cloud Code in Swift!

What is Cloud Code? For complex apps, sometimes you just need logic that isn’t running on a mobile device. Cloud Code makes this possible. Cloud Code in ParseServerSwift is easy to use because it’s built using Parse-Swift and Vapor. The only difference is that this code runs in your ParseServerSwift rather than running on the user’s mobile device. When you update your Cloud Code, it becomes available to all mobile environments instantly. You don’t have to wait for a new release of your application. This lets you change app behavior on the fly and add new features faster.

Configure ParseServerSwift

To configure, you should edit ParseServerSwift/Sources/ParseServerSwift/configure.swift

WebhookKey

The webhookKey should match the webhookKey on the Parse Server. If you decide not the a webhookKey, set the value to nil in your ParseServerSwift.

Hostname, Port, and TLS

By default, the hostname is 127.0.0.1 and the port is 8081. These values can easily be changed:

app.http.server.configuration.hostname = "your.hostname.com"
app.http.server.configuration.port = 8081
app.http.server.configuration.tlsConfiguration = .none

Parse Swift SDK

Configure the SDK as described in the documentation.

// Required: Change to your Parse Server serverURL.
guard let parseServerUrl = URL(string: "http://localhost:1337/1") else {
    throw ParseError(code: .unknownError,
                     message: "Could not make Parse Server URL")
}

// Initialize the Parse-Swift SDK
ParseSwift.initialize(applicationId: "applicationId", // Required: Change to your applicationId.
                      clientKey: "clientKey", // Required: Change to your clientKey.
                      masterKey: "masterKey", // Required: Change to your masterKey.
                      serverURL: parseServerUrl) { _, completionHandler in
    completionHandler(.performDefaultHandling, nil)
}

Starting the Server

To start your server type, swift run in the terminal of the project root directory.

Adding ParseObject‘s

It is recommended to add all of your ParseObject‘s to ParseServerSwift/Sources/ParseServerSwift/Models. An example GameScore model is provided:

import Foundation
import ParseSwift

/**
 An example `ParseObject`. This is for testing. You can
 remove when creating your application.
 */
struct GameScore: ParseObject {
    // These are required by ParseObject.
    var objectId: String?
    var createdAt: Date?
    var updatedAt: Date?
    var ACL: ParseACL?
    var originalData: Data?

    // Your own properties.
    var points: Int?

    // Implement your own version of merge.
    func merge(with object: Self) throws -> Self {
        var updated = try mergeParse(with: object)
        if updated.shouldRestoreKey(\.points,
                                     original: object) {
            updated.points = object.points
        }
        return updated
    }
}

The ParseUser Model

Be sure to add all of the additional properties you have in your _User class to the User model which is located at ParseServerSwift/Sources/ParseServerSwift/Models/User.swift

Parse Cloud Code Hook Routes

Adding routes for ParseHooks are as simple as adding routes in Vapor. ParseServerSwift adds some additional methods to routes to easily create and register Hook Functions and Hook Triggers. All routes should be added to ParseServerSwift/Sources/ParseServerSwift/routes.swift

Cloud Code Functions

Cloud Code Functions can also take parameters. It’s recommended to place all paramters in ParseServerSwift/Sources/ParseServerSwift/Models/Parameters

app.post("foo",
         name: "foo") { req async throws -> ParseHookResponse<String> in
    if let error: ParseHookResponse<String> = checkHeaders(req) {
        return error
    }
    var parseRequest = try req.content
        .decode(ParseHookFunctionRequest<User, FooParameters>.self)
    
    // If a User called the request, fetch the complete user.
    if parseRequest.user != nil {
        parseRequest = try await parseRequest.hydrateUser()
    }
    
    // To query using the User's credentials who called this function,
    // use the options() method from the request
    let options = parseRequest.options()
    let scores = try await GameScore.query.findAll(options: options)
    req.logger.info("Scores this user can access: \(scores)")
    return ParseHookResponse(success: "Hello, new world!")
}

Cloud Code Triggers

// A Parse Hook Trigger route.
app.post("bar",
         className: "GameScore",
         triggerName: .beforeSave) { req async throws -> ParseHookResponse<GameScore> in
    if let error: ParseHookResponse<GameScore> = checkHeaders(req) {
        return error
    }
    var parseRequest = try req.content
        .decode(ParseHookTriggerRequest<User, GameScore>.self)

    // If a User called the request, fetch the complete user.
    if parseRequest.user != nil {
        parseRequest = try await parseRequest.hydrateUser()
    }

    guard let object = parseRequest.object else {
        return ParseHookResponse(error: .init(code: .missingObjectId,
                                              message: "Object not sent in request."))
    }
    // To query using the masterKey pass the `useMasterKey option
    // to ther query.
    let scores = try await GameScore.query.findAll(options: [.useMasterKey])
    req.logger.info("All scores: \(scores)")
    return ParseHookResponse(success: object)
}

// A Parse Hook Trigger route.
app.post("find",
         className: "GameScore",
         triggerName: .beforeFind) { req async throws -> ParseHookResponse<[GameScore]> in
    if let error: ParseHookResponse<[GameScore]> = checkHeaders(req) {
        return error
    }
    let parseRequest = try req.content
        .decode(ParseHookTriggerRequest<User, GameScore>.self)
    req.logger.info("A query is being made: \(parseRequest)")
    let score1 = GameScore(objectId: "yolo",
                           createdAt: Date(),
                           points: 50)
    let score2 = GameScore(objectId: "yolo",
                           createdAt: Date(),
                           points: 60)
    return ParseHookResponse(success: [score1, score2])
}

// A Parse Hook Trigger route.
app.post("login",
         className: "_User",
         triggerName: .afterLogin) { req async throws -> ParseHookResponse<Bool> in
    if let error: ParseHookResponse<Bool> = checkHeaders(req) {
        return error
    }
    let parseRequest = try req.content
        .decode(ParseHookTriggerRequest<User, GameScore>.self)

    req.logger.info("A user has logged in: \(parseRequest)")
    return ParseHookResponse(success: true)
}

// A Parse Hook Trigger route.
app.post("file",
         triggerName: .afterDelete) { req async throws -> ParseHookResponse<Bool> in
    if let error: ParseHookResponse<Bool> = checkHeaders(req) {
        return error
    }
    let parseRequest = try req.content
        .decode(ParseHookTriggerRequest<User, GameScore>.self)

    req.logger.info("A ParseFile is being saved: \(parseRequest)")
    return ParseHookResponse(success: true)
}

// A Parse Hook Trigger route.
app.post("connect",
         triggerName: .beforeConnect) { req async throws -> ParseHookResponse<Bool> in
    if let error: ParseHookResponse<Bool> = checkHeaders(req) {
        return error
    }
    let parseRequest = try req.content
        .decode(ParseHookTriggerRequest<User, GameScore>.self)

    req.logger.info("A LiveQuery connection is being made: \(parseRequest)")
    return ParseHookResponse(success: true)
}

// A Parse Hook Trigger route.
app.post("subscribe",
         className: "GameScore",
         triggerName: .beforeSubscribe) { req async throws -> ParseHookResponse<Bool> in
    if let error: ParseHookResponse<Bool> = checkHeaders(req) {
        return error
    }
    let parseRequest = try req.content
        .decode(ParseHookTriggerRequest<User, GameScore>.self)

    req.logger.info("A LiveQuery subscribe is being made: \(parseRequest)")
    return ParseHookResponse(success: true)
}

// A Parse Hook Trigger route.
app.post("event",
         className: "GameScore",
         triggerName: .afterEvent) { req async throws -> ParseHookResponse<Bool> in
    if let error: ParseHookResponse<Bool> = checkHeaders(req) {
        return error
    }
    let parseRequest = try req.content
        .decode(ParseHookTriggerRequest<User, GameScore>.self)

    req.logger.info("A LiveQuery event occured: \(parseRequest)")
    return ParseHookResponse(success: true)
}

GitHub

View Github