├── KsApi
├── lib
│ ├── Method.swift
│ ├── Decodable.swift
│ ├── auth
│ │ ├── OauthToken.swift
│ │ ├── ClientAuth.swift
│ │ └── BasicHTTPAuth.swift
│ ├── EncodableType.swift
│ ├── EncodableTests.swift
│ └── MimeType.swift
├── models
│ ├── templates
│ │ ├── ShippingRuleTemplates.swift
│ │ ├── ShippingRulesEnvelopeTemplates.swift
│ │ ├── Project.VideoTemplates.swift
│ │ ├── ItemTemplates.swift
│ │ ├── StarEnvelopeTemplates.swift
│ │ ├── RewardsItemTemplates.swift
│ │ ├── UpdatePledgeEnvelopeTemplates.swift
│ │ ├── SubmitApplePayTemplates.swift
│ │ ├── ChangePaymentMethodEnvelopeTemplates.swift
│ │ ├── User.MemberDataTemplates.swift
│ │ ├── CreatePledgeEnvelopeTemplates.swift
│ │ ├── FriendStatsEnvelopeTemplates.swift
│ │ ├── User.NewsletterSubscriptionsTemplates.swift
│ │ ├── CategoriesEnvelopeTemplates.swift
│ │ ├── User.AvatarTemplates.swift
│ │ ├── CommentTemplates.swift
│ │ ├── ProjectStatsEnvelope.VideoStatsTemplates.swift
│ │ ├── MessageThreadTemplates.swift
│ │ ├── ProjectNotificationTemplates.swift
│ │ ├── ProjectStatsEnvelope.CumulativeStatsTemplates.swift
│ │ ├── User.StatsTemplates.swift
│ │ ├── CommentsEnvelopeTemplates.swift
│ │ ├── Project.PhotoTemplates.swift
│ │ ├── ProjectStatsEnvelope.FundingDateStatsTemplates.swift
│ │ ├── ProjectStatsEnvelope.ReferrerStatsTemplates.swift
│ │ ├── SurveyResponseTemplates.swift
│ │ ├── FindFriendsEnvelopeTemplates.swift
│ │ ├── ProjectStatsEnvelope.RewardDistributionTemplates.swift
│ │ ├── BackingTemplates.swift
│ │ ├── User.NotificationsTemplates.swift
│ │ ├── ActivityTemplates.swift
│ │ ├── ProjectStatsEnvelopeTemplates.swift
│ │ ├── MessageTemplates.swift
│ │ ├── CheckoutEnvelopeTemplates.swift
│ │ ├── ConfigTemplates.swift
│ │ ├── UpdateDraftTemplates.swift
│ │ ├── DiscoveryEnvelopeTemplates.swift
│ │ ├── RewardTemplates.swift
│ │ ├── UpdateTemplates.swift
│ │ ├── UserTemplates.swift
│ │ ├── CategoryTemplates.swift
│ │ └── LocationTemplates.swift
│ ├── VoidEnvelope.swift
│ ├── lenses
│ │ ├── CategoriesEnvelopeLenses.swift
│ │ ├── ShippingRulesEnvelopeLenses.swift
│ │ ├── Project.VideoLenses.swift
│ │ ├── StarEnvelopeLenses.swift
│ │ ├── CheckoutEnvelopeLenses.swift
│ │ ├── SubmitApplePayEnvelopeLenses.swift
│ │ ├── ProjectNotification.ProjectLenses.swift
│ │ ├── ShippingRuleLenses.swift
│ │ ├── FriendStatsEnvelope.StatsLenses.swift
│ │ ├── User.AvatarLenses.swift
│ │ ├── SurveyResponseLenses.swift
│ │ ├── Reward.ShippingLenses.swift
│ │ ├── FriendStatsEnvelopeLenses.swift
│ │ ├── CreatePledgeEnvelopeLenses.swift
│ │ ├── RewardItemLenses.swift
│ │ ├── Project.PhotoLenses.swift
│ │ ├── ItemLenses.swift
│ │ ├── LocationLenses.swift
│ │ ├── MessageLenses.swift
│ │ ├── User.NewsletterSubscriptionsLenses.swift
│ │ ├── Project.PersonalizationLenses.swift
│ │ ├── CommentLenses.swift
│ │ ├── ProjectStatsEnvelope.RewardDistributionLenses.swift
│ │ ├── ProjectNotificationLenses.swift
│ │ ├── FindFriendsEnvelopeLenses.swift
│ │ ├── Project.MemberDataLenses.swift
│ │ ├── Project.CreatorDataLenses.swift
│ │ ├── ProjectStatsEnvelope.VideoStatsLenses.swift
│ │ ├── Project.DatesLenses.swift
│ │ ├── VideoStatsLenses.swift
│ │ ├── ProjectStatsEnvelope.CumulativeStatsLenses.swift
│ │ ├── MessageThreadLenses.swift
│ │ ├── ProjectStatsEnvelope.FundingDateStatsLenses.swift
│ │ ├── User.StatsLenses.swift
│ │ ├── CategoryLenses.swift
│ │ ├── ProjectStatsEnvelopeLenses.swift
│ │ ├── DiscoveryEnvelopeLenses.swift
│ │ ├── Project.StatsLenses.swift
│ │ ├── ProjectStatsEnvelope.ReferrerStatsLenses.swift
│ │ ├── Activity.MemberDataLenses.swift
│ │ ├── UpdateDraftLenses.swift
│ │ ├── ConfigLenses.swift
│ │ └── ActivityLenses.swift
│ ├── CategoriesEnvelope.swift
│ ├── StarEnvelope.swift
│ ├── ShippingRulesEnvelope.swift
│ ├── Project.Video.swift
│ ├── AccessTokenEnvelope.swift
│ ├── Project.VideoTests.swift
│ ├── User.AvatarTests.swift
│ ├── SurveyResponseTests.swift
│ ├── CheckoutEnvelopeTests.swift
│ ├── RewardsItem.swift
│ ├── Item.swift
│ ├── MessageThreadEnvelope.swift
│ ├── FriendStatsEnvelopeTests.swift
│ ├── ItemTests.swift
│ ├── Message.swift
│ ├── BackingTests.swift
│ ├── CheckoutEnvelope.swift
│ ├── SubmitApplePayEnvelope.swift
│ ├── UpdatePledgeEnvelope.swift
│ ├── MessageTests.swift
│ ├── ChangePaymentMethodEnvelope.swift
│ ├── FriendStatsEnvelope.swift
│ ├── CreatePledgeEnvelope.swift
│ ├── ShippingRule.swift
│ ├── RewardsItemTests.swift
│ ├── MessageThread.swift
│ ├── CreatePledgeEnvelopeTests.swift
│ ├── UpdatePledgeEnvelopeTests.swift
│ ├── ChangePaymentMethodEnvelopeTests.swift
│ ├── MessageSubject.swift
│ ├── Location.swift
│ ├── PushEnvelopeTests.swift
│ ├── CommentsEnvelope.swift
│ ├── ProjectsEnvelope.swift
│ ├── ActivityEnvelope.swift
│ ├── Comment.swift
│ ├── MessageThreadsEnvelope.swift
│ ├── ProjectActivityEnvelope.swift
│ ├── FindFriendsEnvelope.swift
│ ├── CommentTests.swift
│ ├── ProjectNotification.swift
│ ├── UpdateDraftTests.swift
│ ├── SurveyResponse.swift
│ ├── Project.CountryTests.swift
│ ├── ErrorEnvelopeTests.swift
│ ├── LocationTests.swift
│ ├── SubmitApplePayEnvelopeTests.swift
│ ├── Backing.swift
│ ├── DiscoveryEnvelope.swift
│ ├── Project.PhotoTests.swift
│ ├── User.NewsletterSubscriptionsTests.swift
│ ├── Update.swift
│ ├── UpdateTests.swift
│ ├── ConfigTests.swift
│ ├── Param.swift
│ ├── UpdateDraft.swift
│ ├── Reward.swift
│ ├── ActivityTests.swift
│ ├── FindFriendsEnvelopeTests.swift
│ ├── Config.swift
│ ├── UserTests.swift
│ ├── MessageThreadTests.swift
│ ├── Activity.swift
│ ├── RewardTests.swift
│ ├── Category.swift
│ ├── CategoryTests.swift
│ ├── PushEnvelope.swift
│ └── User.NotificationsTests.swift
├── KsApi.h
├── Info.plist
├── ServiceTests.swift
└── ServerConfig.swift
├── bin
├── bootstrap
└── test
├── README.md
├── KsApi.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── xcshareddata
│ └── xcschemes
│ ├── KsApi-TestHelpers-iOS.xcscheme
│ └── KsApi-TestHelpers-tvOS.xcscheme
├── KsApi.playground
├── contents.xcplayground
└── Contents.swift
├── .swiftlint.yml
├── .gitmodules
├── circle.yml
├── Configs
└── Secrets.swift.example
├── .gitignore
└── NOTICE
/KsApi/lib/Method.swift:
--------------------------------------------------------------------------------
1 | public enum Method: String {
2 | case GET
3 | case POST
4 | case PUT
5 | case DELETE
6 | }
7 |
--------------------------------------------------------------------------------
/bin/bootstrap:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | mkdir -p Frameworks/native-secrets/ios
4 | cp -n Configs/Secrets.swift.example Frameworks/native-secrets/ios/Secrets.swift
5 |
--------------------------------------------------------------------------------
/KsApi/models/templates/ShippingRuleTemplates.swift:
--------------------------------------------------------------------------------
1 | extension ShippingRule {
2 | public static let template = ShippingRule(cost: 5.0, id: 1, location: .template)
3 | }
4 |
--------------------------------------------------------------------------------
/KsApi/models/templates/ShippingRulesEnvelopeTemplates.swift:
--------------------------------------------------------------------------------
1 | extension ShippingRulesEnvelope {
2 | internal static let template = ShippingRulesEnvelope(shippingRules: [])
3 | }
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # KsApi
2 |
3 | > **Moved!** This library has been incorporated into [the main Kickstarter repo](https://github.com/kickstarter/ios-oss).
4 |
5 | A library for interacting with Kickstarter's API.
6 |
--------------------------------------------------------------------------------
/KsApi/models/templates/Project.VideoTemplates.swift:
--------------------------------------------------------------------------------
1 | extension Project.Video {
2 | internal static let template = Project.Video(
3 | id: 1,
4 | high: "http://www.kickstarter.com/video.mp4"
5 | )
6 | }
7 |
--------------------------------------------------------------------------------
/KsApi.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/KsApi/models/templates/ItemTemplates.swift:
--------------------------------------------------------------------------------
1 | extension Item {
2 | internal static let template = Item(
3 | description: "This is an item.",
4 | id: 1,
5 | name: "The Item",
6 | projectId: 1
7 | )
8 | }
9 |
--------------------------------------------------------------------------------
/KsApi/models/templates/StarEnvelopeTemplates.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension StarEnvelope {
4 | internal static let template = StarEnvelope(
5 | user: .template,
6 | project: .template
7 | )
8 | }
9 |
--------------------------------------------------------------------------------
/KsApi/models/templates/RewardsItemTemplates.swift:
--------------------------------------------------------------------------------
1 | extension RewardsItem {
2 | internal static let template = RewardsItem(
3 | id: 1,
4 | item: Item.template,
5 | quantity: 1,
6 | rewardId: 1
7 | )
8 | }
9 |
--------------------------------------------------------------------------------
/KsApi/models/templates/UpdatePledgeEnvelopeTemplates.swift:
--------------------------------------------------------------------------------
1 | extension UpdatePledgeEnvelope {
2 | internal static let template = UpdatePledgeEnvelope(
3 | newCheckoutUrl: "checkouts/1/payments/new",
4 | status: 200
5 | )
6 | }
7 |
--------------------------------------------------------------------------------
/KsApi/models/templates/SubmitApplePayTemplates.swift:
--------------------------------------------------------------------------------
1 | extension SubmitApplePayEnvelope {
2 | internal static let template = SubmitApplePayEnvelope(
3 | thankYouUrl: "https://www.kickstarter.com/thanks",
4 | status: 200
5 | )
6 | }
7 |
--------------------------------------------------------------------------------
/KsApi/models/templates/ChangePaymentMethodEnvelopeTemplates.swift:
--------------------------------------------------------------------------------
1 | extension ChangePaymentMethodEnvelope {
2 | internal static let template = ChangePaymentMethodEnvelope(
3 | newCheckoutUrl: "checkouts/1/payments/new",
4 | status: 200
5 | )
6 | }
7 |
--------------------------------------------------------------------------------
/KsApi/models/VoidEnvelope.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 |
3 | public struct VoidEnvelope {
4 | }
5 |
6 | extension VoidEnvelope: Decodable {
7 | public static func decode(_ json: JSON) -> Decoded {
8 | return .success(VoidEnvelope())
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/KsApi/models/templates/User.MemberDataTemplates.swift:
--------------------------------------------------------------------------------
1 | extension Project.MemberData {
2 | internal static let template = Project.MemberData(
3 | lastUpdatePublishedAt: nil,
4 | permissions: [],
5 | unreadMessagesCount: 0,
6 | unseenActivityCount: 0
7 | )
8 | }
9 |
--------------------------------------------------------------------------------
/KsApi.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/KsApi/models/templates/CreatePledgeEnvelopeTemplates.swift:
--------------------------------------------------------------------------------
1 | extension CreatePledgeEnvelope {
2 | internal static let template = CreatePledgeEnvelope(
3 | checkoutUrl: "checkouts/1/payments",
4 | newCheckoutUrl: "checkouts/1/payments/new",
5 | status: 200
6 | )
7 | }
8 |
--------------------------------------------------------------------------------
/KsApi/models/templates/FriendStatsEnvelopeTemplates.swift:
--------------------------------------------------------------------------------
1 | extension FriendStatsEnvelope {
2 | internal static let template = FriendStatsEnvelope(
3 | stats: FriendStatsEnvelope.Stats(
4 | friendProjectsCount: 100,
5 | remoteFriendsCount: 100
6 | )
7 | )
8 | }
9 |
--------------------------------------------------------------------------------
/KsApi/models/templates/User.NewsletterSubscriptionsTemplates.swift:
--------------------------------------------------------------------------------
1 | extension User.NewsletterSubscriptions {
2 | internal static let template = User.NewsletterSubscriptions(
3 | games: false,
4 | happening: false,
5 | promo: false,
6 | weekly: false
7 | )
8 | }
9 |
--------------------------------------------------------------------------------
/KsApi/models/templates/CategoriesEnvelopeTemplates.swift:
--------------------------------------------------------------------------------
1 | extension CategoriesEnvelope {
2 | internal static let template = CategoriesEnvelope(
3 | categories: [
4 | .art,
5 | .filmAndVideo,
6 | .illustration,
7 | .documentary
8 | ]
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/KsApi/models/templates/User.AvatarTemplates.swift:
--------------------------------------------------------------------------------
1 | extension User.Avatar {
2 | internal static let template = User.Avatar(
3 | large: "http://www.kickstarter.com/large.jpg",
4 | medium: "http://www.kickstarter.com/medium.jpg",
5 | small: "http://www.kickstarter.com/small.jpg"
6 | )
7 | }
8 |
--------------------------------------------------------------------------------
/KsApi/models/templates/CommentTemplates.swift:
--------------------------------------------------------------------------------
1 | extension Comment {
2 | internal static let template = Comment(
3 | author: .template,
4 | body: "Exciting!",
5 | createdAt: Date(timeIntervalSince1970: 1475361315).timeIntervalSince1970,
6 | deletedAt: nil,
7 | id: 1
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/KsApi/models/templates/ProjectStatsEnvelope.VideoStatsTemplates.swift:
--------------------------------------------------------------------------------
1 | extension ProjectStatsEnvelope.VideoStats {
2 | internal static let template = ProjectStatsEnvelope.VideoStats(
3 | externalCompletions: 5,
4 | externalStarts: 5,
5 | internalCompletions: 5,
6 | internalStarts: 5
7 | )
8 | }
9 |
--------------------------------------------------------------------------------
/KsApi/models/templates/MessageThreadTemplates.swift:
--------------------------------------------------------------------------------
1 | extension MessageThread {
2 | internal static let template = MessageThread(
3 | backing: nil,
4 | closed: false,
5 | id: 1,
6 | lastMessage: .template,
7 | participant: .template,
8 | project: .template,
9 | unreadMessagesCount: 1
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/KsApi/models/templates/ProjectNotificationTemplates.swift:
--------------------------------------------------------------------------------
1 | extension ProjectNotification {
2 | internal static let template = ProjectNotification(
3 | email: false,
4 | id: 1,
5 | mobile: false,
6 | project: ProjectNotification.Project(
7 | id: 1738,
8 | name: "The Project"
9 | )
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/KsApi/models/templates/ProjectStatsEnvelope.CumulativeStatsTemplates.swift:
--------------------------------------------------------------------------------
1 | extension ProjectStatsEnvelope.CumulativeStats {
2 | internal static let template = ProjectStatsEnvelope.CumulativeStats(
3 | averagePledge: 0,
4 | backersCount: 0,
5 | goal: 0,
6 | percentRaised: 0,
7 | pledged: 0
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/CategoriesEnvelopeLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension CategoriesEnvelope {
4 | public enum lens {
5 | public static let categories = Lens(
6 | view: { $0.categories },
7 | set: { part, _ in CategoriesEnvelope(categories: part) }
8 | )
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/KsApi/models/templates/User.StatsTemplates.swift:
--------------------------------------------------------------------------------
1 | extension User.Stats {
2 | internal static let template = User.Stats(
3 | backedProjectsCount: nil,
4 | createdProjectsCount: nil,
5 | memberProjectsCount: nil,
6 | starredProjectsCount: nil,
7 | unansweredSurveysCount: nil,
8 | unreadMessagesCount: nil
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/KsApi/models/templates/CommentsEnvelopeTemplates.swift:
--------------------------------------------------------------------------------
1 | extension CommentsEnvelope {
2 | internal static let template = CommentsEnvelope(
3 | comments: [Comment.template],
4 | urls: CommentsEnvelope.UrlsEnvelope(
5 | api: CommentsEnvelope.UrlsEnvelope.ApiEnvelope(
6 | moreComments: ""
7 | )
8 | )
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/KsApi/models/templates/Project.PhotoTemplates.swift:
--------------------------------------------------------------------------------
1 | extension Project.Photo {
2 | internal static let template = Project.Photo(
3 | full: "http://www.kickstarter.com/full.jpg",
4 | med: "http://www.kickstarter.com/med.jpg",
5 | size1024x768: "http://www.kickstarter.com/1024x768.jpg",
6 | small: "http://www.kickstarter.com/small.jpg"
7 | )
8 | }
9 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/ShippingRulesEnvelopeLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension ShippingRulesEnvelope {
4 | public enum lens {
5 | public static let shippingRules = Lens(
6 | view: { $0.shippingRules },
7 | set: { shippingRules, _ in .init(shippingRules: shippingRules) }
8 | )
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/KsApi/models/templates/ProjectStatsEnvelope.FundingDateStatsTemplates.swift:
--------------------------------------------------------------------------------
1 | extension ProjectStatsEnvelope.FundingDateStats {
2 | internal static let template =
3 | ProjectStatsEnvelope.FundingDateStats(
4 | backersCount: 0,
5 | cumulativePledged: 0,
6 | cumulativeBackersCount: 0,
7 | date: 0,
8 | pledged: 0
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/KsApi/models/templates/ProjectStatsEnvelope.ReferrerStatsTemplates.swift:
--------------------------------------------------------------------------------
1 | extension ProjectStatsEnvelope.ReferrerStats {
2 | internal static let template = ProjectStatsEnvelope.ReferrerStats(
3 | backersCount: 0,
4 | code: "",
5 | percentageOfDollars: 0.0,
6 | pledged: 0,
7 | referrerName: "",
8 | referrerType: .`internal`
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | disabled_rules:
2 | - variable_name
3 | - file_length
4 | - function_body_length
5 | - function_parameter_count
6 | - nesting
7 | - trailing_comma
8 | - type_body_length
9 | - type_name
10 | excluded:
11 | - Frameworks
12 | - KsApi.playground/
13 | line_length: 110
14 | type_body_length:
15 | warning: 300
16 | error: 400
17 | reporter: "xcode"
18 |
--------------------------------------------------------------------------------
/KsApi/lib/Decodable.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 |
3 | public extension Decodable {
4 | /**
5 | Decode a JSON dictionary into a `Decoded` type.
6 |
7 | - parameter json: A dictionary with string keys.
8 |
9 | - returns: A decoded value.
10 | */
11 | public static func decodeJSONDictionary(_ json: [String:Any]) -> Decoded {
12 | return Self.decode(JSON(json))
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/KsApi/models/CategoriesEnvelope.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct CategoriesEnvelope {
6 | public let categories: [Category]
7 | }
8 |
9 | extension CategoriesEnvelope: Decodable {
10 | public static func decode(_ json: JSON) -> Decoded {
11 | return curry(CategoriesEnvelope.init)
12 | <^> json <|| "categories"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/KsApi/models/StarEnvelope.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct StarEnvelope {
6 | public let user: User
7 | public let project: Project
8 | }
9 |
10 | extension StarEnvelope: Decodable {
11 | public static func decode(_ json: JSON) -> Decoded {
12 | return curry(StarEnvelope.init)
13 | <^> json <| "user"
14 | <*> json <| "project"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/KsApi/models/templates/SurveyResponseTemplates.swift:
--------------------------------------------------------------------------------
1 | extension SurveyResponse {
2 | internal static let template = SurveyResponse(
3 | answeredAt: nil,
4 | id: 1,
5 | project: .template,
6 | urls: SurveyResponse.UrlsEnvelope(
7 | web: SurveyResponse.UrlsEnvelope.WebEnvelope(
8 | survey: "https://www.kickstarter.com/projects/creator/project/surveys/1"
9 | )
10 | )
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/KsApi/models/ShippingRulesEnvelope.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct ShippingRulesEnvelope {
6 | public let shippingRules: [ShippingRule]
7 | }
8 |
9 | extension ShippingRulesEnvelope: Decodable {
10 | public static func decode(_ json: JSON) -> Decoded {
11 | return curry(ShippingRulesEnvelope.init)
12 | <^> json <|| "shipping_rules"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/Project.VideoLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension Project.Video {
4 | public enum lens {
5 | public static let id = Lens(
6 | view: { $0.id },
7 | set: { .init(id: $0, high: $1.high) }
8 | )
9 |
10 | public static let high = Lens(
11 | view: { $0.high },
12 | set: { .init(id: $1.id, high: $0) }
13 | )
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/KsApi/models/Project.Video.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | extension Project {
6 | public struct Video {
7 | public let id: Int
8 | public let high: String
9 | }
10 | }
11 |
12 | extension Project.Video: Decodable {
13 | public static func decode(_ json: JSON) -> Decoded {
14 | return curry(Project.Video.init)
15 | <^> json <| "id"
16 | <*> json <| "high"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/KsApi/models/AccessTokenEnvelope.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct AccessTokenEnvelope {
6 | public let accessToken: String
7 | public let user: User
8 | }
9 |
10 | extension AccessTokenEnvelope: Decodable {
11 | public static func decode(_ json: JSON) -> Decoded {
12 | return curry(AccessTokenEnvelope.init)
13 | <^> json <| "access_token"
14 | <*> json <| "user"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/StarEnvelopeLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension StarEnvelope {
4 | public enum lens {
5 | public static let user = Lens(
6 | view: { $0.user },
7 | set: { StarEnvelope(user: $0, project: $1.project) }
8 | )
9 |
10 | public static let project = Lens(
11 | view: { $0.project },
12 | set: { StarEnvelope(user: $1.user, project: $0) }
13 | )
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/KsApi/models/templates/FindFriendsEnvelopeTemplates.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension FindFriendsEnvelope {
4 | internal static let template = FindFriendsEnvelope(
5 | contactsImported: true,
6 | urls: FindFriendsEnvelope.UrlsEnvelope(
7 | api: FindFriendsEnvelope.UrlsEnvelope.ApiEnvelope(
8 | moreUsers: "http://somelink.com/more"
9 | )
10 | ),
11 | users: (1...3).map { User.template |> User.lens.id .~ $0 }
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/KsApi/models/Project.VideoTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 |
4 | final class ProjectVideoTests: XCTestCase {
5 |
6 | func testJsonParsing_WithFullData() {
7 | let video = Project.Video.decodeJSONDictionary([
8 | "id": 1,
9 | "high": "kickstarter.com/video.mp4"
10 | ])
11 |
12 | XCTAssertNil(video.error)
13 | XCTAssertEqual(video.value?.id, 1)
14 | XCTAssertEqual(video.value?.high, "kickstarter.com/video.mp4")
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/KsApi/models/User.AvatarTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 |
4 | final class UserAvatarTests: XCTestCase {
5 |
6 | func testJsonEncoding() {
7 | let json: [String:Any] = [
8 | "medium": "http://www.kickstarter.com/medium.jpg",
9 | "small": "http://www.kickstarter.com/small.jpg"
10 | ]
11 | let avatar = User.Avatar.decodeJSONDictionary(json)
12 |
13 | XCTAssertEqual(avatar.value?.encode().description, json.description)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/KsApi/models/templates/ProjectStatsEnvelope.RewardDistributionTemplates.swift:
--------------------------------------------------------------------------------
1 | extension ProjectStatsEnvelope.RewardStats {
2 | internal static let template = ProjectStatsEnvelope.RewardStats(
3 | backersCount: 50,
4 | rewardId: 400,
5 | minimum: 10,
6 | pledged: 500
7 | )
8 |
9 | internal static let unPledged = ProjectStatsEnvelope.RewardStats(
10 | backersCount: 0,
11 | rewardId: Reward.noReward.id,
12 | minimum: 1,
13 | pledged: 0
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/KsApi/models/templates/BackingTemplates.swift:
--------------------------------------------------------------------------------
1 | extension Backing {
2 | internal static let template = Backing(
3 | amount: 10,
4 | backer: .template,
5 | backerId: 1,
6 | id: 1,
7 | locationId: 1,
8 | pledgedAt: Date(timeIntervalSince1970: 1475361315).timeIntervalSince1970,
9 | projectCountry: "US",
10 | projectId: 1,
11 | reward: .template,
12 | rewardId: 1,
13 | sequence: 10,
14 | shippingAmount: 2,
15 | status: .pledged
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/CheckoutEnvelopeLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension CheckoutEnvelope {
4 | public enum lens {
5 | public static let state = Lens(
6 | view: { $0.state },
7 | set: { CheckoutEnvelope(state: $0, stateReason: $1.stateReason) }
8 | )
9 | public static let stateReason = Lens(
10 | view: { $0.stateReason },
11 | set: { CheckoutEnvelope(state: $1.state, stateReason: $0) }
12 | )
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/KsApi/models/templates/User.NotificationsTemplates.swift:
--------------------------------------------------------------------------------
1 | extension User.Notifications {
2 | internal static let template = User.Notifications(
3 | backings: false,
4 | comments: false,
5 | follower: false,
6 | friendActivity: false,
7 | mobileBackings: false,
8 | mobileComments: false,
9 | mobileFollower: false,
10 | mobileFriendActivity: false,
11 | mobilePostLikes: false,
12 | mobileUpdates: false,
13 | postLikes: false,
14 | updates: false
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/bin/test:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -o pipefail
4 |
5 | if [ $# -eq 0 ]; then
6 | echo "Please specify a target, i.e. iOS or tvOS."
7 | exit 1
8 | fi
9 |
10 | if [ "$1" == "iOS" ]; then
11 | DESTINATION='platform=iOS Simulator,name=iPhone 6,OS='
12 | else
13 | DESTINATION='platform=tvOS Simulator,name=Apple TV 1080p,OS='
14 | fi
15 |
16 | xcodebuild \
17 | -destination "$DESTINATION$2" \
18 | -scheme KsApi-$1 \
19 | clean test \
20 | | tee $CIRCLE_ARTIFACTS/xcode_raw.log \
21 | | xcpretty
22 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/SubmitApplePayEnvelopeLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension SubmitApplePayEnvelope {
4 | public enum lens {
5 | public static let thankYouUrl = Lens(
6 | view: { $0.thankYouUrl },
7 | set: { .init(thankYouUrl: $0, status: $1.status) }
8 | )
9 |
10 | public static let status = Lens(
11 | view: { $0.status },
12 | set: { .init(thankYouUrl: $1.thankYouUrl, status: $0) }
13 | )
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/KsApi/models/SurveyResponseTests.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | @testable import KsApi
3 | import XCTest
4 |
5 | final internal class SurveyResponseTests: XCTestCase {
6 | func testJSONDecoding() {
7 | let decoded = SurveyResponse.decodeJSONDictionary([
8 | "id": 1,
9 | "urls": [
10 | "web": [
11 | "survey": "http://"
12 | ]
13 | ]
14 | ])
15 |
16 | XCTAssertNil(decoded.error)
17 | XCTAssertNotNil(decoded.value)
18 | XCTAssertEqual(1, decoded.value?.id)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/KsApi/models/CheckoutEnvelopeTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 | @testable import Argo
4 |
5 | final class CheckoutEnvelopeTests: XCTestCase {
6 | func testJsonDecoding() {
7 | let json: [String:Any] = [
8 | "state": "failed",
9 | "state_reason": "Oof!"
10 | ]
11 |
12 | let envelope = CheckoutEnvelope.decodeJSONDictionary(json)
13 |
14 | XCTAssertEqual(CheckoutEnvelope.State.failed, envelope.value?.state)
15 | XCTAssertEqual("Oof!", envelope.value?.stateReason)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/KsApi/models/RewardsItem.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct RewardsItem {
6 | public let id: Int
7 | public let item: Item
8 | public let quantity: Int
9 | public let rewardId: Int
10 | }
11 |
12 | extension RewardsItem: Decodable {
13 | public static func decode(_ json: JSON) -> Decoded {
14 | return curry(RewardsItem.init)
15 | <^> json <| "id"
16 | <*> json <| "item"
17 | <*> json <| "quantity"
18 | <*> json <| "reward_id"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/ProjectNotification.ProjectLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension ProjectNotification.Project {
4 | public enum lens {
5 | public static let id = Lens (
6 | view: { $0.id },
7 | set: { ProjectNotification.Project(id: $0, name: $1.name) }
8 | )
9 |
10 | public static let name = Lens (
11 | view: { $0.name },
12 | set: { ProjectNotification.Project(id: $1.id, name: $0) }
13 | )
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/KsApi/models/Item.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct Item {
6 | public let description: String?
7 | public let id: Int
8 | public let name: String
9 | public let projectId: Int
10 | }
11 |
12 | extension Item: Decodable {
13 | public static func decode(_ json: JSON) -> Decoded- {
14 | let create = curry(Item.init)
15 | return create
16 | <^> json <|? "description"
17 | <*> json <| "id"
18 | <*> json <| "name"
19 | <*> json <| "project_id"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/KsApi/KsApi.h:
--------------------------------------------------------------------------------
1 | //
2 | // KsApi.h
3 | // KsApi
4 | //
5 | // Created by Brandon Williams on 9/23/15.
6 | // Copyright © 2015 Kickstarter. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for KsApi.
12 | FOUNDATION_EXPORT double KsApiVersionNumber;
13 |
14 | //! Project version string for KsApi.
15 | FOUNDATION_EXPORT const unsigned char KsApiVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/KsApi/models/MessageThreadEnvelope.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct MessageThreadEnvelope {
6 | public let participants: [User]
7 | public let messages: [Message]
8 | public let messageThread: MessageThread
9 | }
10 |
11 | extension MessageThreadEnvelope: Decodable {
12 | public static func decode(_ json: JSON) -> Decoded {
13 | return curry(MessageThreadEnvelope.init)
14 | <^> json <|| "participants"
15 | <*> json <|| "messages"
16 | <*> json <| "message_thread"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/KsApi/models/templates/ActivityTemplates.swift:
--------------------------------------------------------------------------------
1 | extension Activity {
2 | internal static let template = Activity(
3 | category: .launch,
4 | comment: nil,
5 | createdAt: Date(timeIntervalSince1970: 1475361315).timeIntervalSince1970,
6 | id: 1,
7 | memberData: Activity.MemberData(
8 | amount: nil,
9 | backing: nil,
10 | oldAmount: nil,
11 | oldRewardId: nil,
12 | newAmount: nil,
13 | newRewardId: nil,
14 | rewardId: nil
15 | ),
16 | project: .template,
17 | update: nil,
18 | user: .template
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/KsApi/models/templates/ProjectStatsEnvelopeTemplates.swift:
--------------------------------------------------------------------------------
1 | extension ProjectStatsEnvelope {
2 | internal static let template = ProjectStatsEnvelope(
3 | // using `.template` causes a segfault in release builds
4 | cumulativeStats: ProjectStatsEnvelope.CumulativeStats(
5 | averagePledge: 0,
6 | backersCount: 0,
7 | goal: 0,
8 | percentRaised: 0,
9 | pledged: 0
10 | ),
11 | fundingDistribution: [.template],
12 | referralDistribution: [.template],
13 | rewardDistribution: [.template],
14 | videoStats: .template
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/KsApi/models/FriendStatsEnvelopeTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 | @testable import Argo
4 |
5 | final class FriendStatsEnvelopeTests: XCTestCase {
6 | func testJsonDecoding() {
7 | let json: [String:Any] = [
8 | "stats": [
9 | "remote_friends_count": 202,
10 | "friend_projects_count": 1132
11 | ]
12 | ]
13 |
14 | let stats = FriendStatsEnvelope.decodeJSONDictionary(json)
15 |
16 | XCTAssertEqual(202, stats.value?.stats.remoteFriendsCount)
17 | XCTAssertEqual(1132, stats.value?.stats.friendProjectsCount)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/KsApi/models/ItemTests.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 | import XCTest
3 | @testable import KsApi
4 |
5 | final class ItemTests: XCTestCase {
6 |
7 | func testDecoding() {
8 | let decoded = Item.decodeJSONDictionary([
9 | "description": "Hello",
10 | "id": 1,
11 | "name": "The thing",
12 | "project_id": 1
13 | ])
14 |
15 | XCTAssertNil(decoded.error)
16 | XCTAssertEqual("Hello", decoded.value?.description)
17 | XCTAssertEqual(1, decoded.value?.id)
18 | XCTAssertEqual("The thing", decoded.value?.name)
19 | XCTAssertEqual(1, decoded.value?.projectId)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/KsApi/models/Message.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 | import Foundation
5 |
6 | public struct Message {
7 | public let body: String
8 | public let createdAt: TimeInterval
9 | public let id: Int
10 | public let recipient: User
11 | public let sender: User
12 | }
13 |
14 | extension Message: Decodable {
15 | public static func decode(_ json: JSON) -> Decoded {
16 | let create = curry(Message.init)
17 | return create
18 | <^> json <| "body"
19 | <*> json <| "created_at"
20 | <*> json <| "id"
21 | <*> json <| "recipient"
22 | <*> json <| "sender"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "Frameworks/ReactiveExtensions"]
2 | path = Frameworks/ReactiveExtensions
3 | url = https://github.com/kickstarter/Kickstarter-ReactiveExtensions.git
4 | [submodule "Frameworks/Argo"]
5 | path = Frameworks/Argo
6 | url = git://github.com/thoughtbot/Argo.git
7 | [submodule "Frameworks/Curry"]
8 | path = Frameworks/Curry
9 | url = https://github.com/thoughtbot/Curry.git
10 | [submodule "Frameworks/Prelude"]
11 | path = Frameworks/Prelude
12 | url = https://github.com/kickstarter/Kickstarter-Prelude.git
13 | [submodule "Frameworks/Runes"]
14 | path = Frameworks/Runes
15 | url = https://github.com/thoughtbot/Runes.git
16 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/ShippingRuleLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension ShippingRule {
4 | public enum lens {
5 | public static let cost = Lens(
6 | view: { $0.cost },
7 | set: { .init(cost: $0, id: $1.id, location: $1.location) }
8 | )
9 |
10 | public static let id = Lens(
11 | view: { $0.id },
12 | set: { .init(cost: $1.cost, id: $0, location: $1.location) }
13 | )
14 |
15 | public static let location = Lens(
16 | view: { $0.location },
17 | set: { .init(cost: $1.cost, id: $1.id, location: $0) }
18 | )
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/KsApi/models/BackingTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 |
4 | final class BackingTests: XCTestCase {
5 |
6 | func testJSONDecoding_WithCompleteData() {
7 | let backing = Backing.decodeJSONDictionary([
8 | "amount": 1.0,
9 | "backer_id": 1,
10 | "id": 1,
11 | "location_id": 1,
12 | "pledged_at": 1000,
13 | "project_country": "US",
14 | "project_id": 1,
15 | "sequence": 1,
16 | "status": "pledged"
17 | ])
18 |
19 | XCTAssertNil(backing.error)
20 | XCTAssertEqual(1, backing.value?.id)
21 | XCTAssertEqual(Backing.Status.pledged, backing.value?.status)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/FriendStatsEnvelope.StatsLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension FriendStatsEnvelope.Stats {
4 | public enum lens {
5 | public static let friendProjectsCount = Lens(
6 | view: { $0.friendProjectsCount },
7 | set: { FriendStatsEnvelope.Stats(friendProjectsCount: $0, remoteFriendsCount: $1.remoteFriendsCount) }
8 | )
9 |
10 | public static let remoteFriendsCount = Lens(
11 | view: { $0.remoteFriendsCount },
12 | set: { FriendStatsEnvelope.Stats(friendProjectsCount: $1.friendProjectsCount, remoteFriendsCount: $0) }
13 | )
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/KsApi/models/templates/MessageTemplates.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension Message {
4 | internal static let template = Message(
5 | body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam augue dolor, " +
6 | "accumsan nec aliquam a, porttitor sed dui. Integer iaculis ipsum fringilla metus " +
7 | "porttitor euismod. Donec in libero vitae lectus ultrices vehicula id eget dolor. " +
8 | "Nulla lacinia erat a ullamcorper sollicitudin.",
9 | createdAt: Date(timeIntervalSince1970: 1475361315).timeIntervalSince1970,
10 | id: 1,
11 | recipient: .template,
12 | sender: .template |> User.lens.id %~ { $0 + 1 }
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/User.AvatarLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension User.Avatar {
4 | public enum lens {
5 | public static let large = Lens(
6 | view: { $0.large },
7 | set: { User.Avatar(large: $0, medium: $1.medium, small: $1.small) }
8 | )
9 |
10 | public static let medium = Lens(
11 | view: { $0.medium },
12 | set: { User.Avatar(large: $1.large, medium: $0, small: $1.small) }
13 | )
14 |
15 | public static let small = Lens(
16 | view: { $0.small },
17 | set: { User.Avatar(large: $1.large, medium: $1.medium, small: $0) }
18 | )
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/KsApi/models/CheckoutEnvelope.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct CheckoutEnvelope {
6 | public enum State: String {
7 | case authorizing
8 | case failed
9 | case successful
10 | case verifying
11 | }
12 | public let state: State
13 | public let stateReason: String
14 | }
15 |
16 | extension CheckoutEnvelope: Decodable {
17 | public static func decode(_ json: JSON) -> Decoded {
18 | let create = curry(CheckoutEnvelope.init)
19 | return create
20 | <^> json <| "state"
21 | <*> (json <| "state_reason" <|> .success(""))
22 | }
23 | }
24 |
25 | extension CheckoutEnvelope.State: Decodable {
26 | }
27 |
--------------------------------------------------------------------------------
/KsApi/models/templates/CheckoutEnvelopeTemplates.swift:
--------------------------------------------------------------------------------
1 | extension CheckoutEnvelope {
2 | internal static let template = CheckoutEnvelope(
3 | state: .authorizing,
4 | stateReason: ""
5 | )
6 |
7 | internal static let authorizing = template
8 |
9 | internal static let failed = CheckoutEnvelope(
10 | state: .failed,
11 | stateReason: "Sorry, something went wrong."
12 | )
13 |
14 | internal static let successful = CheckoutEnvelope(
15 | state: .successful,
16 | stateReason: ""
17 | )
18 |
19 | internal static let verifying = CheckoutEnvelope(
20 | state: .verifying,
21 | stateReason: "Blob, your payment method change is being processed."
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/KsApi/lib/auth/OauthToken.swift:
--------------------------------------------------------------------------------
1 | /**
2 | A type that can provide oauth token authentication, i.e. a user's personal token.
3 | */
4 | public protocol OauthTokenAuthType {
5 | var token: String { get }
6 | }
7 |
8 | public func == (lhs: OauthTokenAuthType, rhs: OauthTokenAuthType) -> Bool {
9 | return type(of: lhs) == type(of: rhs) &&
10 | lhs.token == rhs.token
11 | }
12 |
13 | public func == (lhs: OauthTokenAuthType?, rhs: OauthTokenAuthType?) -> Bool {
14 | return type(of: lhs) == type(of: rhs) &&
15 | lhs?.token == rhs?.token
16 | }
17 |
18 | public struct OauthToken: OauthTokenAuthType {
19 | public let token: String
20 |
21 | public init(token: String) {
22 | self.token = token
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/SurveyResponseLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension SurveyResponse {
4 | public enum lens {
5 | public static let answeredAt = Lens(
6 | view: { $0.answeredAt },
7 | set: { .init(answeredAt: $0, id: $1.id, project: $1.project, urls: $1.urls) }
8 | )
9 |
10 | public static let id = Lens(
11 | view: { $0.id },
12 | set: { .init(answeredAt: $1.answeredAt, id: $0, project: $1.project, urls: $1.urls) }
13 | )
14 |
15 | public static let project = Lens(
16 | view: { $0.project },
17 | set: { .init(answeredAt: $1.answeredAt, id: $1.id, project: $0, urls: $1.urls) }
18 | )
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/Reward.ShippingLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension Reward.Shipping {
4 | public enum lens {
5 | public static let enabled = Lens(
6 | view: { $0.enabled },
7 | set: { .init(enabled: $0, preference: $1.preference, summary: $1.summary) }
8 | )
9 |
10 | public static let preference = Lens(
11 | view: { $0.preference },
12 | set: { .init(enabled: $1.enabled, preference: $0, summary: $1.summary) }
13 | )
14 |
15 | public static let summary = Lens(
16 | view: { $0.summary },
17 | set: { .init(enabled: $1.enabled, preference: $1.preference, summary: $0) }
18 | )
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/KsApi/models/SubmitApplePayEnvelope.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct SubmitApplePayEnvelope {
6 | public let thankYouUrl: String
7 | public let status: Int
8 | }
9 |
10 | extension SubmitApplePayEnvelope: Decodable {
11 | public static func decode(_ json: JSON) -> Decoded {
12 | return curry(SubmitApplePayEnvelope.init)
13 | <^> json <| ["data", "thankyou_url"]
14 | <*> ((json <| "status" >>- stringToIntOrZero) <|> (json <| "status"))
15 | }
16 | }
17 |
18 | private func stringToIntOrZero(_ string: String) -> Decoded {
19 | return
20 | Double(string).flatMap(Int.init).map(Decoded.success)
21 | ?? Int(string).map(Decoded.success)
22 | ?? .success(0)
23 | }
24 |
--------------------------------------------------------------------------------
/KsApi/models/UpdatePledgeEnvelope.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct UpdatePledgeEnvelope {
6 | public let newCheckoutUrl: String?
7 | public let status: Int
8 | }
9 |
10 | extension UpdatePledgeEnvelope: Decodable {
11 | public static func decode(_ json: JSON) -> Decoded {
12 | return curry(UpdatePledgeEnvelope.init)
13 | <^> json <|? ["data", "new_checkout_url"]
14 | <*> ((json <| "status" >>- stringToIntOrZero) <|> (json <| "status"))
15 | }
16 | }
17 |
18 | private func stringToIntOrZero(_ string: String) -> Decoded {
19 | return
20 | Double(string).flatMap(Int.init).map(Decoded.success)
21 | ?? Int(string).map(Decoded.success)
22 | ?? .success(0)
23 | }
24 |
--------------------------------------------------------------------------------
/KsApi/models/templates/ConfigTemplates.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension Config {
4 | internal static let template = Config(
5 | abExperiments: [:],
6 | appId: 123456789,
7 | applePayCountries: ["US", "GB", "CA", "AU", "FR", "CH", "SG", "HK", "ES", "NZ"],
8 | countryCode: "US",
9 | features: [:],
10 | iTunesLink: "http://www.itunes.com",
11 | launchedCountries: [.US, .CA, .AU, .NZ, .GB, .NL, .IE, .DE, .ES, .FR, .IT, .AT, .BE, .LU, .SE, .DK, .NO,
12 | .CH, .HK, .SG],
13 | locale: "en",
14 | stripePublishableKey: "pk"
15 | )
16 |
17 | internal static let config = Config.template
18 |
19 | internal static let deConfig = Config.template
20 | |> Config.lens.countryCode .~ "DE"
21 | |> Config.lens.locale .~ "de"
22 | }
23 |
--------------------------------------------------------------------------------
/KsApi/lib/EncodableType.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /**
4 | A type that can encode itself into a `[String:Any]` dictionary, usually for then
5 | serializing to a JSON string.
6 | */
7 | public protocol EncodableType {
8 | func encode() -> [String:Any]
9 | }
10 |
11 | public extension EncodableType {
12 | /**
13 | Returns `NSData` form of encoding.
14 |
15 | - returns: `NSData`
16 | */
17 | public func toJSONData() -> Data? {
18 | return try? JSONSerialization.data(withJSONObject: encode(), options: [])
19 | }
20 |
21 | /**
22 | Returns `String` form of encoding.
23 |
24 | - returns: `String`
25 | */
26 | public func toJSONString() -> String? {
27 | return self.toJSONData().flatMap { String(data: $0, encoding: .utf8) }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/KsApi/models/MessageTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 | import Argo
4 |
5 | internal final class MessageTests: XCTestCase {
6 | func testDecoding() {
7 | let result = Message.decodeJSONDictionary([
8 | "body": "Hello!",
9 | "created_at": 123456789.0,
10 | "id": 1,
11 | "recipient": [
12 | "id": 1,
13 | "name": "Blob",
14 | "avatar": [
15 | "medium": "img",
16 | "small": "img"
17 | ],
18 | ],
19 | "sender": [
20 | "id": 2,
21 | "name": "Clob",
22 | "avatar": [
23 | "medium": "img",
24 | "small": "img"
25 | ],
26 | ]
27 | ])
28 |
29 | XCTAssertNil(result.error)
30 | XCTAssertNotNil(result.value)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/FriendStatsEnvelopeLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension FriendStatsEnvelope {
4 | public enum lens {
5 | public static let stats = Lens(
6 | view: { $0.stats },
7 | set: { stats, _ in FriendStatsEnvelope(stats: stats) }
8 | )
9 | }
10 | }
11 |
12 | extension Lens where Whole == FriendStatsEnvelope, Part == FriendStatsEnvelope.Stats {
13 | public var friendProjectsCount: Lens {
14 | return FriendStatsEnvelope.lens.stats..FriendStatsEnvelope.Stats.lens.friendProjectsCount
15 | }
16 |
17 | public var remoteFriendsCount: Lens {
18 | return FriendStatsEnvelope.lens.stats..FriendStatsEnvelope.Stats.lens.remoteFriendsCount
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/KsApi/models/ChangePaymentMethodEnvelope.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct ChangePaymentMethodEnvelope {
6 | public let newCheckoutUrl: String?
7 | public let status: Int
8 | }
9 |
10 | extension ChangePaymentMethodEnvelope: Decodable {
11 | public static func decode(_ json: JSON) -> Decoded {
12 | return curry(ChangePaymentMethodEnvelope.init)
13 | <^> json <|? ["data", "new_checkout_url"]
14 | <*> ((json <| "status" >>- stringToIntOrZero) <|> (json <| "status"))
15 | }
16 | }
17 |
18 | private func stringToIntOrZero(_ string: String) -> Decoded {
19 | return
20 | Double(string).flatMap(Int.init).map(Decoded.success)
21 | ?? Int(string).map(Decoded.success)
22 | ?? .success(0)
23 | }
24 |
--------------------------------------------------------------------------------
/KsApi/models/templates/UpdateDraftTemplates.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension UpdateDraft {
4 | internal static let template = UpdateDraft(
5 | update: .template,
6 | images: [],
7 | video: nil
8 | )
9 |
10 | internal static let blank = template
11 | |> UpdateDraft.lens.update.title .~ ""
12 | |> UpdateDraft.lens.update.body .~ ""
13 | |> UpdateDraft.lens.update.isPublic .~ true
14 | }
15 |
16 | extension UpdateDraft.Image {
17 | internal static let template = UpdateDraft.Image(
18 | id: 1,
19 | thumb: "test-thumb.png",
20 | full: "test-full.png"
21 | )
22 | }
23 |
24 | extension UpdateDraft.Video {
25 | internal static let template = UpdateDraft.Video(
26 | id: 1,
27 | status: .successful,
28 | frame: "test-frame.png"
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/CreatePledgeEnvelopeLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension CreatePledgeEnvelope {
4 | public enum lens {
5 | public static let checkoutUrl = Lens(
6 | view: { $0.checkoutUrl },
7 | set: { .init(checkoutUrl: $0, newCheckoutUrl: $1.newCheckoutUrl, status: $1.status) }
8 | )
9 |
10 | public static let newCheckoutUrl = Lens(
11 | view: { $0.checkoutUrl },
12 | set: { .init(checkoutUrl: $1.checkoutUrl, newCheckoutUrl: $0, status: $1.status) }
13 | )
14 |
15 | public static let status = Lens(
16 | view: { $0.status },
17 | set: { .init(checkoutUrl: $1.checkoutUrl, newCheckoutUrl: $1.newCheckoutUrl, status: $0) }
18 | )
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/KsApi/lib/auth/ClientAuth.swift:
--------------------------------------------------------------------------------
1 | /**
2 | A type that holds an API client id, which provides anonymous authentication to the API.
3 | */
4 | public protocol ClientAuthType {
5 | var clientId: String { get }
6 | }
7 |
8 | public func == (lhs: ClientAuthType, rhs: ClientAuthType) -> Bool {
9 | return type(of: lhs) == type(of: rhs) &&
10 | lhs.clientId == rhs.clientId
11 | }
12 |
13 | public struct ClientAuth: ClientAuthType {
14 | public let clientId: String
15 |
16 | public init(clientId: String) {
17 | self.clientId = clientId
18 | }
19 |
20 | public static let production: ClientAuthType = ClientAuth(
21 | clientId: Secrets.Api.Client.production
22 | )
23 |
24 | public static let development: ClientAuthType = ClientAuth(
25 | clientId: Secrets.Api.Client.staging
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/KsApi/models/templates/DiscoveryEnvelopeTemplates.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension DiscoveryEnvelope {
4 | internal static let template = DiscoveryEnvelope(
5 | projects: [.template],
6 | urls: .template,
7 | stats: .template
8 | )
9 | }
10 |
11 | extension DiscoveryEnvelope.UrlsEnvelope {
12 | internal static let template = DiscoveryEnvelope.UrlsEnvelope(
13 | api: .template
14 | )
15 | }
16 |
17 | extension DiscoveryEnvelope.UrlsEnvelope.ApiEnvelope {
18 | internal static let template = DiscoveryEnvelope.UrlsEnvelope.ApiEnvelope(
19 | more_projects: "http://\(Secrets.Api.Endpoint.production)/gimme/more"
20 | )
21 | }
22 |
23 | extension DiscoveryEnvelope.StatsEnvelope {
24 | internal static let template = DiscoveryEnvelope.StatsEnvelope(
25 | count: 200
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/KsApi/lib/EncodableTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 |
4 | final class EncodableTests: XCTestCase {
5 |
6 | struct EncodableModel: EncodableType {
7 | let id: Int
8 | let name: String
9 | func encode() -> [String:Any] {
10 | return [
11 | "ID": self.id,
12 | "NAME": self.name
13 | ]
14 | }
15 | }
16 |
17 | func testToJSONString() {
18 | let model = EncodableModel(id: 1, name: "Blob")
19 | XCTAssertEqual(model.toJSONString(), "{\"ID\":1,\"NAME\":\"Blob\"}")
20 | }
21 |
22 | func testToJSONData() {
23 | let model = EncodableModel(id: 1, name: "Blob")
24 | let jsonString = "{\"ID\":1,\"NAME\":\"Blob\"}"
25 | let jsonData = jsonString.data(using: .utf8)
26 |
27 | XCTAssertEqual(model.toJSONData(), jsonData)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/KsApi/models/FriendStatsEnvelope.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct FriendStatsEnvelope {
6 | public let stats: Stats
7 |
8 | public struct Stats {
9 | public let friendProjectsCount: Int
10 | public let remoteFriendsCount: Int
11 | }
12 | }
13 |
14 | extension FriendStatsEnvelope: Decodable {
15 | public static func decode(_ json: JSON) -> Decoded {
16 | return curry(FriendStatsEnvelope.init)
17 | <^> json <| "stats"
18 | }
19 | }
20 |
21 | extension FriendStatsEnvelope.Stats: Decodable {
22 | public static func decode(_ json: JSON) -> Decoded {
23 | return curry(FriendStatsEnvelope.Stats.init)
24 | <^> json <| "friend_projects_count"
25 | <*> json <| "remote_friends_count"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/KsApi/models/CreatePledgeEnvelope.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct CreatePledgeEnvelope {
6 | public let checkoutUrl: String?
7 | public let newCheckoutUrl: String?
8 | public let status: Int
9 | }
10 |
11 | extension CreatePledgeEnvelope: Decodable {
12 | public static func decode(_ json: JSON) -> Decoded {
13 | return curry(CreatePledgeEnvelope.init)
14 | <^> json <|? ["data", "checkout_url"]
15 | <*> json <|? ["data", "new_checkout_url"]
16 | <*> ((json <| "status" >>- stringToIntOrZero) <|> (json <| "status"))
17 | }
18 | }
19 |
20 | private func stringToIntOrZero(_ string: String) -> Decoded {
21 | return
22 | Double(string).flatMap(Int.init).map(Decoded.success)
23 | ?? Int(string).map(Decoded.success)
24 | ?? .success(0)
25 | }
26 |
--------------------------------------------------------------------------------
/KsApi/models/ShippingRule.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct ShippingRule {
6 | public let cost: Double
7 | public let id: Int?
8 | public let location: Location
9 | }
10 |
11 | extension ShippingRule: Decodable {
12 | public static func decode(_ json: JSON) -> Decoded {
13 | return curry(ShippingRule.init)
14 | <^> (json <| "cost" >>- stringToDouble)
15 | <*> json <|? "id"
16 | <*> json <| "location"
17 | }
18 | }
19 |
20 | extension ShippingRule: Equatable {}
21 | public func == (lhs: ShippingRule, rhs: ShippingRule) -> Bool {
22 | // todo: change to compare id once that api is deployed
23 | return lhs.location == rhs.location
24 | }
25 |
26 | private func stringToDouble(_ string: String) -> Decoded {
27 | return Double(string).map(Decoded.success) ?? .success(0)
28 | }
29 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/RewardItemLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension RewardsItem {
4 | public enum lens {
5 | public static let id = Lens(
6 | view: { $0.id },
7 | set: { .init(id: $0, item: $1.item, quantity: $1.quantity, rewardId: $1.rewardId) }
8 | )
9 |
10 | public static let item = Lens(
11 | view: { $0.item },
12 | set: { .init(id: $1.id, item: $0, quantity: $1.quantity, rewardId: $1.rewardId) }
13 | )
14 |
15 | public static let quantity = Lens(
16 | view: { $0.quantity },
17 | set: { .init(id: $1.id, item: $1.item, quantity: $0, rewardId: $1.rewardId) }
18 | )
19 |
20 | public static let rewardId = Lens(
21 | view: { $0.rewardId },
22 | set: { .init(id: $1.id, item: $1.item, quantity: $1.quantity, rewardId: $0) }
23 | )
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/KsApi/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/KsApi/models/RewardsItemTests.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 | import XCTest
3 | @testable import KsApi
4 |
5 | final class RewardsItemTests: XCTestCase {
6 |
7 | func testDecoding() {
8 | let decoded = RewardsItem.decodeJSONDictionary([
9 | "id": 1,
10 | "item": [
11 | "description": "Hello",
12 | "id": 1,
13 | "name": "The thing",
14 | "project_id": 1
15 | ],
16 | "quantity": 2,
17 | "reward_id": 3
18 | ])
19 |
20 | XCTAssertNil(decoded.error)
21 |
22 | XCTAssertEqual(1, decoded.value?.id)
23 | XCTAssertEqual(2, decoded.value?.quantity)
24 | XCTAssertEqual(3, decoded.value?.rewardId)
25 |
26 | XCTAssertEqual("Hello", decoded.value?.item.description)
27 | XCTAssertEqual(1, decoded.value?.item.id)
28 | XCTAssertEqual("The thing", decoded.value?.item.name)
29 | XCTAssertEqual(1, decoded.value?.item.projectId)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/Project.PhotoLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension Project.Photo {
4 | public enum lens {
5 | public static let full = Lens(
6 | view: { $0.full },
7 | set: { .init(full: $0, med: $1.med, size1024x768: $1.size1024x768, small: $1.small) }
8 | )
9 |
10 | public static let med = Lens(
11 | view: { $0.full },
12 | set: { .init(full: $1.full, med: $0, size1024x768: $1.size1024x768, small: $1.small) }
13 | )
14 |
15 | public static let size1024x768 = Lens(
16 | view: { $0.size1024x768 },
17 | set: { .init(full: $1.full, med: $1.med, size1024x768: $0, small: $1.small) }
18 | )
19 |
20 | public static let small = Lens(
21 | view: { $0.small },
22 | set: { .init(full: $1.full, med: $1.med, size1024x768: $1.size1024x768, small: $0) }
23 | )
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/ItemLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension Item {
4 | public enum lens {
5 | public static let description = Lens
- (
6 | view: { $0.description },
7 | set: { .init(description: $0, id: $1.id, name: $1.name,
8 | projectId: $1.projectId) }
9 | )
10 |
11 | public static let id = Lens
- (
12 | view: { $0.id },
13 | set: { .init(description: $1.description, id: $0, name: $1.name,
14 | projectId: $1.projectId) }
15 | )
16 |
17 | public static let name = Lens
- (
18 | view: { $0.name },
19 | set: { .init(description: $1.description, id: $1.id, name: $0,
20 | projectId: $1.projectId) }
21 | )
22 |
23 | public static let projectId = Lens
- (
24 | view: { $0.projectId },
25 | set: { .init(description: $1.description, id: $1.id, name: $1.name,
26 | projectId: $0) }
27 | )
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/LocationLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension Location {
4 | public enum lens {
5 | public static let country = Lens(
6 | view: { $0.country },
7 | set: { Location(country: $0, displayableName: $1.displayableName, id: $1.id, name: $1.name) }
8 | )
9 |
10 | public static let displayableName = Lens(
11 | view: { $0.displayableName },
12 | set: { Location(country: $1.country, displayableName: $0, id: $1.id, name: $1.name) }
13 | )
14 |
15 | public static let id = Lens(
16 | view: { $0.id },
17 | set: { Location(country: $1.country, displayableName: $1.displayableName, id: $0, name: $1.name) }
18 | )
19 |
20 | public static let name = Lens(
21 | view: { $0.name },
22 | set: { Location(country: $1.country, displayableName: $1.displayableName, id: $1.id, name: $0) }
23 | )
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/KsApi/models/MessageThread.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct MessageThread {
6 | public let backing: Backing?
7 | public let closed: Bool
8 | public let id: Int
9 | public let lastMessage: Message
10 | public let participant: User
11 | public let project: Project
12 | public let unreadMessagesCount: Int
13 | }
14 |
15 | extension MessageThread: Decodable {
16 | public static func decode(_ json: JSON) -> Decoded {
17 | let create = curry(MessageThread.init)
18 | let tmp = create
19 | <^> json <|? "backing"
20 | <*> json <| "closed"
21 | <*> json <| "id"
22 | <*> json <| "last_message"
23 | return tmp
24 | <*> json <| "participant"
25 | <*> json <| "project"
26 | <*> json <| "unread_messages_count"
27 | }
28 | }
29 |
30 | extension MessageThread: Equatable {}
31 | public func == (lhs: MessageThread, rhs: MessageThread) -> Bool {
32 | return lhs.id == rhs.id
33 | }
34 |
--------------------------------------------------------------------------------
/KsApi/models/CreatePledgeEnvelopeTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 |
4 | final class CreatePledgeEnvelopeTests: XCTestCase {
5 |
6 | func testDecodingWithStringStatus() {
7 | let decoded = CreatePledgeEnvelope.decodeJSONDictionary(["status": "200"])
8 | XCTAssertNil(decoded.error)
9 | XCTAssertEqual(200, decoded.value?.status)
10 | }
11 |
12 | func testDecodingWithIntStatus() {
13 | let decoded = CreatePledgeEnvelope.decodeJSONDictionary(["status": 200])
14 | XCTAssertNil(decoded.error)
15 | XCTAssertEqual(200, decoded.value?.status)
16 | }
17 |
18 | func testDecodingWithMissingStatus() {
19 | let decoded = CreatePledgeEnvelope.decodeJSONDictionary([:])
20 | XCTAssertNotNil(decoded.error)
21 | }
22 |
23 | func testDecodingWithBadStatusData() {
24 | let decoded = CreatePledgeEnvelope.decodeJSONDictionary(["status": "bad data"])
25 | XCTAssertNil(decoded.error)
26 | XCTAssertEqual(0, decoded.value?.status)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/KsApi/models/UpdatePledgeEnvelopeTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 |
4 | final class UpdatePledgeEnvelopeTests: XCTestCase {
5 |
6 | func testDecodingWithStringStatus() {
7 | let decoded = UpdatePledgeEnvelope.decodeJSONDictionary(["status": "200"])
8 | XCTAssertNil(decoded.error)
9 | XCTAssertEqual(200, decoded.value?.status)
10 | }
11 |
12 | func testDecodingWithIntStatus() {
13 | let decoded = UpdatePledgeEnvelope.decodeJSONDictionary(["status": 200])
14 | XCTAssertNil(decoded.error)
15 | XCTAssertEqual(200, decoded.value?.status)
16 | }
17 |
18 | func testDecodingWithMissingStatus() {
19 | let decoded = UpdatePledgeEnvelope.decodeJSONDictionary([:])
20 | XCTAssertNotNil(decoded.error)
21 | }
22 |
23 | func testDecodingWithBadStatusData() {
24 | let decoded = UpdatePledgeEnvelope.decodeJSONDictionary(["status": "bad data"])
25 | XCTAssertNil(decoded.error)
26 | XCTAssertEqual(0, decoded.value?.status)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | xcode:
3 | version: 8.3
4 | dependencies:
5 | pre:
6 | - bin/bootstrap
7 | - brew update || brew update
8 | - brew install swiftlint
9 | - system_profiler SPSoftwareDataType
10 | - security list-keychains
11 | - security find-identity -p codesigning
12 | - instruments -s devices
13 | - xcodebuild -showsdks
14 | override:
15 | - git submodule sync --recursive
16 | - git submodule update --init --recursive || git submodule foreach git fetch origin --tags
17 | - git submodule update --init --recursive
18 | test:
19 | pre:
20 | - xcrun instruments -w 'iPhone 6 (9.3)' || true
21 | - sleep 15
22 | override:
23 | - set -o pipefail &&
24 | swiftlint lint --strict --reporter json |
25 | tee $CIRCLE_ARTIFACTS/swiftlint-report.json
26 | - bin/test iOS 9.3
27 | - bin/test iOS 10.2
28 | - bin/test tvOS 10.0
29 | experimental:
30 | notify:
31 | branches:
32 | only:
33 | - master
34 |
--------------------------------------------------------------------------------
/KsApi/models/ChangePaymentMethodEnvelopeTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 |
4 | final class ChangePaymentMethodEnvelopeTests: XCTestCase {
5 |
6 | func testDecodingWithStringStatus() {
7 | let decoded = ChangePaymentMethodEnvelope.decodeJSONDictionary(["status": "200"])
8 | XCTAssertNil(decoded.error)
9 | XCTAssertEqual(200, decoded.value?.status)
10 | }
11 |
12 | func testDecodingWithIntStatus() {
13 | let decoded = ChangePaymentMethodEnvelope.decodeJSONDictionary(["status": 200])
14 | XCTAssertNil(decoded.error)
15 | XCTAssertEqual(200, decoded.value?.status)
16 | }
17 |
18 | func testDecodingWithMissingStatus() {
19 | let decoded = ChangePaymentMethodEnvelope.decodeJSONDictionary([:])
20 | XCTAssertNotNil(decoded.error)
21 | }
22 |
23 | func testDecodingWithBadStatusData() {
24 | let decoded = ChangePaymentMethodEnvelope.decodeJSONDictionary(["status": "bad data"])
25 | XCTAssertNil(decoded.error)
26 | XCTAssertEqual(0, decoded.value?.status)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/KsApi/models/templates/RewardTemplates.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension Reward {
4 | internal static let template = Reward(
5 | backersCount: 50,
6 | description: "A cool thing",
7 | endsAt: nil,
8 | estimatedDeliveryOn: Date(
9 | timeIntervalSince1970: 1475361315).timeIntervalSince1970 + 60.0 * 60.0 * 24.0 * 365.0,
10 | id: 1,
11 | limit: 100,
12 | minimum: 10,
13 | remaining: 50,
14 | rewardsItems: [],
15 | shipping: Reward.Shipping(
16 | enabled: false,
17 | preference: nil,
18 | summary: nil
19 | ),
20 | startsAt: nil,
21 | title: nil
22 | )
23 |
24 | public static let noReward = Reward(
25 | backersCount: nil,
26 | description: "",
27 | endsAt: nil,
28 | estimatedDeliveryOn: nil,
29 | id: 0,
30 | limit: nil,
31 | minimum: 0,
32 | remaining: nil,
33 | rewardsItems: [],
34 | shipping: Reward.Shipping(enabled: false, preference: nil, summary: nil
35 | ),
36 | startsAt: nil,
37 | title: nil
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/MessageLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension Message {
4 | public enum lens {
5 | public static let id = Lens(
6 | view: { $0.id },
7 | set: { Message(body: $1.body, createdAt: $1.createdAt, id: $0, recipient: $1.recipient,
8 | sender: $1.sender) }
9 | )
10 |
11 | public static let body = Lens(
12 | view: { $0.body },
13 | set: { Message(body: $0, createdAt: $1.createdAt, id: $1.id, recipient: $1.recipient,
14 | sender: $1.sender) }
15 | )
16 |
17 | public static let recipient = Lens(
18 | view: { $0.recipient },
19 | set: { Message(body: $1.body, createdAt: $1.createdAt, id: $1.id, recipient: $0,
20 | sender: $1.sender) }
21 | )
22 |
23 | public static let sender = Lens(
24 | view: { $0.sender },
25 | set: { Message(body: $1.body, createdAt: $1.createdAt, id: $1.id, recipient: $1.recipient,
26 | sender: $0) }
27 | )
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/KsApi/models/templates/UpdateTemplates.swift:
--------------------------------------------------------------------------------
1 | // swiftlint:disable line_length
2 | extension Update {
3 | internal static let template = Update(
4 | body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam id vulputate augue. Donec elementum est facilisis dolor accumsan feugiat. Nam et pellentesque massa. Sed sit amet commodo ligula. Sed viverra, est viverra pretium luctus, arcu ligula congue neque, sed bibendum neque quam vel elit. Nunc varius orci et tempus consequat. Nullam tempor velit vitae consectetur mattis. Proin dignissim id turpis ac fermentum.",
5 | commentsCount: 2,
6 | hasLiked: false,
7 | id: 1,
8 | isPublic: true,
9 | likesCount: 3,
10 | projectId: 1,
11 | publishedAt: Date(timeIntervalSince1970: 1475361315).timeIntervalSince1970,
12 | sequence: 1,
13 | title: "Hello",
14 | urls: Update.UrlsEnvelope(web: Update.UrlsEnvelope.WebEnvelope(
15 | update: "https://www.kickstarter.com/projects/udoo/udoo-x86/posts/1571540")
16 | ),
17 | user: nil,
18 | visible: true
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/KsApi/models/MessageSubject.swift:
--------------------------------------------------------------------------------
1 | public enum MessageSubject {
2 | case backing(Backing)
3 | case messageThread(MessageThread)
4 | case project(Project)
5 |
6 | public var backing: Backing? {
7 | if case let .backing(backing) = self {
8 | return backing
9 | }
10 | return nil
11 | }
12 |
13 | public var messageThread: MessageThread? {
14 | if case let .messageThread(messageThread) = self {
15 | return messageThread
16 | }
17 | return nil
18 | }
19 |
20 | public var project: Project? {
21 | if case let .project(project) = self {
22 | return project
23 | }
24 | return nil
25 | }
26 | }
27 |
28 | extension MessageSubject: Equatable {}
29 | public func == (lhs: MessageSubject, rhs: MessageSubject) -> Bool {
30 | switch (lhs, rhs) {
31 | case let (.backing(lhs), .backing(rhs)):
32 | return lhs == rhs
33 | case let (.messageThread(lhs), .messageThread(rhs)):
34 | return lhs == rhs
35 | case let (.project(lhs), .project(rhs)):
36 | return lhs == rhs
37 | default:
38 | return false
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/KsApi/models/Location.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct Location {
6 | public let country: String
7 | public let displayableName: String
8 | public let id: Int
9 | public let name: String
10 |
11 | public static let none = Location(country: "", displayableName: "", id: -42, name: "")
12 | }
13 |
14 | extension Location: Equatable {}
15 | public func == (lhs: Location, rhs: Location) -> Bool {
16 | return lhs.id == rhs.id
17 | }
18 |
19 | extension Location: Decodable {
20 | static public func decode(_ json: JSON) -> Decoded {
21 | return curry(Location.init)
22 | <^> json <| "country"
23 | <*> json <| "displayable_name"
24 | <*> json <| "id"
25 | <*> json <| "name"
26 | }
27 | }
28 |
29 | extension Location: EncodableType {
30 | public func encode() -> [String:Any] {
31 | var result: [String:Any] = [:]
32 | result["country"] = self.country
33 | result["displayable_name"] = self.displayableName
34 | result["id"] = self.id
35 | result["name"] = self.name
36 | return result
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/User.NewsletterSubscriptionsLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension User.NewsletterSubscriptions {
4 | public enum lens {
5 | public static let games = Lens(
6 | view: { $0.games },
7 | set: { User.NewsletterSubscriptions(games: $0, happening: $1.happening, promo: $1.promo,
8 | weekly: $1.weekly) }
9 | )
10 |
11 | public static let happening = Lens(
12 | view: { $0.happening },
13 | set: { User.NewsletterSubscriptions(games: $1.games, happening: $0, promo: $1.promo,
14 | weekly: $1.weekly) }
15 | )
16 |
17 | public static let promo = Lens(
18 | view: { $0.promo },
19 | set: { User.NewsletterSubscriptions(games: $1.games, happening: $1.happening, promo: $0,
20 | weekly: $1.weekly) }
21 | )
22 |
23 | public static let weekly = Lens(
24 | view: { $0.weekly },
25 | set: { User.NewsletterSubscriptions(games: $1.games, happening: $1.happening, promo: $1.promo,
26 | weekly: $0) }
27 | )
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/KsApi/models/PushEnvelopeTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 | import Prelude
4 |
5 | final class PushEnvelopeTests: XCTestCase {
6 | func testDecode_Update_WithUpdateKey() {
7 | let decodedEnvelope = PushEnvelope.decodeJSONDictionary([
8 | "aps": [
9 | "alert": "Hi"
10 | ],
11 | "update": [
12 | "id": 1,
13 | "project_id": 2
14 | ]
15 | ])
16 | let envelope = decodedEnvelope.value
17 |
18 | XCTAssertNil(decodedEnvelope.error)
19 | XCTAssertNotNil(envelope?.update)
20 | XCTAssertEqual(1, envelope?.update?.id)
21 | XCTAssertEqual(2, envelope?.update?.projectId)
22 | }
23 |
24 | func testDecode_Update_WithPostKey() {
25 | let decodedEnvelope = PushEnvelope.decodeJSONDictionary([
26 | "aps": [
27 | "alert": "Hi"
28 | ],
29 | "post": [
30 | "id": 1,
31 | "project_id": 2
32 | ]
33 | ])
34 | let envelope = decodedEnvelope.value
35 |
36 | XCTAssertNil(decodedEnvelope.error)
37 | XCTAssertNotNil(envelope?.update)
38 | XCTAssertEqual(1, envelope?.update?.id)
39 | XCTAssertEqual(2, envelope?.update?.projectId)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/Project.PersonalizationLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension Project.Personalization {
4 | public enum lens {
5 | public static let backing = Lens(
6 | view: { $0.backing },
7 | set: { Project.Personalization(backing: $0, friends: $1.friends, isBacking: $1.isBacking,
8 | isStarred: $1.isStarred) }
9 | )
10 |
11 | public static let friends = Lens(
12 | view: { $0.friends },
13 | set: { Project.Personalization(backing: $1.backing, friends: $0, isBacking: $1.isBacking,
14 | isStarred: $1.isStarred) }
15 | )
16 |
17 | public static let isBacking = Lens(
18 | view: { $0.isBacking },
19 | set: { Project.Personalization(backing: $1.backing, friends: $1.friends, isBacking: $0,
20 | isStarred: $1.isStarred) }
21 | )
22 |
23 | public static let isStarred = Lens(
24 | view: { $0.isStarred },
25 | set: { Project.Personalization(backing: $1.backing, friends: $1.friends, isBacking: $1.isBacking,
26 | isStarred: $0) }
27 | )
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/KsApi/models/CommentsEnvelope.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct CommentsEnvelope {
6 | public let comments: [Comment]
7 | public let urls: UrlsEnvelope
8 |
9 | public struct UrlsEnvelope {
10 | public let api: ApiEnvelope
11 |
12 | public struct ApiEnvelope {
13 | public let moreComments: String
14 | }
15 | }
16 | }
17 |
18 | extension CommentsEnvelope: Decodable {
19 | public static func decode(_ json: JSON) -> Decoded {
20 | return curry(CommentsEnvelope.init)
21 | <^> json <|| "comments"
22 | <*> json <| "urls"
23 | }
24 | }
25 |
26 | extension CommentsEnvelope.UrlsEnvelope: Decodable {
27 | public static func decode(_ json: JSON) -> Decoded {
28 | return curry(CommentsEnvelope.UrlsEnvelope.init)
29 | <^> json <| "api"
30 | }
31 | }
32 |
33 | extension CommentsEnvelope.UrlsEnvelope.ApiEnvelope: Decodable {
34 | public static func decode(_ json: JSON) -> Decoded {
35 | return curry(CommentsEnvelope.UrlsEnvelope.ApiEnvelope.init)
36 | <^> (json <| "more_comments" <|> .success(""))
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/KsApi/models/ProjectsEnvelope.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct ProjectsEnvelope {
6 | public let projects: [Project]
7 | public let urls: UrlsEnvelope
8 |
9 | public struct UrlsEnvelope {
10 | public let api: ApiEnvelope
11 |
12 | public struct ApiEnvelope {
13 | public let moreProjects: String
14 | }
15 | }
16 | }
17 |
18 | extension ProjectsEnvelope: Decodable {
19 | public static func decode(_ json: JSON) -> Decoded {
20 | return curry(ProjectsEnvelope.init)
21 | <^> json <|| "projects"
22 | <*> json <| "urls"
23 | }
24 | }
25 |
26 | extension ProjectsEnvelope.UrlsEnvelope: Decodable {
27 | public static func decode(_ json: JSON) -> Decoded {
28 | return curry(ProjectsEnvelope.UrlsEnvelope.init)
29 | <^> json <| "api"
30 | }
31 | }
32 |
33 | extension ProjectsEnvelope.UrlsEnvelope.ApiEnvelope: Decodable {
34 | public static func decode(_ json: JSON) -> Decoded {
35 | return curry(ProjectsEnvelope.UrlsEnvelope.ApiEnvelope.init)
36 | <^> (json <| "more_projects" <|> .success(""))
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/KsApi/models/ActivityEnvelope.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct ActivityEnvelope {
6 | public let activities: [Activity]
7 | public let urls: UrlsEnvelope
8 |
9 | public struct UrlsEnvelope {
10 | public let api: ApiEnvelope
11 |
12 | public struct ApiEnvelope {
13 | public let moreActivities: String
14 | }
15 | }
16 | }
17 |
18 | extension ActivityEnvelope: Decodable {
19 | public static func decode(_ json: JSON) -> Decoded {
20 | return curry(ActivityEnvelope.init)
21 | <^> json <|| "activities"
22 | <*> json <| "urls"
23 | }
24 | }
25 |
26 | extension ActivityEnvelope.UrlsEnvelope: Decodable {
27 | public static func decode(_ json: JSON) -> Decoded {
28 | return curry(ActivityEnvelope.UrlsEnvelope.init)
29 | <^> json <| "api"
30 | }
31 | }
32 |
33 | extension ActivityEnvelope.UrlsEnvelope.ApiEnvelope: Decodable {
34 | public static func decode(_ json: JSON) -> Decoded {
35 | return curry(ActivityEnvelope.UrlsEnvelope.ApiEnvelope.init)
36 | <^> (json <| "more_activities" <|> .success(""))
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/CommentLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension Comment {
4 | public enum lens {
5 | public static let author = Lens(
6 | view: { $0.author },
7 | set: { Comment(author: $0, body: $1.body, createdAt: $1.createdAt, deletedAt: $1.deletedAt, id: $1.id) }
8 | )
9 |
10 | public static let body = Lens(
11 | view: { $0.body },
12 | set: { Comment(author: $1.author, body: $0, createdAt: $1.createdAt, deletedAt: $1.deletedAt,
13 | id: $1.id) }
14 | )
15 |
16 | public static let createdAt = Lens(
17 | view: { $0.createdAt },
18 | set: { Comment(author: $1.author, body: $1.body, createdAt: $0, deletedAt: $1.deletedAt, id: $1.id) }
19 | )
20 |
21 | public static let deletedAt = Lens(
22 | view: { $0.deletedAt },
23 | set: { Comment(author: $1.author, body: $1.body, createdAt: $1.createdAt, deletedAt: $0, id: $1.id) }
24 | )
25 |
26 | public static let id = Lens(
27 | view: { $0.id },
28 | set: { Comment(author: $1.author, body: $1.body, createdAt: $1.createdAt, deletedAt: $1.deletedAt,
29 | id: $0) }
30 | )
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/KsApi/models/templates/UserTemplates.swift:
--------------------------------------------------------------------------------
1 | // swiftlint:disable line_length
2 | import Prelude
3 |
4 | extension User {
5 | internal static let template = User(
6 | avatar: .template,
7 | facebookConnected: nil,
8 | id: 1,
9 | isFriend: nil,
10 | liveAuthToken: "deadbeef",
11 | location: nil,
12 | name: "Blob",
13 | newsletters: .template,
14 | notifications: .template,
15 | social: nil,
16 | stats: .template
17 | )
18 |
19 | internal static let brando = .template
20 | |> User.lens.avatar.large .~ "https://ksr-ugc.imgix.net/assets/006/258/518/b9033f46095b83119188cf9a66d19356_original.jpg?w=160&h=160&fit=crop&v=1461376829&auto=format&q=92&s=8d7666f01ab6765c3cf09149751ff077"
21 | |> User.lens.avatar.medium .~ "https://ksr-ugc.imgix.net/assets/006/258/518/b9033f46095b83119188cf9a66d19356_original.jpg?w=40&h=40&fit=crop&v=1461376829&auto=format&q=92&s=0fcedf8888ca6990408ccde81888899b"
22 | |> User.lens.avatar.small .~ "https://ksr-ugc.imgix.net/assets/006/258/518/b9033f46095b83119188cf9a66d19356_original.jpg?w=40&h=40&fit=crop&v=1461376829&auto=format&q=92&s=0fcedf8888ca6990408ccde81888899b"
23 | |> User.lens.id .~ "brando".hash
24 | |> User.lens.name .~ "Brandon Williams"
25 | }
26 |
--------------------------------------------------------------------------------
/KsApi/models/Comment.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct Comment {
6 | public let author: User
7 | public let body: String
8 | public let createdAt: TimeInterval
9 | public let deletedAt: TimeInterval?
10 | public let id: Int
11 | }
12 |
13 | extension Comment: Decodable {
14 | public static func decode(_ json: JSON) -> Decoded {
15 | let create = curry(Comment.init)
16 | let tmp = create
17 | <^> json <| "author"
18 | <*> json <| "body"
19 | <*> json <| "created_at"
20 | return tmp
21 | <*> (json <|? "deleted_at" >>- decodePositiveTimeInterval)
22 | <*> json <| "id"
23 | }
24 | }
25 |
26 | extension Comment: Equatable {
27 | }
28 | public func == (lhs: Comment, rhs: Comment) -> Bool {
29 | return lhs.id == rhs.id
30 | }
31 |
32 | // Decode a time interval so that non-positive values are coalesced to `nil`. We do this because the API
33 | // sends back `0` when the comment hasn't been deleted, and we'd rather handle that value as `nil`.
34 | private func decodePositiveTimeInterval(_ interval: TimeInterval?) -> Decoded {
35 | if let interval = interval, interval > 0.0 {
36 | return .success(interval)
37 | }
38 | return .success(nil)
39 | }
40 |
--------------------------------------------------------------------------------
/KsApi/models/MessageThreadsEnvelope.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct MessageThreadsEnvelope {
6 | public let messageThreads: [MessageThread]
7 | public let urls: UrlsEnvelope
8 |
9 | public struct UrlsEnvelope {
10 | public let api: ApiEnvelope
11 |
12 | public struct ApiEnvelope {
13 | public let moreMessageThreads: String
14 | }
15 | }
16 | }
17 |
18 | extension MessageThreadsEnvelope: Decodable {
19 | public static func decode(_ json: JSON) -> Decoded {
20 | return curry(MessageThreadsEnvelope.init)
21 | <^> json <|| "message_threads"
22 | <*> json <| "urls"
23 | }
24 | }
25 |
26 | extension MessageThreadsEnvelope.UrlsEnvelope: Decodable {
27 | public static func decode(_ json: JSON) -> Decoded {
28 | return curry(MessageThreadsEnvelope.UrlsEnvelope.init)
29 | <^> json <| "api"
30 | }
31 | }
32 |
33 | extension MessageThreadsEnvelope.UrlsEnvelope.ApiEnvelope: Decodable {
34 | public static func decode(_ json: JSON) -> Decoded {
35 | return curry(MessageThreadsEnvelope.UrlsEnvelope.ApiEnvelope.init)
36 | <^> json <| "more_message_threads"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/KsApi/models/ProjectActivityEnvelope.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct ProjectActivityEnvelope {
6 | public let activities: [Activity]
7 | public let urls: UrlsEnvelope
8 |
9 | public struct UrlsEnvelope {
10 | public let api: ApiEnvelope
11 |
12 | public struct ApiEnvelope {
13 | public let moreActivities: String
14 | }
15 | }
16 | }
17 |
18 | extension ProjectActivityEnvelope: Decodable {
19 | public static func decode(_ json: JSON) -> Decoded {
20 | return curry(ProjectActivityEnvelope.init)
21 | <^> json <|| "activities"
22 | <*> json <| "urls"
23 | }
24 | }
25 |
26 | extension ProjectActivityEnvelope.UrlsEnvelope: Decodable {
27 | public static func decode(_ json: JSON) -> Decoded {
28 | return curry(ProjectActivityEnvelope.UrlsEnvelope.init)
29 | <^> json <| "api"
30 | }
31 | }
32 |
33 | extension ProjectActivityEnvelope.UrlsEnvelope.ApiEnvelope: Decodable {
34 | public static func decode(_ json: JSON) -> Decoded {
35 | return curry(ProjectActivityEnvelope.UrlsEnvelope.ApiEnvelope.init)
36 | <^> (json <| "more_activities" <|> .success(""))
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/ProjectStatsEnvelope.RewardDistributionLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension ProjectStatsEnvelope.RewardStats {
4 | public enum lens {
5 | public static let backersCount = Lens(
6 | view: { $0.backersCount },
7 | set: { ProjectStatsEnvelope.RewardStats(backersCount: $0, rewardId: $1.rewardId,
8 | minimum: $1.minimum, pledged: $1.pledged) }
9 | )
10 |
11 | public static let id = Lens(
12 | view: { $0.rewardId },
13 | set: { ProjectStatsEnvelope.RewardStats(backersCount: $1.backersCount, rewardId: $0,
14 | minimum: $1.minimum, pledged: $1.pledged) }
15 | )
16 |
17 | public static let minimum = Lens(
18 | view: { $0.minimum },
19 | set: { ProjectStatsEnvelope.RewardStats(backersCount: $1.backersCount,
20 | rewardId: $1.rewardId, minimum: $0, pledged: $1.pledged) }
21 | )
22 |
23 | public static let pledged = Lens(
24 | view: { $0.pledged },
25 | set: { ProjectStatsEnvelope.RewardStats(backersCount: $1.backersCount,
26 | rewardId: $1.rewardId, minimum: $1.minimum, pledged: $0) }
27 | )
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/KsApi/models/FindFriendsEnvelope.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct FindFriendsEnvelope {
6 | public let contactsImported: Bool
7 | public let urls: UrlsEnvelope
8 | public let users: [User]
9 |
10 | public struct UrlsEnvelope {
11 | public let api: ApiEnvelope
12 |
13 | public struct ApiEnvelope {
14 | public let moreUsers: String?
15 | }
16 | }
17 | }
18 |
19 | extension FindFriendsEnvelope: Decodable {
20 | public static func decode(_ json: JSON) -> Decoded {
21 | return curry(FindFriendsEnvelope.init)
22 | <^> json <| "contacts_imported"
23 | <*> json <| "urls"
24 | <*> (json <|| "users" <|> .success([]))
25 | }
26 | }
27 |
28 | extension FindFriendsEnvelope.UrlsEnvelope: Decodable {
29 | public static func decode(_ json: JSON) -> Decoded {
30 | return curry(FindFriendsEnvelope.UrlsEnvelope.init)
31 | <^> json <| "api"
32 | }
33 | }
34 |
35 | extension FindFriendsEnvelope.UrlsEnvelope.ApiEnvelope: Decodable {
36 | public static func decode(_ json: JSON) -> Decoded {
37 | return curry(FindFriendsEnvelope.UrlsEnvelope.ApiEnvelope.init)
38 | <^> json <|? "more_users"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/KsApi/lib/MimeType.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MobileCoreServices
3 |
4 | extension Data {
5 | internal var imageMime: String? {
6 |
7 | let start = (self as NSData).bytes.bindMemory(to: UInt8.self, capacity: self.count)
8 |
9 | guard let byte: UInt8 = UnsafeBufferPointer(start: start, count: 1).first else { return nil }
10 |
11 | switch byte {
12 | case 0xFF:
13 | return mimeType(uti: kUTTypeJPEG)
14 | case 0x89:
15 | return mimeType(uti: kUTTypePNG)
16 | case 0x47:
17 | return mimeType(uti: kUTTypeGIF)
18 | default:
19 | return nil
20 | }
21 | }
22 | }
23 |
24 | extension URL {
25 | internal var imageMime: String? {
26 | return mimeType(extension: self.pathExtension, where: kUTTypeImage)
27 | }
28 | }
29 |
30 | private func mimeType(extension: String, where: CFString? = nil) -> String? {
31 | let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension,
32 | `extension` as CFString,
33 | `where`)?.takeRetainedValue()
34 | return uti.flatMap(mimeType(uti:))
35 | }
36 |
37 | private func mimeType(uti: CFString) -> String? {
38 | return UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)?.takeRetainedValue() as String?
39 | }
40 |
--------------------------------------------------------------------------------
/KsApi/models/CommentTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 |
4 | final class CommentTests: XCTestCase {
5 |
6 | func testJSONParsing_WithCompleteData() {
7 |
8 | let comment = Comment.decodeJSONDictionary([
9 | "author": [
10 | "id": 1,
11 | "name": "Blob",
12 | "avatar": [
13 | "medium": "http://www.kickstarter.com/medium.jpg",
14 | "small": "http://www.kickstarter.com/small.jpg"
15 | ]
16 | ],
17 | "body": "hello!",
18 | "created_at": 123456789.0,
19 | "deleted_at": 123456789.0,
20 | "id": 1
21 | ])
22 |
23 | XCTAssertNil(comment.error)
24 | XCTAssertEqual(1, comment.value?.id)
25 | }
26 |
27 | func testJSONParsing_ZeroDeletedAt() {
28 |
29 | let comment = Comment.decodeJSONDictionary([
30 | "author": [
31 | "id": 1,
32 | "name": "Blob",
33 | "avatar": [
34 | "medium": "http://www.kickstarter.com/medium.jpg",
35 | "small": "http://www.kickstarter.com/small.jpg"
36 | ]
37 | ],
38 | "body": "hello!",
39 | "created_at": 123456789.0,
40 | "deleted_at": 0,
41 | "id": 1
42 | ])
43 |
44 | XCTAssertNil(comment.error)
45 | XCTAssertNotNil(comment.value)
46 | XCTAssertNil(comment.value?.deletedAt)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/KsApi/ServiceTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 |
4 | final class ServiceTests: XCTestCase {
5 |
6 | func testDefaults() {
7 | XCTAssertTrue(Service().serverConfig == ServerConfig.production)
8 | XCTAssertNil(Service().oauthToken)
9 | XCTAssertEqual(Service().language, "en")
10 | }
11 |
12 | func testEquals() {
13 | let s1 = Service()
14 | let s2 = Service(serverConfig: ServerConfig.staging)
15 | let s3 = Service(oauthToken: OauthToken(token: "deadbeef"))
16 | let s4 = Service(language: "es")
17 |
18 | XCTAssertTrue(s1 == s1)
19 | XCTAssertTrue(s2 == s2)
20 | XCTAssertTrue(s3 == s3)
21 | XCTAssertTrue(s4 == s4)
22 |
23 | XCTAssertFalse(s1 == s2)
24 | XCTAssertFalse(s1 == s3)
25 | XCTAssertFalse(s1 == s4)
26 |
27 | XCTAssertFalse(s2 == s3)
28 | XCTAssertFalse(s2 == s4)
29 |
30 | XCTAssertFalse(s3 == s4)
31 | }
32 |
33 | func testLogin() {
34 | let loggedOut = Service()
35 | let loggedIn = loggedOut.login(OauthToken(token: "deadbeef"))
36 |
37 | XCTAssertTrue(loggedIn == Service(oauthToken: OauthToken(token: "deadbeef")))
38 | }
39 |
40 | func testLogout() {
41 | let loggedIn = Service(oauthToken: OauthToken(token: "deadbeef"))
42 | let loggedOut = loggedIn.logout()
43 |
44 | XCTAssertTrue(loggedOut == Service())
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/KsApi/models/ProjectNotification.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 | import Foundation
5 |
6 | public struct ProjectNotification {
7 | public let email: Bool
8 | public let id: Int
9 | public let mobile: Bool
10 | public let project: Project
11 |
12 | public struct Project {
13 | public let id: Int
14 | public let name: String
15 | }
16 | }
17 |
18 | extension ProjectNotification: Decodable {
19 | public static func decode(_ json: JSON) -> Decoded {
20 | let create = curry(ProjectNotification.init)
21 | return create
22 | <^> json <| "email"
23 | <*> json <| "id"
24 | <*> json <| "mobile"
25 | <*> json <| "project"
26 | }
27 | }
28 |
29 | extension ProjectNotification.Project: Decodable {
30 | public static func decode(_ json: JSON) -> Decoded {
31 | return curry(ProjectNotification.Project.init)
32 | <^> json <| "id"
33 | <*> json <| "name"
34 | }
35 | }
36 |
37 | extension ProjectNotification: Equatable {}
38 | public func == (lhs: ProjectNotification, rhs: ProjectNotification) -> Bool {
39 | return lhs.id == rhs.id
40 | }
41 |
42 | extension ProjectNotification.Project: Equatable {}
43 | public func == (lhs: ProjectNotification.Project, rhs: ProjectNotification.Project) -> Bool {
44 | return lhs.id == rhs.id
45 | }
46 |
--------------------------------------------------------------------------------
/KsApi/models/UpdateDraftTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 | import Prelude
4 |
5 | final class UpdateDraftTests: XCTestCase {
6 |
7 | func testJSONParsing_WithCompleteData() {
8 |
9 | let decoded = UpdateDraft.decodeJSONDictionary([
10 | "body": "world",
11 | "id": 1,
12 | "public": true,
13 | "project_id": 2,
14 | "sequence": 3,
15 | "title": "hello",
16 | "visible": true,
17 | "urls": [
18 | "web": [
19 | "update": "https://www.kickstarter.com/projects/udoo/udoo-x86/posts/1571540"
20 | ]
21 | ],
22 | "images": [["id": 3, "thumb": "thumb.jpg", "full": "full.jpg"]],
23 | "video": ["id": 4, "frame": "frame.jpg", "status": "successful"]
24 | ])
25 |
26 | XCTAssertNil(decoded.error)
27 | let draft = decoded.value
28 | XCTAssertEqual(1, draft?.update.id)
29 | XCTAssertEqual(3, draft?.images.first?.id)
30 | XCTAssertEqual(4, draft?.video?.id)
31 | }
32 |
33 | func testAttachmentThumbUrl() {
34 |
35 | let image = UpdateDraft.Attachment.image(.template |> UpdateDraft.Image.lens.full .~ "full.jpg")
36 | XCTAssertEqual("full.jpg", image.thumbUrl)
37 |
38 | let video = UpdateDraft.Attachment.video(.template |> UpdateDraft.Video.lens.frame .~ "frame.jpg")
39 | XCTAssertEqual("frame.jpg", video.thumbUrl)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/ProjectNotificationLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension ProjectNotification {
4 | public enum lens {
5 | public static let email = Lens(
6 | view: { $0.email },
7 | set: { ProjectNotification(email: $0, id: $1.id, mobile: $1.mobile, project: $1.project) }
8 | )
9 |
10 | public static let id = Lens(
11 | view: { $0.id },
12 | set: { ProjectNotification(email: $1.email, id: $0, mobile: $1.mobile, project: $1.project) }
13 | )
14 |
15 | public static let mobile = Lens(
16 | view: { $0.mobile },
17 | set: { ProjectNotification(email: $1.email, id: $1.id, mobile: $0, project: $1.project) }
18 | )
19 |
20 | public static let project = Lens(
21 | view: { $0.project },
22 | set: { ProjectNotification(email: $1.email, id: $1.id, mobile: $1.mobile, project: $0) }
23 | )
24 | }
25 | }
26 |
27 | extension Lens where Whole == ProjectNotification, Part == ProjectNotification.Project {
28 | public var id: Lens {
29 | return ProjectNotification.lens.project..ProjectNotification.Project.lens.id
30 | }
31 |
32 | public var name: Lens {
33 | return ProjectNotification.lens.project..ProjectNotification.Project.lens.name
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/FindFriendsEnvelopeLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension FindFriendsEnvelope {
4 | public enum lens {
5 | public static let contactsImported = Lens(
6 | view: { $0.contactsImported },
7 | set: { FindFriendsEnvelope(contactsImported: $0, urls: $1.urls, users: $1.users) }
8 | )
9 | public static let urls = Lens(
10 | view: { $0.urls },
11 | set: { FindFriendsEnvelope(contactsImported: $1.contactsImported, urls: $0, users: $1.users) }
12 | )
13 | public static let users = Lens(
14 | view: { $0.users },
15 | set: { FindFriendsEnvelope(contactsImported: $1.contactsImported, urls: $1.urls, users: $0) }
16 | )
17 | }
18 | }
19 |
20 | extension FindFriendsEnvelope.UrlsEnvelope {
21 | public enum lens {
22 | public static let api = Lens(
23 | view: { $0.api },
24 | set: { part, _ in FindFriendsEnvelope.UrlsEnvelope(api: part) }
25 | )
26 | }
27 | }
28 |
29 | extension FindFriendsEnvelope.UrlsEnvelope.ApiEnvelope {
30 | public enum lens {
31 | public static let moreProjects = Lens(
32 | view: { $0.moreUsers },
33 | set: { part, _ in FindFriendsEnvelope.UrlsEnvelope.ApiEnvelope(moreUsers: part) }
34 | )
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/KsApi/models/SurveyResponse.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct SurveyResponse {
6 | public let answeredAt: TimeInterval?
7 | public let id: Int
8 | public let project: Project?
9 | public let urls: UrlsEnvelope
10 |
11 | public struct UrlsEnvelope {
12 | public let web: WebEnvelope
13 |
14 | public struct WebEnvelope {
15 | public let survey: String
16 | }
17 | }
18 | }
19 |
20 | extension SurveyResponse: Equatable {}
21 | public func == (lhs: SurveyResponse, rhs: SurveyResponse) -> Bool {
22 | return lhs.id == rhs.id
23 | }
24 |
25 | extension SurveyResponse: Decodable {
26 | public static func decode(_ json: JSON) -> Decoded {
27 | return curry(SurveyResponse.init)
28 | <^> json <|? "answered_at"
29 | <*> json <| "id"
30 | <*> json <|? "project"
31 | <*> json <| "urls"
32 | }
33 | }
34 |
35 | extension SurveyResponse.UrlsEnvelope: Decodable {
36 | public static func decode(_ json: JSON) -> Decoded {
37 | return curry(SurveyResponse.UrlsEnvelope.init)
38 | <^> json <| "web"
39 | }
40 | }
41 |
42 | extension SurveyResponse.UrlsEnvelope.WebEnvelope: Decodable {
43 | public static func decode(_ json: JSON) -> Decoded {
44 | return curry(SurveyResponse.UrlsEnvelope.WebEnvelope.init)
45 | <^> json <| "survey"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/KsApi/models/Project.CountryTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 |
4 | final class ProjectCountryTests: XCTestCase {
5 |
6 | func testEquatable() {
7 | XCTAssertEqual(Project.Country.US, Project.Country.US)
8 | XCTAssertNotEqual(Project.Country.US, Project.Country.CA)
9 | XCTAssertNotEqual(Project.Country.US, Project.Country.AU)
10 | XCTAssertNotEqual(Project.Country.DE, Project.Country.ES)
11 | }
12 |
13 | func testDescription() {
14 | XCTAssertNotEqual(Project.Country.US.description, "")
15 | }
16 |
17 | func testJsonDecoding_StandardJSON() {
18 | let decodedCountry = Project.Country.decodeJSONDictionary([
19 | "country": "US",
20 | "currency": "USD",
21 | "currency_symbol": "$",
22 | "currency_trailing_code": true
23 | ])
24 |
25 | XCTAssertEqual(.US, decodedCountry.value)
26 |
27 | let country = decodedCountry.value!
28 | XCTAssertEqual(country, Project.Country.decodeJSONDictionary(country.encode()).value)
29 | }
30 |
31 | func testJsonDecoding_ConfigJSON() {
32 | let decodedCountry = Project.Country.decodeJSONDictionary([
33 | "name": "US",
34 | "currency_code": "USD",
35 | "currency_symbol": "$",
36 | "trailing_code": true
37 | ])
38 |
39 | XCTAssertEqual(.US, decodedCountry.value)
40 |
41 | let country = decodedCountry.value!
42 | XCTAssertEqual(country, Project.Country.decodeJSONDictionary(country.encode()).value)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/KsApi/models/ErrorEnvelopeTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 | @testable import Argo
4 |
5 | class ErrorEnvelopeTests: XCTestCase {
6 |
7 | func testJsonDecodingWithFullData() {
8 | let env = ErrorEnvelope.decodeJSONDictionary([
9 | "error_messages": ["hello"],
10 | "ksr_code": "access_token_invalid",
11 | "http_code": 401,
12 | "exception": [
13 | "backtrace": ["hello"],
14 | "message": "hello"
15 | ]
16 | ])
17 | XCTAssertNotNil(env)
18 | }
19 |
20 | func testJsonDecodingWithBadKsrCode() {
21 | let env = ErrorEnvelope.decodeJSONDictionary([
22 | "error_messages": ["hello"],
23 | "ksr_code": "doesnt_exist",
24 | "http_code": 401,
25 | "exception": [
26 | "backtrace": ["hello"],
27 | "message": "hello"
28 | ]
29 | ])
30 | XCTAssertNil(env.error)
31 | XCTAssertEqual(ErrorEnvelope.KsrCode.UnknownCode, env.value?.ksrCode)
32 | }
33 |
34 | func testJsonDecodingWithNonStandardError() {
35 | let env = ErrorEnvelope.decodeJSONDictionary([
36 | "status": 406,
37 | "data": [
38 | "errors": [
39 | "amount": [
40 | "Bad amount"
41 | ]
42 | ]
43 | ]
44 | ])
45 | XCTAssertNil(env.error)
46 | XCTAssertEqual(ErrorEnvelope.KsrCode.UnknownCode, env.value?.ksrCode)
47 | XCTAssertEqual(["Bad amount"], env.value!.errorMessages)
48 | XCTAssertEqual(406, env.value?.httpCode)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/Project.MemberDataLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension Project.MemberData {
4 | public enum lens {
5 | public static let lastUpdatePublishedAt = Lens(
6 | view: { $0.lastUpdatePublishedAt },
7 | set: { Project.MemberData(lastUpdatePublishedAt: $0, permissions: $1.permissions,
8 | unreadMessagesCount: $1.unreadMessagesCount, unseenActivityCount: $1.unseenActivityCount) }
9 | )
10 |
11 | public static let permissions = Lens(
12 | view: { $0.permissions },
13 | set: { Project.MemberData(lastUpdatePublishedAt: $1.lastUpdatePublishedAt, permissions: $0,
14 | unreadMessagesCount: $1.unreadMessagesCount, unseenActivityCount: $1.unseenActivityCount) }
15 | )
16 |
17 | public static let unreadMessagesCount = Lens(
18 | view: { $0.unreadMessagesCount },
19 | set: { Project.MemberData(lastUpdatePublishedAt: $1.lastUpdatePublishedAt, permissions: $1.permissions,
20 | unreadMessagesCount: $0, unseenActivityCount: $1.unseenActivityCount) }
21 | )
22 |
23 | public static let unseenActivityCount = Lens(
24 | view: { $0.unseenActivityCount },
25 | set: { Project.MemberData(lastUpdatePublishedAt: $1.lastUpdatePublishedAt, permissions: $1.permissions,
26 | unreadMessagesCount: $1.unreadMessagesCount, unseenActivityCount: $0) }
27 | )
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/KsApi/models/LocationTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 | import Prelude
4 |
5 | final class LocationTests: XCTestCase {
6 |
7 | func testEquatable() {
8 | XCTAssertEqual(Location.template, Location.template)
9 | XCTAssertNotEqual(Location.template, Location.template |> Location.lens.id %~ { $0 + 1 })
10 | }
11 |
12 | func testJSONParsing_WithPartialData() {
13 |
14 | let location = Location.decodeJSONDictionary([
15 | "id": 1
16 | ])
17 |
18 | XCTAssertNotNil(location.error)
19 | }
20 |
21 | func testJSONParsing_WithFullData() {
22 |
23 | let location = Location.decodeJSONDictionary([
24 | "country": "US",
25 | "id": 1,
26 | "displayable_name": "Brooklyn, NY",
27 | "name": "Brooklyn"
28 | ])
29 |
30 | XCTAssertNil(location.error)
31 | XCTAssertEqual(location.value?.id, 1)
32 | XCTAssertEqual(location.value?.displayableName, "Brooklyn, NY")
33 | XCTAssertEqual(location.value?.name, "Brooklyn")
34 | }
35 |
36 | func testEncodeDecode() {
37 | let location: [String:Any] = [
38 | "country": "US",
39 | "id": 44,
40 | "displayable_name": "New Amsterdam, NY",
41 | "name": "New Amsterdam"
42 | ]
43 |
44 | let decodedLocation = Location.decodeJSONDictionary(location).value
45 |
46 | XCTAssertEqual(decodedLocation, Location.decodeJSONDictionary(decodedLocation?.encode() ?? [:]).value)
47 | XCTAssertEqual(decodedLocation?.encode() as NSDictionary?, location as NSDictionary?)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/Project.CreatorDataLenses.swift:
--------------------------------------------------------------------------------
1 | // swiftlint:disable type_name
2 | import Prelude
3 |
4 | extension Project.CreatorData {
5 | public enum lens {
6 | public static let lastUpdatePublishedAt = Lens(
7 | view: { $0.lastUpdatePublishedAt },
8 | set: { Project.CreatorData(lastUpdatePublishedAt: $0, permissions: $1.permissions,
9 | unreadMessagesCount: $1.unreadMessagesCount, unseenActivityCount: $1.unseenActivityCount) }
10 | )
11 |
12 | public static let permissions = Lens(
13 | view: { $0.permissions },
14 | set: { Project.CreatorData(lastUpdatePublishedAt: $1.lastUpdatePublishedAt, permissions: $0,
15 | unreadMessagesCount: $1.unreadMessagesCount, unseenActivityCount: $1.unseenActivityCount) }
16 | )
17 |
18 | public static let unreadMessagesCount = Lens(
19 | view: { $0.unreadMessagesCount },
20 | set: { Project.CreatorData(lastUpdatePublishedAt: $1.lastUpdatePublishedAt, permissions: $1.permissions,
21 | unreadMessagesCount: $0, unseenActivityCount: $1.unseenActivityCount) }
22 | )
23 |
24 | public static let unseenActivityCount = Lens(
25 | view: { $0.unseenActivityCount },
26 | set: { Project.CreatorData(lastUpdatePublishedAt: $1.lastUpdatePublishedAt, permissions: $1.permissions,
27 | unreadMessagesCount: $1.unreadMessagesCount, unseenActivityCount: $0) }
28 | )
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/ProjectStatsEnvelope.VideoStatsLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension ProjectStatsEnvelope.VideoStats {
4 | public enum lens {
5 | public static let externalCompletions = Lens(
6 | view: { $0.externalCompletions },
7 | set: { ProjectStatsEnvelope.VideoStats(externalCompletions: $0, externalStarts: $1.externalStarts,
8 | internalCompletions: $1.internalCompletions, internalStarts: $1.internalStarts) }
9 | )
10 |
11 | public static let externalStarts = Lens(
12 | view: { $0.externalStarts },
13 | set: { ProjectStatsEnvelope.VideoStats(externalCompletions: $1.externalCompletions, externalStarts: $0,
14 | internalCompletions: $1.internalCompletions, internalStarts: $1.internalStarts) }
15 | )
16 |
17 | public static let internalCompletions = Lens(
18 | view: { $0.internalCompletions },
19 | set: { ProjectStatsEnvelope.VideoStats(externalCompletions: $1.externalCompletions,
20 | externalStarts: $1.externalStarts, internalCompletions: $0, internalStarts: $1.internalStarts) }
21 | )
22 |
23 | public static let internalStarts = Lens(
24 | view: { $0.internalStarts },
25 | set: { ProjectStatsEnvelope.VideoStats(externalCompletions: $1.externalCompletions,
26 | externalStarts: $1.externalStarts, internalCompletions: $1.internalCompletions, internalStarts: $0) }
27 | )
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/KsApi/lib/auth/BasicHTTPAuth.swift:
--------------------------------------------------------------------------------
1 | /**
2 | A type that understands basic HTTP authentication: username and password.
3 | */
4 | public protocol BasicHTTPAuthType {
5 | var username: String { get }
6 | var password: String { get }
7 | }
8 |
9 | public func == (lhs: BasicHTTPAuthType, rhs: BasicHTTPAuthType) -> Bool {
10 | return type(of: lhs) == type(of: rhs) &&
11 | lhs.username == rhs.username &&
12 | lhs.password == rhs.password
13 | }
14 |
15 | public func == (lhs: BasicHTTPAuthType?, rhs: BasicHTTPAuthType?) -> Bool {
16 | return type(of: lhs) == type(of: rhs) &&
17 | lhs?.username == rhs?.username &&
18 | lhs?.password == rhs?.password
19 | }
20 |
21 | extension BasicHTTPAuthType {
22 | /**
23 | Contents of the `Authorization` header needed to perform basic HTTP auth.
24 | */
25 | var authorizationHeader: String? {
26 | let string = "\(username):\(password)"
27 | if let data = string.data(using: .utf8) {
28 | let base64 = data.base64EncodedString(options: .lineLength64Characters)
29 | return "Basic \(base64)"
30 | }
31 | return nil
32 | }
33 | }
34 |
35 | public struct BasicHTTPAuth: BasicHTTPAuthType {
36 | public let username: String
37 | public let password: String
38 |
39 | public static let development: BasicHTTPAuthType = BasicHTTPAuth(
40 | username: Secrets.BasicHTTPAuth.username,
41 | password: Secrets.BasicHTTPAuth.password
42 | )
43 |
44 | public init(username: String, password: String) {
45 | self.username = username
46 | self.password = password
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/Project.DatesLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension Project.Dates {
4 | public enum lens {
5 | public static let deadline = Lens(
6 | view: { $0.deadline },
7 | set: { Project.Dates(deadline: $0, featuredAt: $1.featuredAt, launchedAt: $1.launchedAt,
8 | potdAt: $1.potdAt, stateChangedAt: $1.stateChangedAt) }
9 | )
10 |
11 | public static let featuredAt = Lens(
12 | view: { $0.featuredAt },
13 | set: { Project.Dates(deadline: $1.deadline, featuredAt: $0, launchedAt: $1.launchedAt,
14 | potdAt: $1.potdAt, stateChangedAt: $1.stateChangedAt) }
15 | )
16 |
17 | public static let launchedAt = Lens(
18 | view: { $0.launchedAt },
19 | set: { Project.Dates(deadline: $1.deadline, featuredAt: $1.featuredAt, launchedAt: $0,
20 | potdAt: $1.potdAt, stateChangedAt: $1.stateChangedAt) }
21 | )
22 |
23 | public static let potdAt = Lens(
24 | view: { $0.potdAt },
25 | set: { Project.Dates(deadline: $1.deadline, featuredAt: $1.featuredAt, launchedAt: $1.launchedAt,
26 | potdAt: $0, stateChangedAt: $1.stateChangedAt) }
27 | )
28 |
29 | public static let stateChangedAt = Lens(
30 | view: { $0.stateChangedAt },
31 | set: { Project.Dates(deadline: $1.deadline, featuredAt: $1.featuredAt, launchedAt: $1.launchedAt,
32 | potdAt: $1.potdAt, stateChangedAt: $0) }
33 | )
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/VideoStatsLenses.swift:
--------------------------------------------------------------------------------
1 | // swiftlint:disable type_name
2 | import Prelude
3 |
4 | extension ProjectStatsEnvelope.VideoStats {
5 | public enum lens {
6 | public static let externalCompletions = Lens(
7 | view: { $0.externalCompletions },
8 | set: { ProjectStatsEnvelope.VideoStats(externalCompletions: $0, externalStarts: $1.externalStarts,
9 | internalCompletions: $1.internalCompletions, internalStarts: $1.internalStarts) }
10 | )
11 |
12 | public static let externalStarts = Lens(
13 | view: { $0.externalStarts },
14 | set: { ProjectStatsEnvelope.VideoStats(externalCompletions: $1.externalCompletions, externalStarts: $0,
15 | internalCompletions: $1.internalCompletions, internalStarts: $1.internalStarts) }
16 | )
17 |
18 | public static let internalCompletions = Lens(
19 | view: { $0.internalCompletions },
20 | set: { ProjectStatsEnvelope.VideoStats(externalCompletions: $1.externalCompletions,
21 | externalStarts: $1.externalStarts, internalCompletions: $0, internalStarts: $1.internalStarts) }
22 | )
23 |
24 | public static let internalStarts = Lens(
25 | view: { $0.internalStarts },
26 | set: { ProjectStatsEnvelope.VideoStats(externalCompletions: $1.externalCompletions,
27 | externalStarts: $1.externalStarts, internalCompletions: $1.internalCompletions, internalStarts: $0) }
28 | )
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Configs/Secrets.swift.example:
--------------------------------------------------------------------------------
1 | public enum Secrets {
2 | public static let isOSS = false
3 | public static let fieldReportEmail = "hello@email.com"
4 |
5 | public enum Api {
6 | public enum Client {
7 | public static let production = "deadbeef"
8 | public static let staging = "beefdead"
9 | }
10 |
11 | public enum Endpoint {
12 | public static let production = "api.com"
13 | public static let staging = "api.staging"
14 | }
15 | }
16 |
17 | public enum BasicHTTPAuth {
18 | public static let username = "usr"
19 | public static let password = "pswd"
20 | }
21 |
22 | public enum Firebase {
23 | public enum Huzza {
24 | public static let apiKey = ""
25 | public static let appName = ""
26 | public static let bundleID = ""
27 | public static let clientID = ""
28 | public static let databaseURL = ""
29 | public static let gcmSenderID = ""
30 | public static let googleAppID = ""
31 | public static let storageBucket = ""
32 | }
33 | }
34 |
35 | public enum HockeyAppId {
36 | public static let beta = "beta"
37 | public static let production = "production"
38 | }
39 |
40 | public enum KoalaEndpoint {
41 | public static let staging = "staging";
42 | public static let production = "production";
43 | }
44 |
45 | public enum LiveStreams {
46 | public static let endpoint = "streams"
47 | }
48 |
49 | public enum WebEndpoint {
50 | public static let production = "www.kickstarter.com"
51 | public static let staging = "staging.com"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/KsApi.playground/Contents.swift:
--------------------------------------------------------------------------------
1 | import XCPlayground
2 | import KsApi
3 | import Prelude
4 | import ReactiveCocoa
5 | import Result
6 |
7 | XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
8 |
9 | let service = Service.init(
10 | serverConfig: ServerConfig.production,
11 | //oauthToken: OauthToken.init(token: "uncomment and put in your token!"),
12 | language: "en"
13 | )
14 |
15 | let categories = service.fetchCategories()
16 | .flatMap(.Concat) { SignalProducer(values: $0) }
17 | .filter { c in c.isRoot }
18 | .replayLazily(1)
19 |
20 | categories
21 | .map { c in c.name }
22 | .startWithNext { c in
23 | print("Root category ----> \(c)")
24 | }
25 |
26 | // Get the most popular project in each category above.
27 | categories
28 | .map {
29 | DiscoveryParams.defaults
30 | |> DiscoveryParams.lens.category .~ $0
31 | <> DiscoveryParams.lens.sort .~ .Popular
32 | <> DiscoveryParams.lens.perPage .~ 1
33 | }
34 | .flatMap(.Merge, transform: service.fetchProject)
35 | .map { p in p.name }
36 | .startWithNext { name in
37 | print("Project -----> \(name)")
38 | }
39 |
40 | // Get a few recent activities and print out the name of the user that created the activity.
41 | // This only works if you uncomment `oauthToken` above and put in a valid token.
42 | service.fetchActivities()
43 | .flatMap(.Concat) { SignalProducer(values: $0.activities) }
44 | .map { $0.user?.name }
45 | .skipNil()
46 | .startWithNext { name in
47 | print("Activity user's name: \(name)")
48 | }
49 |
--------------------------------------------------------------------------------
/KsApi/models/SubmitApplePayEnvelopeTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 |
4 | final class SubmitApplePayEnvelopeTests: XCTestCase {
5 |
6 | func testDecodingWithStringStatus() {
7 | let decoded = SubmitApplePayEnvelope.decodeJSONDictionary(
8 | [
9 | "data": [
10 | "thankyou_url": "https://www.kickstarter.com/thanks"
11 | ],
12 | "status": "200"
13 | ]
14 | )
15 |
16 | XCTAssertNil(decoded.error)
17 | XCTAssertEqual(200, decoded.value?.status)
18 | }
19 |
20 | func testDecodingWithStatus() {
21 | let decoded = SubmitApplePayEnvelope.decodeJSONDictionary(
22 | [
23 | "data": [
24 | "thankyou_url": "https://www.kickstarter.com/thanks"
25 | ],
26 | "status": 200
27 | ]
28 | )
29 |
30 | XCTAssertNil(decoded.error)
31 | XCTAssertEqual(200, decoded.value?.status)
32 | }
33 |
34 | func testDecodingWithMissingStatus() {
35 |
36 | let decoded = SubmitApplePayEnvelope.decodeJSONDictionary(
37 | [
38 | "data": [
39 | "thankyou_url": "https://www.kickstarter.com/thanks"
40 | ]
41 | ]
42 | )
43 |
44 | XCTAssertNotNil(decoded.error)
45 | }
46 |
47 | func testDecodingWithBadStatusData() {
48 | let decoded = SubmitApplePayEnvelope.decodeJSONDictionary(
49 | [
50 | "data": [
51 | "thankyou_url": "bad data"
52 | ],
53 | "status": "bad data"
54 | ]
55 | )
56 |
57 | XCTAssertNil(decoded.error)
58 | XCTAssertEqual(0, decoded.value?.status)
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/KsApi/models/Backing.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct Backing {
6 | public let amount: Int
7 | public let backer: User?
8 | public let backerId: Int
9 | public let id: Int
10 | public let locationId: Int?
11 | public let pledgedAt: TimeInterval
12 | public let projectCountry: String
13 | public let projectId: Int
14 | public let reward: Reward?
15 | public let rewardId: Int?
16 | public let sequence: Int
17 | public let shippingAmount: Int?
18 | public let status: Status
19 |
20 | public enum Status: String {
21 | case canceled
22 | case collected
23 | case dropped
24 | case errored
25 | case pledged
26 | case preauth
27 | }
28 | }
29 |
30 | extension Backing: Equatable {
31 | }
32 | public func == (lhs: Backing, rhs: Backing) -> Bool {
33 | return lhs.id == rhs.id
34 | }
35 |
36 | extension Backing: Decodable {
37 | public static func decode(_ json: JSON) -> Decoded {
38 | let create = curry(Backing.init)
39 | let tmp1 = create
40 | <^> json <| "amount"
41 | <*> json <|? "backer"
42 | <*> json <| "backer_id"
43 | <*> json <| "id"
44 | let tmp2 = tmp1
45 | <*> json <|? "location_id"
46 | <*> json <| "pledged_at"
47 | <*> json <| "project_country"
48 | <*> json <| "project_id"
49 | return tmp2
50 | <*> json <|? "reward"
51 | <*> json <|? "reward_id"
52 | <*> json <| "sequence"
53 | <*> json <|? "shipping_amount"
54 | <*> json <| "status"
55 | }
56 | }
57 |
58 | extension Backing.Status: Decodable {
59 | }
60 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/ProjectStatsEnvelope.CumulativeStatsLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension ProjectStatsEnvelope.CumulativeStats {
4 | public enum lens {
5 | public static let averagePledge = Lens(
6 | view: { $0.averagePledge },
7 | set: { .init(averagePledge: $0, backersCount: $1.backersCount, goal: $1.goal,
8 | percentRaised: $1.percentRaised, pledged: $1.pledged) }
9 | )
10 |
11 | public static let backersCount = Lens(
12 | view: { $0.backersCount },
13 | set: { .init(averagePledge: $1.averagePledge, backersCount: $0, goal: $1.goal,
14 | percentRaised: $1.percentRaised, pledged: $1.pledged) }
15 | )
16 |
17 | public static let goal = Lens(
18 | view: { $0.goal },
19 | set: { .init(averagePledge: $1.averagePledge, backersCount: $1.backersCount, goal: $0,
20 | percentRaised: $1.percentRaised, pledged: $1.pledged) }
21 | )
22 |
23 | public static let percentRaised = Lens(
24 | view: { $0.percentRaised },
25 | set: { .init(averagePledge: $1.averagePledge, backersCount: $1.backersCount, goal: $1.goal,
26 | percentRaised: $0, pledged: $1.pledged) }
27 | )
28 |
29 | public static let pledged = Lens(
30 | view: { $0.pledged },
31 | set: { .init(averagePledge: $1.averagePledge, backersCount: $1.backersCount, goal: $1.goal,
32 | percentRaised: $1.percentRaised, pledged: $0) }
33 | )
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/KsApi/models/DiscoveryEnvelope.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct DiscoveryEnvelope {
6 | public let projects: [Project]
7 | public let urls: UrlsEnvelope
8 | public let stats: StatsEnvelope
9 |
10 | public struct UrlsEnvelope {
11 | public let api: ApiEnvelope
12 |
13 | public struct ApiEnvelope {
14 | public let moreProjects: String
15 |
16 | public init(more_projects: String) {
17 | moreProjects = more_projects
18 | }
19 | }
20 | }
21 |
22 | public struct StatsEnvelope {
23 | public let count: Int
24 | }
25 | }
26 |
27 | extension DiscoveryEnvelope: Decodable {
28 | public static func decode(_ json: JSON) -> Decoded {
29 | return curry(DiscoveryEnvelope.init)
30 | <^> json <|| "projects"
31 | <*> json <| "urls"
32 | <*> json <| "stats"
33 | }
34 | }
35 |
36 | extension DiscoveryEnvelope.UrlsEnvelope: Decodable {
37 | public static func decode(_ json: JSON) -> Decoded {
38 | return curry(DiscoveryEnvelope.UrlsEnvelope.init)
39 | <^> json <| "api"
40 | }
41 | }
42 |
43 | extension DiscoveryEnvelope.UrlsEnvelope.ApiEnvelope: Decodable {
44 | public static func decode(_ json: JSON) -> Decoded {
45 | return curry(DiscoveryEnvelope.UrlsEnvelope.ApiEnvelope.init)
46 | <^> json <| "more_projects"
47 | }
48 | }
49 |
50 | extension DiscoveryEnvelope.StatsEnvelope: Decodable {
51 | public static func decode(_ json: JSON) -> Decoded {
52 | return curry(DiscoveryEnvelope.StatsEnvelope.init)
53 | <^> json <| "count"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata
19 |
20 | ## Other
21 | *.xccheckout
22 | *.moved-aside
23 | *.xcuserstate
24 | *.xcscmblueprint
25 |
26 | ## Obj-C/Swift specific
27 | *.hmap
28 | *.ipa
29 |
30 | ## Playgrounds
31 | timeline.xctimeline
32 | playground.xcworkspace
33 |
34 | # Swift Package Manager
35 | #
36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
37 | # Packages/
38 | .build/
39 |
40 | # CocoaPods
41 | #
42 | # We recommend against adding the Pods directory to your .gitignore. However
43 | # you should judge for yourself, the pros and cons are mentioned at:
44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
45 | #
46 | # Pods/
47 |
48 | # Carthage
49 | #
50 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
51 | # Carthage/Checkouts
52 |
53 | Carthage/Build
54 | Carthage/Cartfile.resolved
55 |
56 | # fastlane
57 | #
58 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
59 | # screenshots whenever they are needed.
60 | # For more information about the recommended setup visit:
61 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md
62 |
63 | fastlane/report.xml
64 | fastlane/screenshots
65 |
66 | .DS_Store
67 |
68 | Frameworks/native-secrets
69 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/MessageThreadLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension MessageThread {
4 | public enum lens {
5 | public static let id = Lens(
6 | view: { $0.id },
7 | set: { MessageThread(backing: $1.backing, closed: $1.closed, id: 0, lastMessage: $1.lastMessage,
8 | participant: $1.participant, project: $1.project, unreadMessagesCount: $1.unreadMessagesCount) }
9 | )
10 |
11 | public static let participant = Lens(
12 | view: { $0.participant },
13 | set: { MessageThread(backing: $1.backing, closed: $1.closed, id: $0.id, lastMessage: $1.lastMessage,
14 | participant: $0, project: $1.project, unreadMessagesCount: $1.unreadMessagesCount) }
15 | )
16 |
17 | public static let project = Lens(
18 | view: { $0.project },
19 | set: { MessageThread(backing: $1.backing, closed: $1.closed, id: $0.id, lastMessage: $1.lastMessage,
20 | participant: $1.participant, project: $0, unreadMessagesCount: $1.unreadMessagesCount) }
21 | )
22 |
23 | public static let lastMessage = Lens(
24 | view: { $0.lastMessage },
25 | set: { .init(backing: $1.backing, closed: $1.closed, id: $0.id, lastMessage: $0,
26 | participant: $1.participant, project: $1.project,
27 | unreadMessagesCount: $1.unreadMessagesCount) }
28 | )
29 |
30 | public static let unreadMessagesCount = Lens(
31 | view: { $0.unreadMessagesCount },
32 | set: { .init(backing: $1.backing, closed: $1.closed, id: $1.id, lastMessage: $1.lastMessage,
33 | participant: $1.participant, project: $1.project, unreadMessagesCount: $0) }
34 | )
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/KsApi/models/Project.PhotoTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 |
4 | final class ProjectPhotoTests: XCTestCase {
5 |
6 | func testJSONParsing_WithPartialData() {
7 | let photo = Project.Photo.decodeJSONDictionary([
8 | "full": "http://www.kickstarter.com/full.jpg",
9 | "med": "http://www.kickstarter.com/med.jpg",
10 | ])
11 |
12 | XCTAssertNotNil(photo.error)
13 | }
14 |
15 | func testJSONParsing_WithMissing1024() {
16 | let photo = Project.Photo.decodeJSONDictionary([
17 | "full": "http://www.kickstarter.com/full.jpg",
18 | "med": "http://www.kickstarter.com/med.jpg",
19 | "small": "http://www.kickstarter.com/small.jpg",
20 | ])
21 |
22 | XCTAssertNil(photo.error)
23 | XCTAssertEqual(photo.value?.full, "http://www.kickstarter.com/full.jpg")
24 | XCTAssertEqual(photo.value?.med, "http://www.kickstarter.com/med.jpg")
25 | XCTAssertEqual(photo.value?.small, "http://www.kickstarter.com/small.jpg")
26 | XCTAssertNil(photo.value?.size1024x768)
27 | }
28 |
29 | func testJSONParsing_WithFullData() {
30 | let photo = Project.Photo.decodeJSONDictionary([
31 | "full": "http://www.kickstarter.com/full.jpg",
32 | "med": "http://www.kickstarter.com/med.jpg",
33 | "small": "http://www.kickstarter.com/small.jpg",
34 | "1024x768": "http://www.kickstarter.com/1024x768.jpg",
35 | ])
36 |
37 | XCTAssertNil(photo.error)
38 | XCTAssertEqual(photo.value?.full, "http://www.kickstarter.com/full.jpg")
39 | XCTAssertEqual(photo.value?.med, "http://www.kickstarter.com/med.jpg")
40 | XCTAssertEqual(photo.value?.small, "http://www.kickstarter.com/small.jpg")
41 | XCTAssertEqual(photo.value?.size1024x768, "http://www.kickstarter.com/1024x768.jpg")
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/ProjectStatsEnvelope.FundingDateStatsLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension ProjectStatsEnvelope.FundingDateStats {
4 | public enum lens {
5 | public static let backersCount = Lens(
6 | view: { $0.backersCount },
7 | set: { .init(backersCount: $0, cumulativePledged: $1.cumulativePledged,
8 | cumulativeBackersCount: $1.cumulativeBackersCount, date: $1.date, pledged: $1.pledged) }
9 | )
10 |
11 | public static let cumulativePledged = Lens(
12 | view: { $0.cumulativePledged },
13 | set: { .init(backersCount: $1.backersCount, cumulativePledged: $0,
14 | cumulativeBackersCount: $1.cumulativeBackersCount, date: $1.date, pledged: $1.pledged) }
15 | )
16 |
17 | public static let cumulativeBackersCount = Lens(
18 | view: { $0.cumulativeBackersCount },
19 | set: { .init(backersCount: $1.backersCount, cumulativePledged: $1.cumulativePledged,
20 | cumulativeBackersCount: $0, date: $1.date, pledged: $1.pledged) }
21 | )
22 |
23 | public static let date = Lens(
24 | view: { $0.date },
25 | set: { .init(backersCount: $1.backersCount, cumulativePledged: $1.cumulativePledged,
26 | cumulativeBackersCount: $1.cumulativeBackersCount, date: $0, pledged: $1.pledged) }
27 | )
28 |
29 | public static let pledged = Lens(
30 | view: { $0.pledged },
31 | set: { .init(backersCount: $1.backersCount, cumulativePledged: $1.cumulativePledged,
32 | cumulativeBackersCount: $1.cumulativeBackersCount, date: $1.date, pledged: $0) }
33 | )
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/User.StatsLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension User.Stats {
4 | public enum lens {
5 | public static let backedProjectsCount = Lens(
6 | view: { $0.backedProjectsCount },
7 | set: { User.Stats(backedProjectsCount: $0, createdProjectsCount: $1.createdProjectsCount,
8 | memberProjectsCount: $1.memberProjectsCount, starredProjectsCount: $1.starredProjectsCount,
9 | unansweredSurveysCount: $1.unansweredSurveysCount, unreadMessagesCount: $1.unreadMessagesCount) }
10 | )
11 |
12 | public static let createdProjectsCount = Lens(
13 | view: { $0.createdProjectsCount },
14 | set: { User.Stats(backedProjectsCount: $1.backedProjectsCount, createdProjectsCount: $0,
15 | memberProjectsCount: $1.memberProjectsCount, starredProjectsCount: $1.starredProjectsCount,
16 | unansweredSurveysCount: $1.unansweredSurveysCount, unreadMessagesCount: $1.unreadMessagesCount) }
17 | )
18 |
19 | public static let memberProjectsCount = Lens(
20 | view: { $0.memberProjectsCount },
21 | set: { User.Stats(backedProjectsCount: $1.backedProjectsCount,
22 | createdProjectsCount: $1.createdProjectsCount, memberProjectsCount: $0,
23 | starredProjectsCount: $1.starredProjectsCount, unansweredSurveysCount: $1.unansweredSurveysCount,
24 | unreadMessagesCount: $1.unreadMessagesCount) }
25 | )
26 |
27 | public static let starredProjectsCount = Lens(
28 | view: { $0.starredProjectsCount },
29 | set: { User.Stats(backedProjectsCount: $1.backedProjectsCount,
30 | createdProjectsCount: $1.createdProjectsCount, memberProjectsCount: $1.memberProjectsCount,
31 | starredProjectsCount: $0, unansweredSurveysCount: $1.unansweredSurveysCount,
32 | unreadMessagesCount: $1.unreadMessagesCount) }
33 | )
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/KsApi/models/User.NewsletterSubscriptionsTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 |
4 | final class NewsletterSubscriptionsTests: XCTestCase {
5 |
6 | func testJsonEncoding() {
7 | let json: [String:Any] = [
8 | "games_newsletter": false,
9 | "promo_newsletter": false,
10 | "happening_newsletter": false,
11 | "weekly_newsletter": false
12 | ]
13 |
14 | let newsletter = User.NewsletterSubscriptions.decodeJSONDictionary(json)
15 |
16 | XCTAssertEqual(newsletter.value?.encode().description, json.description)
17 |
18 | XCTAssertEqual(false, newsletter.value?.weekly)
19 | XCTAssertEqual(false, newsletter.value?.promo)
20 | XCTAssertEqual(false, newsletter.value?.happening)
21 | XCTAssertEqual(false, newsletter.value?.games)
22 | }
23 |
24 | func testJsonEncoding_TrueValues() {
25 | let json: [String:Any] = [
26 | "games_newsletter": true,
27 | "promo_newsletter": true,
28 | "happening_newsletter": true,
29 | "weekly_newsletter": true
30 | ]
31 |
32 | let newsletter = User.NewsletterSubscriptions.decodeJSONDictionary(json)
33 |
34 | XCTAssertEqual(newsletter.value?.encode().description, json.description)
35 |
36 | XCTAssertEqual(true, newsletter.value?.weekly)
37 | XCTAssertEqual(true, newsletter.value?.promo)
38 | XCTAssertEqual(true, newsletter.value?.happening)
39 | XCTAssertEqual(true, newsletter.value?.games)
40 | }
41 |
42 | func testJsonDecoding() {
43 | let json = User.NewsletterSubscriptions.decodeJSONDictionary([
44 | "games_newsletter": true,
45 | "happening_newsletter": false,
46 | "promo_newsletter": true,
47 | "weekly_newsletter": false
48 | ])
49 |
50 | let newsletters = json.value
51 |
52 | XCTAssertEqual(newsletters,
53 | User.NewsletterSubscriptions.decodeJSONDictionary(newsletters?.encode() ?? [:]).value)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/KsApi/models/templates/CategoryTemplates.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension KsApi.Category {
4 | internal static let template = Category(
5 | color: nil,
6 | id: 1,
7 | name: "Art",
8 | parent: nil,
9 | parentId: nil,
10 | position: 1,
11 | projectsCount: 450,
12 | slug: "art"
13 | )
14 |
15 | internal static let art = template
16 | |> Category.lens.id .~ 1
17 | <> Category.lens.name .~ "Art"
18 | <> Category.lens.slug .~ "art"
19 | <> Category.lens.position .~ 1
20 |
21 | internal static let filmAndVideo = template
22 | |> Category.lens.id .~ 11
23 | <> Category.lens.name .~ "Film & Video"
24 | <> Category.lens.slug .~ "film-and-video"
25 | <> Category.lens.position .~ 7
26 |
27 | internal static let games = template
28 | |> Category.lens.id .~ 12
29 | <> Category.lens.name .~ "Games"
30 | <> Category.lens.slug .~ "games"
31 | <> Category.lens.position .~ 9
32 |
33 | internal static let illustration = template
34 | |> Category.lens.id .~ 22
35 | <> Category.lens.name .~ "Illustration"
36 | <> Category.lens.slug .~ "art/illustration"
37 | <> Category.lens.position .~ 4
38 | <> Category.lens.parentId .~ Category.art.id
39 | <> Category.lens.parent .~ Category.art
40 |
41 | internal static let documentary = template
42 | |> Category.lens.id .~ 30
43 | <> Category.lens.name .~ "Documentary"
44 | <> Category.lens.slug .~ "film-and-video/documentary"
45 | <> Category.lens.position .~ 4
46 | <> Category.lens.parentId .~ Category.filmAndVideo.id
47 | <> Category.lens.parent .~ Category.filmAndVideo
48 |
49 | internal static let tabletopGames = template
50 | |> Category.lens.id .~ 34
51 | <> Category.lens.name .~ "Tabletop Games"
52 | <> Category.lens.slug .~ "games/tabletop-games"
53 | <> Category.lens.position .~ 9
54 | <> Category.lens.parentId .~ Category.games.id
55 | <> Category.lens.parent .~ Category.games
56 | }
57 |
--------------------------------------------------------------------------------
/KsApi/models/Update.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Argo
3 | import Curry
4 | import Runes
5 |
6 | public struct Update {
7 | public let body: String?
8 | public let commentsCount: Int?
9 | public let hasLiked: Bool?
10 | public let id: Int
11 | public let isPublic: Bool
12 | public let likesCount: Int?
13 | public let projectId: Int
14 | public let publishedAt: TimeInterval?
15 | public let sequence: Int
16 | public let title: String
17 | public let urls: UrlsEnvelope
18 | public let user: User?
19 | public let visible: Bool?
20 |
21 | public struct UrlsEnvelope {
22 | public let web: WebEnvelope
23 |
24 | public struct WebEnvelope {
25 | public let update: String
26 | }
27 | }
28 | }
29 |
30 | extension Update: Equatable {
31 | }
32 | public func == (lhs: Update, rhs: Update) -> Bool {
33 | return lhs.id == rhs.id
34 | }
35 |
36 | extension Update: Decodable {
37 |
38 | public static func decode(_ json: JSON) -> Decoded {
39 | let create = curry(Update.init)
40 | let tmp1 = create
41 | <^> json <|? "body"
42 | <*> json <|? "comments_count"
43 | <*> json <|? "has_liked"
44 | let tmp2 = tmp1
45 | <*> json <| "id"
46 | <*> json <| "public"
47 | <*> json <|? "likes_count"
48 | let tmp3 = tmp2
49 | <*> json <| "project_id"
50 | <*> json <|? "published_at"
51 | <*> json <| "sequence"
52 | <*> (json <| "title" <|> .success(""))
53 | return tmp3
54 | <*> json <| "urls"
55 | <*> json <|? "user"
56 | <*> json <|? "visible"
57 | }
58 | }
59 |
60 | extension Update.UrlsEnvelope: Decodable {
61 | static public func decode(_ json: JSON) -> Decoded {
62 | return curry(Update.UrlsEnvelope.init)
63 | <^> json <| "web"
64 | }
65 | }
66 |
67 | extension Update.UrlsEnvelope.WebEnvelope: Decodable {
68 | static public func decode(_ json: JSON) -> Decoded {
69 | return curry(Update.UrlsEnvelope.WebEnvelope.init)
70 | <^> json <| "update"
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/KsApi/models/UpdateTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 | import Argo
4 | import Prelude
5 |
6 | final internal class UpdateTests: XCTestCase {
7 |
8 | func testEquatable() {
9 | XCTAssertEqual(Update.template, Update.template)
10 | XCTAssertNotEqual(Update.template, Update.template |> Update.lens.id %~ { $0 + 1 })
11 | }
12 |
13 | func testJSONDecoding_WithBadData() {
14 | let update = Update.decodeJSONDictionary([
15 | "body": "world",
16 | ])
17 |
18 | XCTAssertNotNil(update.error)
19 | }
20 |
21 | func testJSONDecoding_WithGoodData() {
22 | let update = Update.decodeJSONDictionary([
23 | "body": "world",
24 | "id": 1,
25 | "public": true,
26 | "project_id": 2,
27 | "sequence": 3,
28 | "title": "hello",
29 | "visible": true,
30 | "urls": [
31 | "web": [
32 | "update": "https://www.kickstarter.com/projects/udoo/udoo-x86/posts/1571540"
33 | ]
34 | ]
35 | ])
36 |
37 | XCTAssertNil(update.error)
38 | XCTAssertEqual(1, update.value?.id)
39 | }
40 |
41 | func testJSONDecoding_WithNestedGoodData() {
42 | let update = Update.decodeJSONDictionary([
43 | "body": "world",
44 | "id": 1,
45 | "public": true,
46 | "project_id": 2,
47 | "sequence": 3,
48 | "title": "hello",
49 | "user": [
50 | "id": 2,
51 | "name": "User",
52 | "avatar": [
53 | "medium": "img.jpg",
54 | "small": "img.jpg",
55 | "large": "img.jpg",
56 | ]
57 | ],
58 | "visible": true,
59 | "urls": [
60 | "web": [
61 | "update": "https://www.kickstarter.com/projects/udoo/udoo-x86/posts/1571540"
62 | ]
63 | ]
64 | ])
65 |
66 | XCTAssertNil(update.error)
67 | XCTAssertEqual(1, update.value?.id)
68 | XCTAssertEqual(2, update.value?.user?.id)
69 | XCTAssertEqual("https://www.kickstarter.com/projects/udoo/udoo-x86/posts/1571540",
70 | update.value?.urls.web.update)
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/KsApi/models/ConfigTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 | import Argo
4 | import Curry
5 | import Runes
6 |
7 | final class ConfigTests: XCTestCase {
8 |
9 | func testDecoding() {
10 | let abExperiments = [
11 | "2001_space_odyssey": "control",
12 | "dr_strangelove": "experiment"
13 | ]
14 | let features = [
15 | "feature1": true,
16 | "feature2": false,
17 | ]
18 | let json: [String:Any] = [
19 | "ab_experiments": abExperiments,
20 | "app_id": 123456789,
21 | "apple_pay_countries": ["US", "GB", "CA"],
22 | "country_code": "US",
23 | "features": features,
24 | "itunes_link": "http://www.itunes.com",
25 | "launched_countries": [
26 | [ "trailing_code": false,
27 | "currency_symbol": "€",
28 | "currency_code": "EUR",
29 | "name": "ES" ],
30 | [
31 | "trailing_code": false,
32 | "currency_symbol": "€",
33 | "currency_code": "EUR",
34 | "name": "FR" ]
35 | ],
36 | "locale": "en",
37 | "stripe": [
38 | "publishable_key": "pk"
39 | ]
40 | ]
41 |
42 | // Confirm json decoded successfully
43 | let decodedConfig = Config.decodeJSONDictionary(json)
44 | XCTAssertNil(decodedConfig.error)
45 |
46 | // Confirm fields decoded properly
47 | let config = decodedConfig.value!
48 | XCTAssertEqual(abExperiments, config.abExperiments)
49 | XCTAssertEqual(123456789, config.appId)
50 | XCTAssertEqual("US", config.countryCode)
51 | XCTAssertEqual(["US", "GB", "CA"], config.applePayCountries)
52 | XCTAssertEqual(features, config.features)
53 | XCTAssertEqual("http://www.itunes.com", config.iTunesLink)
54 | XCTAssertEqual([.ES, .FR], config.launchedCountries)
55 | XCTAssertEqual("en", config.locale)
56 | XCTAssertEqual("pk", config.stripePublishableKey)
57 |
58 | // Confirm that encoding and decoding again results in the same config.
59 | XCTAssertEqual(config, Config.decodeJSONDictionary(config.encode()).value)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/KsApi/ServerConfig.swift:
--------------------------------------------------------------------------------
1 | /**
2 | A type that knows the location of a Kickstarter API and web server.
3 | */
4 | public protocol ServerConfigType {
5 | var apiBaseUrl: URL { get }
6 | var webBaseUrl: URL { get }
7 | var apiClientAuth: ClientAuthType { get }
8 | var basicHTTPAuth: BasicHTTPAuthType? { get }
9 | }
10 |
11 | public func == (lhs: ServerConfigType, rhs: ServerConfigType) -> Bool {
12 | return
13 | type(of: lhs) == type(of: rhs) &&
14 | lhs.apiBaseUrl == rhs.apiBaseUrl &&
15 | lhs.webBaseUrl == rhs.webBaseUrl &&
16 | lhs.apiClientAuth == rhs.apiClientAuth &&
17 | lhs.basicHTTPAuth == rhs.basicHTTPAuth
18 | }
19 |
20 | public struct ServerConfig: ServerConfigType {
21 | public let apiBaseUrl: URL
22 | public let webBaseUrl: URL
23 | public let apiClientAuth: ClientAuthType
24 | public let basicHTTPAuth: BasicHTTPAuthType?
25 |
26 | public static let production: ServerConfigType = ServerConfig(
27 | apiBaseUrl: URL(string: "https://\(Secrets.Api.Endpoint.production)")!,
28 | webBaseUrl: URL(string: "https://\(Secrets.WebEndpoint.production)")!,
29 | apiClientAuth: ClientAuth.production,
30 | basicHTTPAuth: nil
31 | )
32 |
33 | public static let staging: ServerConfigType = ServerConfig(
34 | apiBaseUrl: URL(string: "https://\(Secrets.Api.Endpoint.staging)")!,
35 | webBaseUrl: URL(string: "https://\(Secrets.WebEndpoint.staging)")!,
36 | apiClientAuth: ClientAuth.development,
37 | basicHTTPAuth: BasicHTTPAuth.development
38 | )
39 |
40 | public static let local: ServerConfigType = ServerConfig(
41 | apiBaseUrl: URL(string: "http://api.ksr.dev")!,
42 | webBaseUrl: URL(string: "http://ksr.dev")!,
43 | apiClientAuth: ClientAuth.development,
44 | basicHTTPAuth: BasicHTTPAuth.development
45 | )
46 |
47 | public init(apiBaseUrl: URL,
48 | webBaseUrl: URL,
49 | apiClientAuth: ClientAuthType,
50 | basicHTTPAuth: BasicHTTPAuthType?) {
51 |
52 | self.apiBaseUrl = apiBaseUrl
53 | self.webBaseUrl = webBaseUrl
54 | self.apiClientAuth = apiClientAuth
55 | self.basicHTTPAuth = basicHTTPAuth
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/CategoryLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension Category {
4 | public enum lens {
5 | public static let id = Lens(
6 | view: { $0.id },
7 | set: { Category(color: $1.color, id: $0, name: $1.name, parent: $1.parent, parentId: $1.parentId,
8 | position: $1.position, projectsCount: $1.projectsCount, slug: $1.slug) }
9 | )
10 |
11 | public static let name = Lens(
12 | view: { $0.name },
13 | set: { Category(color: $1.color, id: $1.id, name: $0, parent: $1.parent, parentId: $1.parentId,
14 | position: $1.position, projectsCount: $1.projectsCount, slug: $1.slug) }
15 | )
16 |
17 | public static let parent = Lens(
18 | view: { $0.parent },
19 | set: { Category(color: $1.color, id: $1.id, name: $1.name, parent: $0, parentId: $1.parentId,
20 | position: $1.position, projectsCount: $1.projectsCount, slug: $1.slug) }
21 | )
22 |
23 | public static let parentId = Lens(
24 | view: { $0.parentId },
25 | set: { Category(color: $1.color, id: $1.id, name: $1.name, parent: $1.parent, parentId: $0,
26 | position: $1.position, projectsCount: $1.projectsCount, slug: $1.slug) }
27 | )
28 |
29 | public static let position = Lens(
30 | view: { $0.position },
31 | set: { Category(color: $1.color, id: $1.id, name: $1.name, parent: $1.parent,
32 | parentId: $1.parentId, position: $0, projectsCount: $1.projectsCount, slug: $1.slug) }
33 | )
34 |
35 | public static let projectsCount = Lens(
36 | view: { $0.projectsCount },
37 | set: { Category(color: $1.color, id: $1.id, name: $1.name, parent: $1.parent,
38 | parentId: $1.parentId, position: $1.position, projectsCount: $0, slug: $1.slug) }
39 | )
40 |
41 | public static let slug = Lens(
42 | view: { $0.slug },
43 | set: { Category(color: $1.color, id: $1.id, name: $1.name, parent: $1.parent,
44 | parentId: $1.parentId, position: $1.position, projectsCount: $1.projectsCount, slug: $0) }
45 | )
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/ProjectStatsEnvelopeLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension ProjectStatsEnvelope {
4 | public enum lens {
5 | public static let cumulativeStats = Lens(
6 | view: { $0.cumulativeStats },
7 | set: { ProjectStatsEnvelope(cumulativeStats: $0, fundingDistribution: $1.fundingDistribution,
8 | referralDistribution: $1.referralDistribution, rewardDistribution: $1.rewardDistribution,
9 | videoStats: $1.videoStats) }
10 | )
11 |
12 | public static let fundingDistribution =
13 | Lens(
14 | view: { $0.fundingDistribution },
15 | set: { ProjectStatsEnvelope(cumulativeStats: $1.cumulativeStats, fundingDistribution: $0,
16 | referralDistribution: $1.referralDistribution, rewardDistribution: $1.rewardDistribution,
17 | videoStats: $1.videoStats) }
18 | )
19 |
20 | public static let referralDistribution =
21 | Lens(
22 | view: { $0.referralDistribution },
23 | set: { ProjectStatsEnvelope(cumulativeStats: $1.cumulativeStats,
24 | fundingDistribution: $1.fundingDistribution, referralDistribution: $0,
25 | rewardDistribution: $1.rewardDistribution, videoStats: $1.videoStats) }
26 | )
27 |
28 | public static let rewardDistribution = Lens(
29 | view: { $0.rewardDistribution },
30 | set: { ProjectStatsEnvelope(cumulativeStats: $1.cumulativeStats,
31 | fundingDistribution: $1.fundingDistribution, referralDistribution: $1.referralDistribution,
32 | rewardDistribution: $0, videoStats: $1.videoStats) }
33 | )
34 |
35 | public static let videoStats = Lens(
36 | view: { $0.videoStats },
37 | set: { ProjectStatsEnvelope(cumulativeStats: $1.cumulativeStats,
38 | fundingDistribution: $1.fundingDistribution, referralDistribution: $1.referralDistribution,
39 | rewardDistribution: $1.rewardDistribution, videoStats: $0) }
40 | )
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/KsApi/models/Param.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 |
3 | /// Represents a way to paramterize a model by either an `id` integer or `slug` string.
4 | public enum Param {
5 | case id(Int)
6 | case slug(String)
7 |
8 | /// Returns the `id` of the param if it is of type `.id`.
9 | public var id: Int? {
10 | if case let .id(id) = self {
11 | return id
12 | }
13 | return nil
14 | }
15 |
16 | /// Returns the `slug` of the param if it is of type `.slug`.
17 | public var slug: String? {
18 | if case let .slug(slug) = self {
19 | return slug
20 | }
21 | return nil
22 | }
23 |
24 | /// Returns a value suitable for interpolating into a URL.
25 | public var urlComponent: String {
26 | switch self {
27 | case let .id(id):
28 | return String(id)
29 | case let .slug(slug):
30 | return slug
31 | }
32 | }
33 |
34 | public var escapedUrlComponent: String {
35 | switch self {
36 | case let .id(id):
37 | return String(id)
38 | case let .slug(slug):
39 | return encodeForRFC3986(slug) ?? ""
40 | }
41 | }
42 | }
43 |
44 | extension Param: Equatable {}
45 | public func == (lhs: Param, rhs: Param) -> Bool {
46 |
47 | switch (lhs, rhs) {
48 | case let (.id(lhs), .id(rhs)):
49 | return lhs == rhs
50 | case let (.slug(lhs), .slug(rhs)):
51 | return lhs == rhs
52 | case let (.id(lhs), .slug(rhs)):
53 | return String(lhs) == rhs
54 | case let (.slug(lhs), .id(rhs)):
55 | return lhs == String(rhs)
56 | }
57 | }
58 |
59 | extension Param: Decodable {
60 | public static func decode(_ json: JSON) -> Decoded {
61 | switch json {
62 | case let .string(slug):
63 | return .success(.slug(slug))
64 | case let .number(number):
65 | return .success(.id(number.intValue))
66 | default:
67 | return .failure(.custom("Param must be a number or string."))
68 | }
69 | }
70 | }
71 |
72 | private let allowableRFC3986: CharacterSet = {
73 | var set = CharacterSet.alphanumerics
74 | set.insert(charactersIn: "-._~/?")
75 | return set
76 | }()
77 |
78 | private func encodeForRFC3986(_ str: String) -> String? {
79 | return str.addingPercentEncoding(withAllowedCharacters: allowableRFC3986)
80 | }
81 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/DiscoveryEnvelopeLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension DiscoveryEnvelope {
4 | public enum lens {
5 | public static let projects = Lens(
6 | view: { $0.projects },
7 | set: { DiscoveryEnvelope(projects: $0, urls: $1.urls, stats: $1.stats) }
8 | )
9 | public static let urls = Lens(
10 | view: { $0.urls },
11 | set: { DiscoveryEnvelope(projects: $1.projects, urls: $0, stats: $1.stats) }
12 | )
13 | public static let stats = Lens(
14 | view: { $0.stats },
15 | set: { DiscoveryEnvelope(projects: $1.projects, urls: $1.urls, stats: $0) }
16 | )
17 | }
18 | }
19 |
20 | extension DiscoveryEnvelope.UrlsEnvelope {
21 | public enum lens {
22 | public static let api = Lens(
23 | view: { $0.api },
24 | set: { part, _ in DiscoveryEnvelope.UrlsEnvelope(api: part) }
25 | )
26 | }
27 | }
28 |
29 | extension DiscoveryEnvelope.UrlsEnvelope.ApiEnvelope {
30 | public enum lens {
31 | public static let moreProjects = Lens(
32 | view: { $0.moreProjects },
33 | set: { part, _ in DiscoveryEnvelope.UrlsEnvelope.ApiEnvelope(more_projects: part) }
34 | )
35 | }
36 | }
37 |
38 | extension DiscoveryEnvelope.StatsEnvelope {
39 | public enum lens {
40 | public static let count = Lens(
41 | view: { $0.count },
42 | set: { part, _ in DiscoveryEnvelope.StatsEnvelope(count: part) }
43 | )
44 | }
45 | }
46 |
47 | extension Lens where Whole == DiscoveryEnvelope, Part == DiscoveryEnvelope.UrlsEnvelope {
48 | public var api: Lens {
49 | return DiscoveryEnvelope.lens.urls..DiscoveryEnvelope.UrlsEnvelope.lens.api
50 | }
51 | }
52 |
53 | extension Lens where Whole == DiscoveryEnvelope, Part == DiscoveryEnvelope.UrlsEnvelope.ApiEnvelope {
54 | public var moreProjects: Lens {
55 | return DiscoveryEnvelope.lens.urls.api..DiscoveryEnvelope.UrlsEnvelope.ApiEnvelope.lens.moreProjects
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/Project.StatsLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension Project.Stats {
4 | public enum lens {
5 | public static let backersCount = Lens(
6 | view: { $0.backersCount },
7 | set: { .init(backersCount: $0, commentsCount: $1.commentsCount, goal: $1.goal,
8 | pledged: $1.pledged, staticUsdRate: $1.staticUsdRate, updatesCount: $1.updatesCount) }
9 | )
10 |
11 | public static let commentsCount = Lens(
12 | view: { $0.commentsCount },
13 | set: { .init(backersCount: $1.backersCount, commentsCount: $0, goal: $1.goal,
14 | pledged: $1.pledged, staticUsdRate: $1.staticUsdRate, updatesCount: $1.updatesCount) }
15 | )
16 |
17 | public static let goal = Lens(
18 | view: { $0.goal },
19 | set: { .init(backersCount: $1.backersCount, commentsCount: $1.commentsCount, goal: $0,
20 | pledged: $1.pledged, staticUsdRate: $1.staticUsdRate, updatesCount: $1.updatesCount) }
21 | )
22 |
23 | public static let pledged = Lens(
24 | view: { $0.pledged },
25 | set: { .init(backersCount: $1.backersCount, commentsCount: $1.commentsCount, goal: $1.goal,
26 | pledged: $0, staticUsdRate: $1.staticUsdRate, updatesCount: $1.updatesCount) }
27 | )
28 |
29 | public static let staticUsdRate = Lens(
30 | view: { $0.staticUsdRate },
31 | set: { .init(backersCount: $1.backersCount, commentsCount: $1.commentsCount, goal: $1.goal,
32 | pledged: $1.pledged, staticUsdRate: $0, updatesCount: $1.updatesCount) }
33 | )
34 |
35 | public static let updatesCount = Lens(
36 | view: { $0.updatesCount },
37 | set: { .init(backersCount: $1.backersCount, commentsCount: $1.commentsCount, goal: $1.goal,
38 | pledged: $1.pledged, staticUsdRate: $1.staticUsdRate, updatesCount: $0) }
39 | )
40 |
41 | public static let fundingProgress = Lens(
42 | view: { $0.fundingProgress },
43 | set: { .init(backersCount: $1.backersCount, commentsCount: $1.commentsCount, goal: $1.goal,
44 | pledged: Int($0 * Float($1.goal)), staticUsdRate: $1.staticUsdRate, updatesCount: $1.updatesCount) }
45 | )
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/KsApi/models/UpdateDraft.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct UpdateDraft {
6 | public let update: Update
7 | public let images: [Image]
8 | public let video: Video?
9 |
10 | public enum Attachment {
11 | case image(Image)
12 | case video(Video)
13 | }
14 |
15 | public struct Image {
16 | public let id: Int
17 | public let thumb: String
18 | public let full: String
19 | }
20 |
21 | public struct Video {
22 | public let id: Int
23 | public let status: Status
24 | public let frame: String
25 |
26 | public enum Status: String {
27 | case processing
28 | case failed
29 | case successful
30 | }
31 | }
32 | }
33 |
34 | extension UpdateDraft: Equatable {}
35 | public func == (lhs: UpdateDraft, rhs: UpdateDraft) -> Bool {
36 | return lhs.update.id == rhs.update.id
37 | }
38 |
39 | extension UpdateDraft.Attachment {
40 | public var id: Int {
41 | switch self {
42 | case let .image(image):
43 | return image.id
44 | case let .video(video):
45 | return video.id
46 | }
47 | }
48 |
49 | public var thumbUrl: String {
50 | switch self {
51 | case let .image(image):
52 | return image.full
53 | case let .video(video):
54 | return video.frame
55 | }
56 | }
57 | }
58 |
59 | extension UpdateDraft.Attachment: Equatable {}
60 | public func == (lhs: UpdateDraft.Attachment, rhs: UpdateDraft.Attachment) -> Bool {
61 | return lhs.id == rhs.id
62 | }
63 |
64 | extension UpdateDraft: Decodable {
65 | public static func decode(_ json: JSON) -> Decoded {
66 | return curry(UpdateDraft.init)
67 | <^> Update.decode(json)
68 | <*> json <|| "images"
69 | <*> json <|? "video"
70 | }
71 | }
72 |
73 | extension UpdateDraft.Image: Decodable {
74 | public static func decode(_ json: JSON) -> Decoded {
75 | return curry(UpdateDraft.Image.init)
76 | <^> json <| "id"
77 | <*> json <| "thumb"
78 | <*> json <| "full"
79 | }
80 | }
81 |
82 | extension UpdateDraft.Video: Decodable {
83 | public static func decode(_ json: JSON) -> Decoded {
84 | return curry(UpdateDraft.Video.init)
85 | <^> json <| "id"
86 | <*> json <| "status"
87 | <*> json <| "frame"
88 | }
89 | }
90 |
91 | extension UpdateDraft.Video.Status: Decodable {
92 | }
93 |
--------------------------------------------------------------------------------
/KsApi/models/templates/LocationTemplates.swift:
--------------------------------------------------------------------------------
1 | extension Location {
2 | internal static let template = Location(country: "US",
3 | displayableName: "Brooklyn, NY",
4 | id: 42,
5 | name: "Brooklyn")
6 |
7 | internal static let brooklyn = Location(country: "US",
8 | displayableName: "Brooklyn, NY",
9 | id: 1,
10 | name: "Brooklyn")
11 |
12 | internal static let losAngeles = Location(country: "US",
13 | displayableName: "Los Angeles, CA",
14 | id: 2,
15 | name: "Los Angeles")
16 |
17 | internal static let portland = Location(country: "US",
18 | displayableName: "Portland, OR",
19 | id: 3,
20 | name: "Portland")
21 |
22 | internal static let london = Location(country: "GB",
23 | displayableName: "London, GB",
24 | id: 4,
25 | name: "London")
26 |
27 | internal static let usa = Location(country: "US",
28 | displayableName: "United States",
29 | id: 5,
30 | name: "United States")
31 |
32 | internal static let canada = Location(country: "CA",
33 | displayableName: "Canada",
34 | id: 6,
35 | name: "Canada")
36 |
37 | internal static let greatBritain = Location(country: "GB",
38 | displayableName: "Great Britain",
39 | id: 7,
40 | name: "Great Britain")
41 |
42 | internal static let australia = Location(country: "AU",
43 | displayableName: "Australia",
44 | id: 8,
45 | name: "Australia")
46 | }
47 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/ProjectStatsEnvelope.ReferrerStatsLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension ProjectStatsEnvelope.ReferrerStats {
4 | public enum lens {
5 | public static let backersCount = Lens(
6 | view: { $0.backersCount },
7 | set: { ProjectStatsEnvelope.ReferrerStats(backersCount: $0, code: $1.code,
8 | percentageOfDollars: $1.percentageOfDollars, pledged: $1.pledged, referrerName: $1.referrerName,
9 | referrerType: $1.referrerType) }
10 | )
11 |
12 | public static let code = Lens(
13 | view: { $0.code },
14 | set: { ProjectStatsEnvelope.ReferrerStats(backersCount: $1.backersCount, code: $0,
15 | percentageOfDollars: $1.percentageOfDollars, pledged: $1.pledged, referrerName: $1.referrerName,
16 | referrerType: $1.referrerType) }
17 | )
18 |
19 | public static let percentageOfDollars = Lens(
20 | view: { $0.percentageOfDollars },
21 | set: { ProjectStatsEnvelope.ReferrerStats(backersCount: $1.backersCount, code: $1.code,
22 | percentageOfDollars: $0, pledged: $1.pledged, referrerName: $1.referrerName,
23 | referrerType: $1.referrerType) }
24 | )
25 |
26 | public static let pledged = Lens(
27 | view: { $0.pledged },
28 | set: { ProjectStatsEnvelope.ReferrerStats(backersCount: $1.backersCount, code: $1.code,
29 | percentageOfDollars: $1.percentageOfDollars, pledged: $0, referrerName: $1.referrerName,
30 | referrerType: $1.referrerType) }
31 | )
32 |
33 | public static let referrerName = Lens(
34 | view: { $0.referrerName },
35 | set: { ProjectStatsEnvelope.ReferrerStats(backersCount: $1.backersCount, code: $1.code,
36 | percentageOfDollars: $1.percentageOfDollars, pledged: $1.pledged, referrerName: $0,
37 | referrerType: $1.referrerType) }
38 | )
39 |
40 | public static let referrerType =
41 | Lens(
42 | view: { $0.referrerType },
43 | set: { ProjectStatsEnvelope.ReferrerStats(backersCount: $1.backersCount, code: $1.code,
44 | percentageOfDollars: $1.percentageOfDollars, pledged: $1.pledged, referrerName: $1.referrerName,
45 | referrerType: $0) }
46 | )
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/KsApi/models/Reward.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 | import Prelude
5 |
6 | public struct Reward {
7 | public let backersCount: Int?
8 | public let description: String
9 | public let endsAt: TimeInterval?
10 | public let estimatedDeliveryOn: TimeInterval?
11 | public let id: Int
12 | public let limit: Int?
13 | public let minimum: Int
14 | public let remaining: Int?
15 | public let rewardsItems: [RewardsItem]
16 | public let shipping: Shipping
17 | public let startsAt: TimeInterval?
18 | public let title: String?
19 |
20 | /// Returns `true` is this is the "fake" "No reward" reward.
21 | public var isNoReward: Bool {
22 | return self.id == Reward.noReward.id
23 | }
24 |
25 | public struct Shipping {
26 | public let enabled: Bool
27 | public let preference: Preference?
28 | public let summary: String?
29 |
30 | public enum Preference: String {
31 | case none
32 | case restricted
33 | case unrestricted
34 | }
35 | }
36 | }
37 |
38 | extension Reward: Equatable {}
39 | public func == (lhs: Reward, rhs: Reward) -> Bool {
40 | return lhs.id == rhs.id
41 | }
42 |
43 | private let minimumAndIdComparator = Reward.lens.minimum.comparator <> Reward.lens.id.comparator
44 |
45 | extension Reward: Comparable {}
46 | public func < (lhs: Reward, rhs: Reward) -> Bool {
47 | return minimumAndIdComparator.isOrdered(lhs, rhs)
48 | }
49 |
50 | extension Reward: Decodable {
51 | public static func decode(_ json: JSON) -> Decoded {
52 | let create = curry(Reward.init)
53 | let tmp1 = create
54 | <^> json <|? "backers_count"
55 | <*> (json <| "description" <|> json <| "reward")
56 | <*> json <|? "ends_at"
57 | <*> json <|? "estimated_delivery_on"
58 | let tmp2 = tmp1
59 | <*> json <| "id"
60 | <*> json <|? "limit"
61 | <*> json <| "minimum"
62 | <*> json <|? "remaining"
63 | return tmp2
64 | <*> ((json <|| "rewards_items") <|> .success([]))
65 | <*> Reward.Shipping.decode(json)
66 | <*> json <|? "starts_at"
67 | <*> json <|? "title"
68 | }
69 | }
70 |
71 | extension Reward.Shipping: Decodable {
72 | public static func decode(_ json: JSON) -> Decoded {
73 | return curry(Reward.Shipping.init)
74 | <^> (json <| "shipping_enabled" <|> .success(false))
75 | <*> json <|? "shipping_preference"
76 | <*> json <|? "shipping_summary"
77 | }
78 | }
79 |
80 | extension Reward.Shipping.Preference: Decodable {}
81 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/Activity.MemberDataLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension Activity.MemberData {
4 | public enum lens {
5 | public static let amount = Lens(
6 | view: { $0.amount },
7 | set: { Activity.MemberData(amount: $0, backing: $1.backing, oldAmount: $1.oldAmount,
8 | oldRewardId: $1.oldRewardId, newAmount: $1.newAmount, newRewardId: $1.newRewardId,
9 | rewardId: $1.rewardId) }
10 | )
11 |
12 | public static let backing = Lens(
13 | view: { $0.backing },
14 | set: { Activity.MemberData(amount: $1.amount, backing: $0, oldAmount: $1.oldAmount,
15 | oldRewardId: $1.oldRewardId, newAmount: $1.newAmount, newRewardId: $1.newRewardId,
16 | rewardId: $1.rewardId) }
17 | )
18 |
19 | public static let oldAmount = Lens(
20 | view: { $0.oldAmount },
21 | set: { Activity.MemberData(amount: $1.amount, backing: $1.backing, oldAmount: $0,
22 | oldRewardId: $1.oldRewardId, newAmount: $1.newAmount, newRewardId: $1.newRewardId,
23 | rewardId: $1.rewardId) }
24 | )
25 |
26 | public static let oldRewardId = Lens(
27 | view: { $0.oldRewardId },
28 | set: { Activity.MemberData(amount: $1.amount, backing: $1.backing, oldAmount: $1.oldAmount,
29 | oldRewardId: $0, newAmount: $1.newAmount, newRewardId: $1.newRewardId,
30 | rewardId: $1.rewardId) }
31 | )
32 |
33 | public static let newAmount = Lens(
34 | view: { $0.newAmount },
35 | set: { Activity.MemberData(amount: $1.amount, backing: $1.backing, oldAmount: $1.oldAmount,
36 | oldRewardId: $1.oldRewardId, newAmount: $0, newRewardId: $1.newRewardId,
37 | rewardId: $1.rewardId) }
38 | )
39 |
40 | public static let newRewardId = Lens(
41 | view: { $0.newRewardId },
42 | set: { Activity.MemberData(amount: $1.amount, backing: $1.backing, oldAmount: $1.oldAmount,
43 | oldRewardId: $1.oldRewardId, newAmount: $1.newAmount, newRewardId: $0,
44 | rewardId: $1.rewardId) }
45 | )
46 |
47 | public static let rewardId = Lens(
48 | view: { $0.rewardId },
49 | set: { Activity.MemberData(amount: $1.amount, backing: $1.backing, oldAmount: $1.oldAmount,
50 | oldRewardId: $1.oldRewardId, newAmount: $1.newRewardId, newRewardId: $1.newRewardId,
51 | rewardId: $0) }
52 | )
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | Copyright (C) 2016 Kickstarter, PBC
2 |
3 | # Argo
4 |
5 | Functional JSON parsing library for Swift https://thoughtbot.com/open-source
6 |
7 | https://github.com/thoughtbot/Argo
8 |
9 | Argo is Copyright (c) 2015 thoughtbot, inc.
10 |
11 | Copyright (c) 2014 thoughtbot, inc.
12 |
13 | MIT License
14 |
15 | Permission is hereby granted, free of charge, to any person obtaining
16 | a copy of this software and associated documentation files (the
17 | "Software"), to deal in the Software without restriction, including
18 | without limitation the rights to use, copy, modify, merge, publish,
19 | distribute, sublicense, and/or sell copies of the Software, and to
20 | permit persons to whom the Software is furnished to do so, subject to
21 | the following conditions:
22 |
23 | The above copyright notice and this permission notice shall be
24 | included in all copies or substantial portions of the Software.
25 |
26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 |
34 | # ReactiveCocoa
35 |
36 | Streams of values over time
37 |
38 | https://github.com/ReactiveCocoa/ReactiveCocoa
39 |
40 | **Copyright (c) 2012 - 2016, GitHub, Inc.**
41 | **All rights reserved.**
42 |
43 | Permission is hereby granted, free of charge, to any person obtaining a copy of
44 | this software and associated documentation files (the "Software"), to deal in
45 | the Software without restriction, including without limitation the rights to
46 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
47 | the Software, and to permit persons to whom the Software is furnished to do so,
48 | subject to the following conditions:
49 |
50 | The above copyright notice and this permission notice shall be included in all
51 | copies or substantial portions of the Software.
52 |
53 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
54 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
55 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
56 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
57 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
58 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
59 |
--------------------------------------------------------------------------------
/KsApi/models/ActivityTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 | import Argo
4 |
5 | final internal class ActivityTests: XCTestCase {
6 |
7 | func testEquatable() {
8 | XCTAssertEqual(Activity.template, Activity.template)
9 | }
10 |
11 | func testJSONDecoding_WithBadData() {
12 | let activity = Activity.decodeJSONDictionary([
13 | "category": "update"
14 | ])
15 |
16 | XCTAssertNotNil(activity.error)
17 | }
18 |
19 | func testJSONDecoding_WithGoodData() {
20 | let activity = Activity.decodeJSONDictionary([
21 | "category": "update",
22 | "created_at": 123123123,
23 | "id": 1,
24 | ])
25 |
26 | XCTAssertNil(activity.error)
27 | XCTAssertEqual(activity.value?.id, 1)
28 | }
29 |
30 | func testJSONParsing_WithMemberData() {
31 | let memberData = Activity.MemberData.decodeJSONDictionary([
32 | "amount": 25.0,
33 | "backing": [
34 | "amount": 1.0,
35 | "backer_id": 1,
36 | "id": 1,
37 | "location_id": 1,
38 | "pledged_at": 1000,
39 | "project_country": "US",
40 | "project_id": 1,
41 | "sequence": 1,
42 | "status": "pledged"
43 | ],
44 | "old_amount": 15.0,
45 | "old_reward_id": 1,
46 | "new_amount": 25.0,
47 | "new_reward_id": 2,
48 | "reward_id": 2
49 | ])
50 |
51 | XCTAssertNil(memberData.error)
52 | XCTAssertEqual(25, memberData.value?.amount)
53 | XCTAssertEqual(1, memberData.value?.backing?.id)
54 | XCTAssertEqual(15, memberData.value?.oldAmount)
55 | XCTAssertEqual(1, memberData.value?.oldRewardId)
56 | XCTAssertEqual(25, memberData.value?.newAmount)
57 | XCTAssertEqual(2, memberData.value?.newRewardId)
58 | XCTAssertEqual(2, memberData.value?.rewardId)
59 | }
60 |
61 | func testJSONDecoding_WithNestedGoodData() {
62 | let activity = Activity.decodeJSONDictionary([
63 | "category": "update",
64 | "created_at": 123123123,
65 | "id": 1,
66 | "user": [
67 | "id": 2,
68 | "name": "User",
69 | "avatar": [
70 | "medium": "img.jpg",
71 | "small": "img.jpg",
72 | "large": "img.jpg",
73 | ]
74 | ]
75 | ])
76 |
77 | XCTAssertNil(activity.error)
78 | XCTAssertEqual(activity.value?.id, 1)
79 | XCTAssertEqual(activity.value?.user?.id, 2)
80 | }
81 |
82 | func testJSONDecoding_WithIncorrectCategory() {
83 | let activity = Activity.decodeJSONDictionary([
84 | "category": "incorrect_category",
85 | "created_at": 123123123,
86 | "id": 1,
87 | ])
88 |
89 | XCTAssertNil(activity.error)
90 | XCTAssertEqual(.some(.unknown), activity.value?.category)
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/KsApi/models/FindFriendsEnvelopeTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 |
4 | final class FindFriendsEnvelopeTests: XCTestCase {
5 | func testJsonDecoding() {
6 | let json: [String:Any] = [
7 | "contacts_imported": true,
8 | "urls": [
9 | "api": [
10 | "more_users": "http://api.dev/v1/users/self/friends/find?count=10"
11 | ]
12 | ],
13 | "users": [
14 | [
15 | "id": 1,
16 | "name": "Blob",
17 | "avatar": [
18 | "medium": "http://www.kickstarter.com/medium.jpg",
19 | "small": "http://www.kickstarter.com/small.jpg"
20 | ],
21 | "backed_projects_count": 2,
22 | "weekly_newsletter": false,
23 | "promo_newsletter": false,
24 | "happening_newsletter": false,
25 | "games_newsletter": false,
26 | "facebook_connected": false,
27 | "location": [
28 | "id": 12,
29 | "displayable_name": "Brooklyn, NY",
30 | "name": "Brooklyn"
31 | ],
32 | "is_friend": false
33 | ],
34 | [
35 | "id": 2,
36 | "name": "Blab",
37 | "avatar": [
38 | "medium": "http://www.kickstarter.com/medium.jpg",
39 | "small": "http://www.kickstarter.com/small.jpg"
40 | ],
41 | "backed_projects_count": 2,
42 | "weekly_newsletter": false,
43 | "promo_newsletter": false,
44 | "happening_newsletter": false,
45 | "games_newsletter": false,
46 | "facebook_connected": false,
47 | "location": [
48 | "id": 12,
49 | "displayable_name": "Brooklyn, NY",
50 | "name": "Brooklyn"
51 | ],
52 | "is_friend": true
53 | ]
54 | ]
55 | ]
56 |
57 | let friends = FindFriendsEnvelope.decodeJSONDictionary(json)
58 | let users = friends.value?.users ?? []
59 |
60 | XCTAssertEqual(true, friends.value?.contactsImported)
61 | XCTAssertEqual("http://api.dev/v1/users/self/friends/find?count=10",
62 | friends.value?.urls.api.moreUsers)
63 | XCTAssertEqual(false, users[0].isFriend)
64 | XCTAssertEqual(true, users[1].isFriend)
65 | }
66 | // swiftlint:enable function_body_length
67 |
68 | func testJsonDecoding_MissingData() {
69 | let json: [String:Any] = [
70 | "contacts_imported": true,
71 | "urls": [
72 | "api": [
73 | ]
74 | ],
75 | "users": [
76 | ]
77 | ]
78 |
79 | let friends = FindFriendsEnvelope.decodeJSONDictionary(json)
80 |
81 | XCTAssertNil(friends.value?.urls.api.moreUsers)
82 | XCTAssertEqual([], friends.value?.users ?? [])
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/KsApi/models/Config.swift:
--------------------------------------------------------------------------------
1 | import Argo
2 | import Curry
3 | import Runes
4 |
5 | public struct Config {
6 | public let abExperiments: [String:String]
7 | public let appId: Int
8 | public let applePayCountries: [String]
9 | public let countryCode: String
10 | public let features: [String:Bool]
11 | public let iTunesLink: String
12 | public let launchedCountries: [Project.Country]
13 | public let locale: String
14 | public let stripePublishableKey: String
15 | }
16 |
17 | extension Config: Decodable {
18 | public static func decode(_ json: JSON) -> Decoded {
19 | let create = curry(Config.init)
20 | let tmp = create
21 | <^> decodeDictionary(json <| "ab_experiments")
22 | <*> json <| "app_id"
23 | <*> json <|| "apple_pay_countries"
24 | <*> json <| "country_code"
25 | <*> decodeDictionary(json <| "features")
26 | return tmp
27 | <*> json <| "itunes_link"
28 | <*> json <|| "launched_countries"
29 | <*> json <| "locale"
30 | <*> json <| ["stripe", "publishable_key"]
31 | }
32 | }
33 |
34 | extension Config: Equatable {
35 | }
36 | public func == (lhs: Config, rhs: Config) -> Bool {
37 | return lhs.abExperiments == rhs.abExperiments &&
38 | lhs.appId == rhs.appId &&
39 | lhs.applePayCountries == rhs.applePayCountries &&
40 | lhs.countryCode == rhs.countryCode &&
41 | lhs.features == rhs.features &&
42 | lhs.iTunesLink == rhs.iTunesLink &&
43 | lhs.launchedCountries == rhs.launchedCountries &&
44 | lhs.locale == rhs.locale &&
45 | lhs.stripePublishableKey == rhs.stripePublishableKey
46 | }
47 |
48 | extension Config: EncodableType {
49 | public func encode() -> [String:Any] {
50 | var result: [String:Any] = [:]
51 | result["ab_experiments"] = self.abExperiments
52 | result["app_id"] = self.appId
53 | result["apple_pay_countries"] = self.applePayCountries
54 | result["country_code"] = self.countryCode
55 | result["features"] = self.features
56 | result["itunes_link"] = self.iTunesLink
57 | result["launched_countries"] = self.launchedCountries.map { $0.encode() }
58 | result["locale"] = self.locale
59 | result["stripe"] = ["publishable_key": self.stripePublishableKey]
60 | return result
61 | }
62 | }
63 |
64 | // Useful for getting around swift optimization bug: https://github.com/thoughtbot/Argo/issues/363
65 | // Turns out using `>>-` or `flatMap` on a `Decoded` fails to compile with optimizations on, so this
66 | // function does it manually.
67 | private func decodeDictionary(_ j: Decoded)
68 | -> Decoded<[String:T]> where T.DecodedType == T {
69 | switch j {
70 | case let .success(json): return [String: T].decode(json)
71 | case let .failure(e): return .failure(e)
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/UpdateDraftLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension UpdateDraft {
4 | public enum lens {
5 | public static let update = Lens(
6 | view: { $0.update },
7 | set: { UpdateDraft(update: $0, images: $1.images, video: $1.video) }
8 | )
9 |
10 | public static let images = Lens(
11 | view: { $0.images },
12 | set: { UpdateDraft(update: $1.update, images: $0, video: $1.video) }
13 | )
14 |
15 | public static let video = Lens(
16 | view: { $0.video },
17 | set: { UpdateDraft(update: $1.update, images: $1.images, video: $0) }
18 | )
19 | }
20 | }
21 |
22 | extension UpdateDraft.Image {
23 | public enum lens {
24 | public static let id = Lens(
25 | view: { $0.id },
26 | set: { UpdateDraft.Image(id: $0, thumb: $1.thumb, full: $1.full) }
27 | )
28 |
29 | public static let thumb = Lens(
30 | view: { $0.thumb },
31 | set: { UpdateDraft.Image(id: $1.id, thumb: $0, full: $1.full) }
32 | )
33 |
34 | public static let full = Lens(
35 | view: { $0.thumb },
36 | set: { UpdateDraft.Image(id: $1.id, thumb: $1.thumb, full: $0) }
37 | )
38 | }
39 | }
40 |
41 | extension UpdateDraft.Video {
42 | public enum lens {
43 | public static let id = Lens(
44 | view: { $0.id },
45 | set: { UpdateDraft.Video(id: $0, status: $1.status, frame: $1.frame) }
46 | )
47 |
48 | public static let status = Lens(
49 | view: { $0.status },
50 | set: { UpdateDraft.Video(id: $1.id, status: $0, frame: $1.frame) }
51 | )
52 |
53 | public static let frame = Lens(
54 | view: { $0.frame },
55 | set: { UpdateDraft.Video(id: $1.id, status: $1.status, frame: $0) }
56 | )
57 | }
58 | }
59 |
60 | extension Lens where Whole == UpdateDraft, Part == Update {
61 | public var id: Lens {
62 | return UpdateDraft.lens.update..Update.lens.id
63 | }
64 |
65 | public var projectId: Lens {
66 | return UpdateDraft.lens.update..Update.lens.projectId
67 | }
68 |
69 | public var title: Lens {
70 | return UpdateDraft.lens.update..Update.lens.title
71 | }
72 |
73 | public var body: Lens {
74 | return UpdateDraft.lens.update..Update.lens.body
75 | }
76 |
77 | public var isPublic: Lens {
78 | return UpdateDraft.lens.update..Update.lens.isPublic
79 | }
80 |
81 | public var sequence: Lens {
82 | return UpdateDraft.lens.update..Update.lens.sequence
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/KsApi/models/lenses/ConfigLenses.swift:
--------------------------------------------------------------------------------
1 | import Prelude
2 |
3 | extension Config {
4 | public enum lens {
5 | public static let applePayCountries = Lens(
6 | view: { $0.applePayCountries },
7 | set: { Config(abExperiments: $1.abExperiments, appId: $1.appId, applePayCountries: $0,
8 | countryCode: $1.countryCode, features: $1.features, iTunesLink: $1.iTunesLink,
9 | launchedCountries: $1.launchedCountries, locale: $1.locale,
10 | stripePublishableKey: $1.stripePublishableKey) }
11 | )
12 |
13 | public static let countryCode = Lens(
14 | view: { $0.countryCode },
15 | set: { Config(abExperiments: $1.abExperiments, appId: $1.appId, applePayCountries: $1.applePayCountries,
16 | countryCode: $0, features: $1.features, iTunesLink: $1.iTunesLink,
17 | launchedCountries: $1.launchedCountries, locale: $1.locale,
18 | stripePublishableKey: $1.stripePublishableKey) }
19 | )
20 |
21 | public static let features = Lens(
22 | view: { $0.features },
23 | set: { Config(abExperiments: $1.abExperiments, appId: $1.appId, applePayCountries: $1.applePayCountries,
24 | countryCode: $1.countryCode, features: $0, iTunesLink: $1.iTunesLink,
25 | launchedCountries: $1.launchedCountries, locale: $1.locale,
26 | stripePublishableKey: $1.stripePublishableKey) }
27 | )
28 |
29 | public static let launchedCountries = Lens(
30 | view: { $0.launchedCountries },
31 | set: { Config(abExperiments: $1.abExperiments, appId: $1.appId, applePayCountries: $1.applePayCountries,
32 | countryCode: $1.countryCode, features: $1.features, iTunesLink: $1.iTunesLink, launchedCountries: $0,
33 | locale: $1.locale, stripePublishableKey: $1.stripePublishableKey) }
34 | )
35 |
36 | public static let locale = Lens(
37 | view: { $0.locale },
38 | set: { Config(abExperiments: $1.abExperiments, appId: $1.appId, applePayCountries: $1.applePayCountries,
39 | countryCode: $1.countryCode, features: $1.features, iTunesLink: $1.iTunesLink,
40 | launchedCountries: $1.launchedCountries, locale: $0, stripePublishableKey: $1.stripePublishableKey) }
41 | )
42 |
43 | public static let stripePublishableKey = Lens(
44 | view: { $0.stripePublishableKey },
45 | set: { Config(abExperiments: $1.abExperiments, appId: $1.appId, applePayCountries: $1.applePayCountries,
46 | countryCode: $1.countryCode, features: $1.features, iTunesLink: $1.iTunesLink,
47 | launchedCountries: $1.launchedCountries, locale: $1.locale, stripePublishableKey: $0) }
48 | )
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/KsApi/models/UserTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 | import Prelude
4 |
5 | // swiftlint:disable force_cast
6 | final class UserTests: XCTestCase {
7 |
8 | func testEquatable() {
9 | XCTAssertEqual(User.template, User.template)
10 | XCTAssertNotEqual(User.template, User.template |> User.lens.id %~ { $0 + 1 })
11 | }
12 |
13 | func testDescription() {
14 | XCTAssertNotEqual("", User.template.debugDescription)
15 | }
16 |
17 | func testJsonParsing() {
18 | let json: [String:Any] = [
19 | "id": 1,
20 | "name": "Blob",
21 | "avatar": [
22 | "medium": "http://www.kickstarter.com/medium.jpg",
23 | "small": "http://www.kickstarter.com/small.jpg"
24 | ],
25 | "backed_projects_count": 2,
26 | "weekly_newsletter": false,
27 | "promo_newsletter": false,
28 | "happening_newsletter": false,
29 | "games_newsletter": false,
30 | "facebook_connected": false,
31 | "ksr_live_token": "token",
32 | "location": [
33 | "country": "US",
34 | "id": 12,
35 | "displayable_name": "Brooklyn, NY",
36 | "name": "Brooklyn"
37 | ],
38 | "is_friend": false
39 | ]
40 | let decoded = User.decodeJSONDictionary(json)
41 | let user = decoded.value
42 |
43 | XCTAssertNil(decoded.error)
44 | XCTAssertEqual(1, user?.id)
45 | XCTAssertEqual("http://www.kickstarter.com/small.jpg", user?.avatar.small)
46 | XCTAssertEqual(2, user?.stats.backedProjectsCount)
47 | XCTAssertEqual(false, user?.newsletters.weekly)
48 | XCTAssertEqual(false, user?.newsletters.promo)
49 | XCTAssertEqual(false, user?.newsletters.happening)
50 | XCTAssertEqual(false, user?.newsletters.games)
51 | XCTAssertEqual(false, user?.facebookConnected)
52 | XCTAssertEqual(false, user?.isFriend)
53 | XCTAssertEqual("token", user?.liveAuthToken)
54 | XCTAssertNotNil(user?.location)
55 | XCTAssertEqual(json as NSDictionary?, user?.encode() as NSDictionary?)
56 | }
57 |
58 | func testJsonEncoding() {
59 | let json: [String:Any] = [
60 | "id": 1,
61 | "name": "Blob",
62 | "avatar": [
63 | "medium": "http://www.kickstarter.com/medium.jpg",
64 | "small": "http://www.kickstarter.com/small.jpg",
65 | "large": "http://www.kickstarter.com/large.jpg"
66 | ],
67 | "backed_projects_count": 2,
68 | "games_newsletter": false,
69 | "happening_newsletter": false,
70 | "promo_newsletter": false,
71 | "weekly_newsletter": false,
72 | "facebook_connected": false,
73 | "ksr_live_token": "token",
74 | "location": [
75 | "country": "US",
76 | "id": 12,
77 | "displayable_name": "Brooklyn, NY",
78 | "name": "Brooklyn"
79 | ],
80 | "is_friend": false
81 | ]
82 | let user = User.decodeJSONDictionary(json)
83 |
84 | XCTAssertEqual(user.value?.encode() as NSDictionary?, json as NSDictionary?)
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/KsApi/models/MessageThreadTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import KsApi
3 | import Argo
4 |
5 | internal final class MessageThreadTests: XCTestCase {
6 | func testDecoding() {
7 | let result = MessageThread.decodeJSONDictionary([
8 | "closed": false,
9 | "id": 1,
10 | "last_message": [
11 | "body": "Hello!",
12 | "created_at": 123456789.0,
13 | "id": 1,
14 | "recipient": [
15 | "id": 1,
16 | "name": "Blob",
17 | "avatar": [
18 | "medium": "img",
19 | "small": "img"
20 | ],
21 | ],
22 | "sender": [
23 | "id": 2,
24 | "name": "Clob",
25 | "avatar": [
26 | "medium": "img",
27 | "small": "img"
28 | ],
29 | ]
30 | ],
31 | "unread_messages_count": 1,
32 | "participant": [
33 | "id": 1,
34 | "name": "Blob",
35 | "avatar": [
36 | "medium": "img",
37 | "small": "img"
38 | ],
39 | ],
40 | "project": [
41 | "id": 1,
42 | "name": "Project",
43 | "blurb": "The project blurb",
44 | "pledged": 1_000,
45 | "goal": 2_000,
46 | "category": [
47 | "id": 1,
48 | "name": "Art",
49 | "slug": "art",
50 | "position": 1
51 | ],
52 | "creator": [
53 | "id": 1,
54 | "name": "Blob",
55 | "avatar": [
56 | "medium": "http://www.kickstarter.com/medium.jpg",
57 | "small": "http://www.kickstarter.com/small.jpg"
58 | ]
59 | ],
60 | "photo": [
61 | "full": "http://www.kickstarter.com/full.jpg",
62 | "med": "http://www.kickstarter.com/med.jpg",
63 | "small": "http://www.kickstarter.com/small.jpg",
64 | "1024x768": "http://www.kickstarter.com/1024x768.jpg",
65 | ],
66 | "location": [
67 | "country": "US",
68 | "id": 1,
69 | "displayable_name": "Brooklyn, NY",
70 | "name": "Brooklyn"
71 | ],
72 | "video": [
73 | "id": 1,
74 | "high": "kickstarter.com/video.mp4"
75 | ],
76 | "backers_count": 10,
77 | "currency_symbol": "$",
78 | "currency": "USD",
79 | "currency_trailing_code": false,
80 | "country": "US",
81 | "launched_at": 1000,
82 | "deadline": 1000,
83 | "state_changed_at": 1000,
84 | "static_usd_rate": 1.0,
85 | "slug": "project",
86 | "urls": [
87 | "web": [
88 | "project": "https://www.kickstarter.com/projects/my-cool-projects"
89 | ]
90 | ],
91 | "state": "live"
92 | ]
93 | ])
94 |
95 | XCTAssertNil(result.error)
96 | XCTAssertNotNil(result.value)
97 | }
98 | }
99 | // swiftlint:enable function_body_length
100 |
--------------------------------------------------------------------------------
/KsApi/models/Activity.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Argo
3 | import Curry
4 | import Runes
5 |
6 | public struct Activity {
7 | public let category: Activity.Category
8 | public let comment: Comment?
9 | public let createdAt: TimeInterval
10 | public let id: Int
11 | public let memberData: MemberData
12 | public let project: Project?
13 | public let update: Update?
14 | public let user: User?
15 |
16 | public enum Category: String {
17 | case backing = "backing"
18 | case backingAmount = "backing-amount"
19 | case backingCanceled = "backing-canceled"
20 | case backingDropped = "backing-dropped"
21 | case backingReward = "backing-reward"
22 | case cancellation = "cancellation"
23 | case commentPost = "comment-post"
24 | case commentProject = "comment-project"
25 | case failure = "failure"
26 | case follow = "follow"
27 | case funding = "funding"
28 | case launch = "launch"
29 | case success = "success"
30 | case suspension = "suspension"
31 | case update = "update"
32 | case watch = "watch"
33 | case unknown = "unknown"
34 | }
35 |
36 | public struct MemberData {
37 | public let amount: Int?
38 | public let backing: Backing?
39 | public let oldAmount: Int?
40 | public let oldRewardId: Int?
41 | public let newAmount: Int?
42 | public let newRewardId: Int?
43 | public let rewardId: Int?
44 | }
45 | }
46 |
47 | extension Activity: Equatable {
48 | }
49 | public func == (lhs: Activity, rhs: Activity) -> Bool {
50 | return lhs.id == rhs.id
51 | }
52 |
53 | extension Activity: Decodable {
54 | public static func decode(_ json: JSON) -> Decoded {
55 | let create = curry(Activity.init)
56 | let tmp = create
57 | <^> json <| "category"
58 | <*> json <|? "comment"
59 | <*> json <| "created_at"
60 | <*> json <| "id"
61 | return tmp
62 | <*> Activity.MemberData.decode(json)
63 | <*> json <|? "project"
64 | <*> json <|? "update"
65 | <*> json <|? "user"
66 | }
67 | }
68 |
69 | extension Activity.Category: Decodable {
70 | public static func decode(_ json: JSON) -> Decoded {
71 | switch json {
72 | case let .string(category):
73 | return .success(Activity.Category(rawValue: category) ?? .unknown)
74 | default:
75 | return .failure(.typeMismatch(expected: "String", actual: json.description))
76 | }
77 | }
78 | }
79 |
80 | extension Activity.MemberData: Decodable {
81 | public static func decode(_ json: JSON) -> Decoded