Skip to content

Commit

Permalink
feat: Add support of Editable Shared Link
Browse files Browse the repository at this point in the history
  • Loading branch information
arjankowski committed Jun 22, 2022
1 parent 8ad475c commit 9b8cf90
Show file tree
Hide file tree
Showing 15 changed files with 298 additions and 15 deletions.
4 changes: 4 additions & 0 deletions BoxSDK.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
05610A9927107079009F92CC /* SignRequestCreateParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05610A9827107079009F92CC /* SignRequestCreateParameters.swift */; };
05610A9B271099A7009F92CC /* SignRequestPrefillTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05610A9A271099A7009F92CC /* SignRequestPrefillTag.swift */; };
05610A9D27109BFB009F92CC /* SignRequestSignerInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05610A9C27109BFB009F92CC /* SignRequestSignerInput.swift */; };
056B628B2860C16F008E9418 /* SharedLinkDataSpecs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 056B628A2860C16F008E9418 /* SharedLinkDataSpecs.swift */; };
0579F6F727577DAD00473A3C /* Nimble.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 05EFAB1F26F0F01100DF1830 /* Nimble.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
0579F6F827577DAD00473A3C /* Quick.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 05EFAB2026F0F01100DF1830 /* Quick.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
0579F70027577FD200473A3C /* FolderModuleIntegrationSpecs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0579F6FF27577FD200473A3C /* FolderModuleIntegrationSpecs.swift */; };
Expand Down Expand Up @@ -632,6 +633,7 @@
05610A9827107079009F92CC /* SignRequestCreateParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignRequestCreateParameters.swift; sourceTree = "<group>"; };
05610A9A271099A7009F92CC /* SignRequestPrefillTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignRequestPrefillTag.swift; sourceTree = "<group>"; };
05610A9C27109BFB009F92CC /* SignRequestSignerInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignRequestSignerInput.swift; sourceTree = "<group>"; };
056B628A2860C16F008E9418 /* SharedLinkDataSpecs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedLinkDataSpecs.swift; sourceTree = "<group>"; };
056FE12726EF6F6800098F00 /* Quick.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Quick.xcframework; path = Carthage/Build/Quick.xcframework; sourceTree = "<group>"; };
056FE12826EF6F6800098F00 /* OHHTTPStubs.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = OHHTTPStubs.xcframework; path = Carthage/Build/OHHTTPStubs.xcframework; sourceTree = "<group>"; };
056FE12926EF6F6800098F00 /* Nimble.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Nimble.xcframework; path = Carthage/Build/Nimble.xcframework; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1241,6 +1243,7 @@
isa = PBXGroup;
children = (
05EE8150271869B4006A2329 /* SignRequestCreateRequestSpecs.swift */,
056B628A2860C16F008E9418 /* SharedLinkDataSpecs.swift */,
);
path = BodyData;
sourceTree = "<group>";
Expand Down Expand Up @@ -3016,6 +3019,7 @@
05EE814827186336006A2329 /* SignRequestSignerRoleSpecs.swift in Sources */,
97F04DA122B84E540034B9A3 /* UserSpecs.swift in Sources */,
05A58A4726FB487500AB309C /* EventTypeSpecs.swift in Sources */,
056B628B2860C16F008E9418 /* SharedLinkDataSpecs.swift in Sources */,
05F59F0626FC740100D9A539 /* FileRepresentationSpecs.swift in Sources */,
05F59F0E26FC984900D9A539 /* AuthenticationSessionSpecs.swift in Sources */,
F929D63C2331B06B0039E452 /* LegalHoldsModuleSpecs.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,14 +157,18 @@ class FolderModuleAsyncIntegrationTests: BaseAsyncIntegrationTests {

XCTAssertEqual(createdSharedLink.access, .open)
XCTAssertEqual(createdSharedLink.permissions?.canDownload, true)
XCTAssertEqual(createdSharedLink.permissions?.canPreview, true)
XCTAssertEqual(createdSharedLink.permissions?.canEdit, false)
XCTAssertEqual(createdSharedLink.isPasswordEnabled, true)
XCTAssertEqual(createdSharedLink.vanityName, "iOS-SDK-Folder-VanityName")

// get
let fetchedSharedLink = try await client.folders.getSharedLink(forFolder: folder.id)

XCTAssertEqual(fetchedSharedLink.access, .open)
XCTAssertEqual(fetchedSharedLink.permissions?.canDownload, true)
XCTAssertEqual(createdSharedLink.permissions?.canDownload, true)
XCTAssertEqual(createdSharedLink.permissions?.canPreview, true)
XCTAssertEqual(createdSharedLink.permissions?.canEdit, false)
XCTAssertEqual(fetchedSharedLink.isPasswordEnabled, true)
XCTAssertEqual(fetchedSharedLink.vanityName, "iOS-SDK-Folder-VanityName")

Expand Down
9 changes: 8 additions & 1 deletion IntegrationTests/FileModuleIntegrationSpecs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -699,12 +699,15 @@ class FileModuleIntegrationSpecs: BaseIntegrationSpecs {
self.client.files.setSharedLink(
forFile: file.id,
access: .open,
canDownload: true
canDownload: true,
canEdit: true
) { result in
switch result {
case let .success(sharedLink):
expect(sharedLink.access).to(equal(.open))
expect(sharedLink.permissions?.canDownload).to(equal(true))
expect(sharedLink.permissions?.canPreview).to(equal(true))
expect(sharedLink.permissions?.canEdit).to(equal(true))
expect(sharedLink.isPasswordEnabled).to(equal(false))
expect(sharedLink.vanityName).to(beNil())
case let .failure(error):
Expand All @@ -728,6 +731,8 @@ class FileModuleIntegrationSpecs: BaseIntegrationSpecs {
case let .success(sharedLink):
expect(sharedLink.access).to(equal(.open))
expect(sharedLink.permissions?.canDownload).to(equal(true))
expect(sharedLink.permissions?.canPreview).to(equal(true))
expect(sharedLink.permissions?.canEdit).to(equal(true))
expect(sharedLink.isPasswordEnabled).to(equal(true))
expect(sharedLink.vanityName).to(equal("iOS-SDK-File-VanityName"))
case let .failure(error):
Expand All @@ -745,6 +750,8 @@ class FileModuleIntegrationSpecs: BaseIntegrationSpecs {
case let .success(sharedLink):
expect(sharedLink.access).to(equal(.open))
expect(sharedLink.permissions?.canDownload).to(equal(true))
expect(sharedLink.permissions?.canPreview).to(equal(true))
expect(sharedLink.permissions?.canEdit).to(equal(true))
expect(sharedLink.isPasswordEnabled).to(equal(true))
expect(sharedLink.vanityName).to(equal("iOS-SDK-File-VanityName"))
case let .failure(error):
Expand Down
4 changes: 4 additions & 0 deletions IntegrationTests/FolderModuleIntegrationSpecs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,8 @@ class FolderModuleIntegrationSpecs: BaseIntegrationSpecs {
case let .success(sharedLink):
expect(sharedLink.access).to(equal(.open))
expect(sharedLink.permissions?.canDownload).to(equal(true))
expect(sharedLink.permissions?.canPreview).to(equal(true))
expect(sharedLink.permissions?.canEdit).to(equal(false))
expect(sharedLink.isPasswordEnabled).to(equal(true))
expect(sharedLink.vanityName).to(equal("iOS-SDK-Folder-VanityName"))
case let .failure(error):
Expand All @@ -428,6 +430,8 @@ class FolderModuleIntegrationSpecs: BaseIntegrationSpecs {
case let .success(sharedLink):
expect(sharedLink.access).to(equal(.open))
expect(sharedLink.permissions?.canDownload).to(equal(true))
expect(sharedLink.permissions?.canPreview).to(equal(true))
expect(sharedLink.permissions?.canEdit).to(equal(false))
expect(sharedLink.isPasswordEnabled).to(equal(true))
expect(sharedLink.vanityName).to(equal("iOS-SDK-Folder-VanityName"))
case let .failure(error):
Expand Down
5 changes: 4 additions & 1 deletion Sources/Modules/FilesModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1257,6 +1257,7 @@ public class FilesModule {
/// - access: The level of access. If you omit this field then the access level will be set to the default access level specified by the enterprise admin
/// - password: The password required to access the shared link. Set to .empty to delete the password
/// - canDownload: Whether the shared link allows downloads
/// - canEdit: Whether the shared link allows editing
/// - completion: Returns a standard SharedLink object or an error
public func setSharedLink(
forFile fileId: String,
Expand All @@ -1265,6 +1266,7 @@ public class FilesModule {
access: SharedLinkAccess? = nil,
password: NullableParameter<String>? = nil,
canDownload: Bool? = nil,
canEdit: Bool? = nil,
completion: @escaping Callback<SharedLink>
) {
update(
Expand All @@ -1274,7 +1276,8 @@ public class FilesModule {
password: password,
unsharedAt: unsharedAt,
vanityName: vanityName,
canDownload: canDownload
canDownload: canDownload,
canEdit: canEdit
)),
fields: ["shared_link"]
) { result in
Expand Down
2 changes: 1 addition & 1 deletion Sources/Modules/FoldersModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ public class FoldersModule {
case .null:
body["shared_link"] = NSNull()
case let .value(sharedLinkValue):
body["shared_link"] = sharedLinkValue.bodyDict
body["shared_link"] = sharedLinkValue.copyWithoutPermissions(["can_edit"]).bodyDict
}
}

Expand Down
35 changes: 30 additions & 5 deletions Sources/Requests/BodyData/SharedLinkData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,48 @@ public struct SharedLinkData: Encodable {
/// - vanityName: The custom vanity name to use in the shared link URL.
/// It should be between 12 and 30 characters. This field can contains only letters, numbers, and hyphens.
/// - canDownload: Permission specifying whether user can download from the shared link.
/// - canEdit: Permission specifying whether user can edit from the shared link.
/// This value can only be true if `canDownload` is also true and if the item has a type of file.
public init(
access: SharedLinkAccess? = nil,
password: NullableParameter<String>? = nil,
unsharedAt: NullableParameter<Date>? = nil,
vanityName: NullableParameter<String>? = nil,
canDownload: Bool? = nil
canDownload: Bool? = nil,
canEdit: Bool? = nil
) {
self.access = access
self.password = password
self.unsharedAt = unsharedAt
self.vanityName = vanityName

if let canDownload = canDownload {
permissions = ["can_download": canDownload]
var permissions = [String: Bool]()
permissions["can_download"] = canDownload
permissions["can_edit"] = canEdit
self.permissions = permissions.isEmpty ? nil : permissions
}

/// Create a copy of current SharedLinkData object with excluded passed permissions
/// - Parameters:
/// - permissions: Array of permissions that should be excluded during copying
/// - Returns: Returns copied SharedLinkData object.
internal func copyWithoutPermissions(_ permissions: [String]?) -> SharedLinkData {
guard let permissions = permissions, let sourcePermissions = self.permissions else {
return self
}
else {
permissions = nil

var destinationPermissions = sourcePermissions
for item in permissions {
destinationPermissions[item] = nil
}

return SharedLinkData(
access: access,
password: password,
unsharedAt: unsharedAt,
vanityName: vanityName,
canDownload: destinationPermissions.first { $0.key == "can_download" }?.value,
canEdit: destinationPermissions.first { $0.key == "can_edit" }?.value
)
}
}
3 changes: 3 additions & 0 deletions Sources/Requests/QueryParameters/NullableParameter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ extension NullableParameter: Encodable where T: Encodable {
}
}
}

/// Extension for Equatable conformance
extension NullableParameter: Equatable where T: Equatable {}
2 changes: 2 additions & 0 deletions Sources/Responses/SharedLink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ public class SharedLink: BoxModel {
public let canPreview: Bool?
/// Whether the shared link allows downloads. For shared links on folders, this also applies to any items in the folder.
public let canDownload: Bool?
/// Whether the shared link allows editing of files.
public let canEdit: Bool?
}

// MARK: - BoxModel
Expand Down
69 changes: 69 additions & 0 deletions Tests/Modules/FilesModuleSpecs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1766,6 +1766,75 @@ class FilesModuleSpecs: QuickSpec {
}

describe("setSharedLink()") {
context("updating shared link with setting canDownload and canEdit to true") {
beforeEach {
stub(
condition: isHost("api.box.com") &&
isPath("/2.0/files/5000948880") &&
isMethodPUT() &&
containsQueryParams(["fields": "shared_link"]) &&
hasJsonBody(["shared_link": ["access": "open", "permissions": ["can_download": true, "can_edit": true]]])
) { _ in
OHHTTPStubsResponse(
fileAtPath: OHPathForFile("GetFileSharedLink.json", type(of: self))!,
statusCode: 200, headers: ["Content-Type": "application/json"]
)
}
}
it("should update a shared link on a file", closure: {
waitUntil(timeout: .seconds(10)) { done in
self.sut.files.setSharedLink(forFile: "5000948880", access: .open, canDownload: true, canEdit: true) { result in
switch result {
case let .success(sharedLink):
expect(sharedLink.access).to(equal(.open))
expect(sharedLink.previewCount).to(equal(0))
expect(sharedLink.downloadCount).to(equal(0))
expect(sharedLink.downloadURL).toNot(beNil())
expect(sharedLink.permissions?.canDownload).to(equal(true))
expect(sharedLink.permissions?.canEdit).to(equal(true))
case let .failure(error):
fail("Expected call to setSharedLink to suceeded, but instead got \(error)")
}
done()
}
}
})
}

context("updating shared link with setting canDownload to true and canEdit to false") {
beforeEach {
stub(
condition: isHost("api.box.com") &&
isPath("/2.0/files/5000948880") &&
isMethodPUT() &&
containsQueryParams(["fields": "shared_link"]) &&
hasJsonBody(["shared_link": ["access": "open", "permissions": ["can_download": true, "can_edit": false]]])
) { _ in
OHHTTPStubsResponse(
fileAtPath: OHPathForFile("GetFileSharedLink.json", type(of: self))!,
statusCode: 200, headers: ["Content-Type": "application/json"]
)
}
}
it("should update a shared link on a file", closure: {
waitUntil(timeout: .seconds(10)) { done in
self.sut.files.setSharedLink(forFile: "5000948880", access: .open, canDownload: true, canEdit: false) { result in
switch result {
case let .success(sharedLink):
expect(sharedLink.access).to(equal(.open))
expect(sharedLink.previewCount).to(equal(0))
expect(sharedLink.downloadCount).to(equal(0))
expect(sharedLink.downloadURL).toNot(beNil())
expect(sharedLink.permissions?.canDownload).to(equal(true))
case let .failure(error):
fail("Expected call to setSharedLink to suceeded, but instead got \(error)")
}
done()
}
}
})
}

context("updating shared link and setting password to new value") {
beforeEach {
stub(
Expand Down

0 comments on commit 9b8cf90

Please sign in to comment.