gitbay/Discovery/FeedEvent.swift
84 lines · 3019 bytes
1import Foundation
2
3/// One `feed` event. `data` varies by kind; unknown kinds and fields
4/// must render, not sink the feed.
5nonisolated struct FeedEvent: Decodable, Sendable, Hashable, Identifiable {
6 let id: Int64
7 let repo: String
8 let actor: String?
9 let kind: String
10 let data: Payload?
11 let createdAt: Date
12
13 enum CodingKeys: String, CodingKey {
14 case id, repo, actor, kind, data
15 case createdAt = "created_at"
16 }
17
18 nonisolated struct Payload: Decodable, Sendable, Hashable {
19 let number: Int64?
20 let sha: String?
21 let tag: String?
22 let title: String?
23 }
24
25 /// "opened !4", "build #56 succeeded" — the verb phrase after the actor.
26 var phrase: String {
27 let n = data?.number.map(String.init) ?? ""
28 switch kind {
29 case "mr.created": return "opened !\(n)"
30 case "mr.merged": return "merged !\(n)"
31 case "mr.closed": return "closed !\(n)"
32 case "issue.created": return "opened #\(n)"
33 case "issue.closed": return "closed #\(n)"
34 case "issue.open": return "reopened #\(n)"
35 case "issue.commented": return "commented on #\(n)"
36 case "build.success": return "build #\(n) succeeded"
37 case "build.failure": return "build #\(n) failed"
38 case "release.created": return "released \(data?.tag ?? "")"
39 case "push": return "pushed"
40 default: return kind
41 }
42 }
43
44 var icon: (name: String, isPositive: Bool?) {
45 switch kind {
46 case "mr.created": ("arrow.triangle.merge", nil)
47 case "mr.merged": ("arrow.triangle.merge", true)
48 case "mr.closed": ("xmark.circle", false)
49 case "issue.created", "issue.open": ("smallcircle.filled.circle", nil)
50 case "issue.closed": ("checkmark.circle", true)
51 case "issue.commented": ("bubble.left", nil)
52 case "build.success": ("checkmark.circle.fill", true)
53 case "build.failure": ("xmark.circle.fill", false)
54 case "release.created": ("shippingbox", nil)
55 case "push": ("arrow.up.circle", nil)
56 default: ("circle", nil)
57 }
58 }
59
60 /// Where tapping the row goes, when the event names something the app
61 /// has a screen for.
62 var destination: FeedDestination? {
63 if kind == "release.created", let tag = data?.tag {
64 return .release(repo: repo, tag: tag)
65 }
66 guard let number = data?.number else {
67 return .repo(repo)
68 }
69 switch kind.split(separator: ".").first {
70 case "mr": return .mr(repo: repo, number: number)
71 case "issue": return .issue(repo: repo, number: number)
72 case "build": return .build(repo: repo, number: number)
73 default: return .repo(repo)
74 }
75 }
76}
77
78nonisolated enum FeedDestination: Hashable {
79 case repo(String)
80 case mr(repo: String, number: Int64)
81 case issue(repo: String, number: Int64)
82 case build(repo: String, number: Int64)
83 case release(repo: String, tag: String)
84}