Skip to content

Commit 756ce17

Browse files
authored
Merge pull request #24 from cybozu/add-fetchRecordComments
Add fetchRecordComments API
2 parents 8a8ddf0 + 51d6b22 commit 756ce17

18 files changed

+358
-20
lines changed

Example/Example/ContentView.swift

+12
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,15 @@ enum TabCategory {
7272
}
7373
}
7474

75+
func fetchRecordComments(recordID: Int) async -> RecordComments? {
76+
do {
77+
return try await kintoneAPI.fetchRecordComments(appID: appID, recordID: recordID)
78+
} catch {
79+
print(error.localizedDescription)
80+
return nil
81+
}
82+
}
83+
7584
func updateStatus(recordIdentity: RecordIdentity.Write, action: StatusAction) async {
7685
do {
7786
let assignee = statusSettings?.states.first(where: { $0.name == action.to })?.assignee.entities.first?.code
@@ -224,6 +233,9 @@ enum TabCategory {
224233
},
225234
downloadFileHandler: { fileKey in
226235
await viewModel.downloadFile(fileKey: fileKey)
236+
},
237+
fetchRecordCommentsHandler: { recordID in
238+
await viewModel.fetchRecordComments(recordID: recordID)
227239
}
228240
)
229241
.tabItem {

Example/Example/Extensions/KintoneAPI+Extension.swift

+2
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,5 @@ extension RecordState: @retroactive Identifiable {
3838
extension StatusAction: @retroactive Identifiable {
3939
public var id: String { name }
4040
}
41+
42+
extension RecordComment.Read: @retroactive Identifiable {}

Example/Example/FetchApps/FetchAppsView.swift

+6-6
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,16 @@ struct FetchAppsView: View {
2727
Divider()
2828
VStack(alignment: .leading, spacing: 4) {
2929
VStack(alignment: .leading, spacing: 4) {
30-
Text("Created At: \(String(optional: app.createdAt))")
31-
Text("Code: \(app.creator.code)")
32-
Text("Name: \(String(optional: app.creator.name))")
30+
Text("Created At: \(app.createdAt)")
31+
Text("Creator/Code: \(app.creator.code)")
32+
Text("Creator/Name: \(String(optional: app.creator.name))")
3333
}
3434
.frame(maxWidth: .infinity, alignment: .leading)
3535
.cornerRadiusBorder()
3636
VStack(alignment: .leading, spacing: 4) {
37-
Text("Modified At: \(String(optional: app.modifiedAt))")
38-
Text("Code: \(app.modifier.code)")
39-
Text("Name: \(String(optional: app.modifier.name))")
37+
Text("Modified At: \(app.modifiedAt)")
38+
Text("Modifier/Code: \(app.modifier.code)")
39+
Text("Modifier/Name: \(String(optional: app.modifier.name))")
4040
}
4141
.frame(maxWidth: .infinity, alignment: .leading)
4242
.cornerRadiusBorder()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//
2+
// FetchRecordCommentsView.swift
3+
// Example
4+
//
5+
// Created by ky0me22 on 2025/02/19.
6+
//
7+
8+
import KintoneAPI
9+
import SwiftUI
10+
11+
struct FetchRecordCommentsView: View {
12+
@State private var isPresented = false
13+
@State private var comments = [RecordComment.Read]()
14+
var fetchRecordCommentsHandler: () async -> RecordComments?
15+
16+
var body: some View {
17+
HStack(alignment: .top) {
18+
Text("Comments:")
19+
Divider()
20+
Button {
21+
isPresented = true
22+
} label: {
23+
Text("Fetch")
24+
}
25+
}
26+
.frame(maxWidth: .infinity, alignment: .leading)
27+
.cornerRadiusBorder()
28+
.sheet(isPresented: $isPresented) {
29+
RecordCommentsView(comments: comments)
30+
.task {
31+
if let recordComments = await fetchRecordCommentsHandler() {
32+
comments = recordComments.comments
33+
}
34+
}
35+
}
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//
2+
// RecordCommentViewDetailView.swift
3+
// Example
4+
//
5+
// Created by ky0me22 on 2025/02/19.
6+
//
7+
8+
import KintoneAPI
9+
import SwiftUI
10+
11+
struct RecordCommentViewDetailView: View {
12+
var comment: RecordComment.Read
13+
14+
var body: some View {
15+
VStack(alignment: .leading, spacing: 4) {
16+
CornerRadiusText("ID: \(comment.id)")
17+
HStack(alignment: .top) {
18+
Text("Text:")
19+
Divider()
20+
Text(comment.text)
21+
}
22+
.frame(maxWidth: .infinity, alignment: .leading)
23+
.cornerRadiusBorder()
24+
VStack(alignment: .leading, spacing: 4) {
25+
Text("Created At: \(comment.createdAt)")
26+
Text("Creator/Code: \(comment.creator.code)")
27+
Text("Creator/Name: \(String(optional: comment.creator.name))")
28+
}
29+
.frame(maxWidth: .infinity, alignment: .leading)
30+
.cornerRadiusBorder()
31+
if comment.mentions.isEmpty {
32+
CornerRadiusText("Mentions: Empty")
33+
} else {
34+
HStack(alignment: .top) {
35+
Text("Mentions:")
36+
Divider()
37+
VStack(alignment: .leading, spacing: 4) {
38+
ForEach(comment.mentions) { entity in
39+
VStack(alignment: .leading, spacing: 4) {
40+
Text("Code: \(entity.code)")
41+
Text("Type: \(entity.type)")
42+
}
43+
.frame(maxWidth: .infinity, alignment: .leading)
44+
.cornerRadiusBorder()
45+
}
46+
}
47+
}
48+
.cornerRadiusBorder()
49+
}
50+
}
51+
.cornerRadiusBorder()
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// RecordCommentsView.swift
3+
// Example
4+
//
5+
// Created by ky0me22 on 2025/02/19.
6+
//
7+
8+
import KintoneAPI
9+
import SwiftUI
10+
11+
struct RecordCommentsView: View {
12+
var comments: [RecordComment.Read]
13+
14+
var body: some View {
15+
ScrollView {
16+
VStack(spacing: 16) {
17+
if comments.isEmpty {
18+
CornerRadiusText("Comments: Empty")
19+
} else {
20+
ForEach(comments) { comment in
21+
RecordCommentViewDetailView(comment: comment)
22+
}
23+
}
24+
}
25+
.padding()
26+
}
27+
}
28+
}

Example/Example/FetchRecords/FetchRecordsView.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ struct FetchRecordsView: View {
1313
var actions: [StatusAction]
1414
var updateStatusHandler: (RecordIdentity.Write, StatusAction) async -> Void
1515
var downloadFileHandler: (String) async -> Data?
16+
var fetchRecordCommentsHandler: (Int) async -> RecordComments?
1617

1718
var body: some View {
1819
ScrollView {
@@ -22,7 +23,8 @@ struct FetchRecordsView: View {
2223
record: records[i],
2324
actions: actions,
2425
updateStatusHandler: updateStatusHandler,
25-
downloadFileHandler: downloadFileHandler
26+
downloadFileHandler: downloadFileHandler,
27+
fetchRecordCommentsHandler: fetchRecordCommentsHandler
2628
)
2729
}
2830
}

Example/Example/FetchRecords/RecordDetailView.swift

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ struct RecordDetailView: View {
1313
var actions: [StatusAction]
1414
var updateStatusHandler: (RecordIdentity.Write, StatusAction) async -> Void
1515
var downloadFileHandler: (String) async -> Data?
16+
var fetchRecordCommentsHandler: (Int) async -> RecordComments?
1617

1718
var body: some View {
1819
VStack(alignment: .leading, spacing: 4) {
@@ -27,6 +28,9 @@ struct RecordDetailView: View {
2728
downloadFileHandler: downloadFileHandler
2829
)
2930
}
31+
FetchRecordCommentsView {
32+
await fetchRecordCommentsHandler(record.identity.id)
33+
}
3034
}
3135
.cornerRadiusBorder()
3236
}

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Providing kintone REST API with Swift interface.
3030
| `removeRecords` | DELETE | [/k/v1/records.json](https://kintone.dev/en/docs/kintone/rest-api/records/delete-records/) |
3131
| `submitRecord` | POST | [/k/v1/record.json](https://kintone.dev/en/docs/kintone/rest-api/records/add-record/) |
3232
| `updateRecord` | PUT | [/k/v1/record.json](https://kintone.dev/en/docs/kintone/rest-api/records/update-record/) |
33+
| `fetchRecordComments` | GET | [/k/v1/record/comments.json](https://kintone.dev/en/docs/kintone/rest-api/records/get-comments/) |
3334
| `updateStatus` | PUT | [/k/v1/record/status.json](https://kintone.dev/en/docs/kintone/rest-api/records/update-status/) |
3435
| `downloadFile` | GET | [/k/v1/file.json](https://kintone.dev/en/docs/kintone/rest-api/files/download-file/) |
3536
| `uploadFile` | POST | [/k/v1/file.json](https://kintone.dev/en/docs/kintone/rest-api/files/upload-file/) |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//
2+
// RecordComment+Read.swift
3+
//
4+
//
5+
// Created by ky0me22 on 2025/02/19.
6+
//
7+
8+
import Foundation
9+
10+
extension RecordComment {
11+
public struct Read: Decodable, Sendable {
12+
public var id: Int
13+
public var text: String
14+
public var createdAt: Date
15+
public var creator: Entity.Read
16+
public var mentions: [Entity.Read]
17+
18+
enum CodingKeys: CodingKey {
19+
case id
20+
case text
21+
case createdAt
22+
case creator
23+
case mentions
24+
}
25+
26+
public init(from decoder: any Decoder) throws {
27+
let container = try decoder.container(keyedBy: CodingKeys.self)
28+
id = try container.customDecode(String.self, forKey: .id) { Int($0) }
29+
text = try container.decode(String.self, forKey: .text)
30+
createdAt = try container.customDecode(String.self, forKey: .createdAt) {
31+
DateFormatter.kintoneDateTime.date(from: $0.normalizedDateTime)
32+
}
33+
creator = try container.customDecode(EntityValue.self, forKey: .creator) {
34+
Entity.Read(type: .user, code: $0.code, name: $0.name)
35+
}
36+
mentions = try container.decode([Entity.Read].self, forKey: .mentions)
37+
}
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//
2+
// RecordComments.swift
3+
//
4+
//
5+
// Created by ky0me22 on 2025/02/19.
6+
//
7+
8+
public struct RecordComments: Decodable, Sendable {
9+
public var comments: [RecordComment.Read]
10+
public var older: Bool
11+
public var newer: Bool
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// FetchRecordCommentsResponse.swift
3+
//
4+
//
5+
// Created by ky0me22 on 2025/02/19.
6+
//
7+
8+
struct FetchRecordCommentsResponse: Decodable {
9+
var recordComments: RecordComments
10+
11+
init(from decoder: any Decoder) throws {
12+
recordComments = try RecordComments(from: decoder)
13+
}
14+
}

Sources/KintoneAPI/Decodable/KintoneApp/KintoneApp.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ public struct KintoneApp: Decodable, Sendable {
1414
public var description: String
1515
public var spaceID: Int?
1616
public var threadID: Int?
17-
public var createdAt: Date?
17+
public var createdAt: Date
1818
public var creator: Entity.Read
19-
public var modifiedAt: Date?
19+
public var modifiedAt: Date
2020
public var modifier: Entity.Read
2121

2222
enum CodingKeys: String, CodingKey {
@@ -40,13 +40,13 @@ public struct KintoneApp: Decodable, Sendable {
4040
description = try container.decode(String.self, forKey: .description)
4141
spaceID = try container.customDecodeIfPresent(String.self, forKey: .spaceID) { Int($0) }
4242
threadID = try container.customDecodeIfPresent(String.self, forKey: .threadID) { Int($0) }
43-
createdAt = try container.customDecodeIfPresent(String.self, forKey: .createdAt) {
43+
createdAt = try container.customDecode(String.self, forKey: .createdAt) {
4444
DateFormatter.kintoneDateTime.date(from: $0.normalizedDateTime)
4545
}
4646
creator = try container.customDecode(EntityValue.self, forKey: .creator) {
4747
Entity.Read(type: .user, code: $0.code, name: $0.name)
4848
}
49-
modifiedAt = try container.customDecodeIfPresent(String.self, forKey: .modifiedAt) {
49+
modifiedAt = try container.customDecode(String.self, forKey: .modifiedAt) {
5050
DateFormatter.kintoneDateTime.date(from: $0.normalizedDateTime)
5151
}
5252
modifier = try container.customDecode(EntityValue.self, forKey: .modifier) {

Sources/KintoneAPI/Endpoint.swift

+10-9
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66
//
77

88
enum Endpoint: String {
9-
case apps = "/k/v1/apps.json"
10-
case formLayout = "/k/v1/app/form/layout.json"
11-
case fields = "/k/v1/app/form/fields.json"
12-
case appSettings = "/k/v1/app/settings.json"
13-
case appStatus = "/k/v1/app/status.json"
14-
case records = "/k/v1/records.json"
15-
case record = "/k/v1/record.json"
16-
case recordStatus = "/k/v1/record/status.json"
17-
case file = "/k/v1/file.json"
9+
case apps = "/k/v1/apps.json"
10+
case formLayout = "/k/v1/app/form/layout.json"
11+
case fields = "/k/v1/app/form/fields.json"
12+
case appSettings = "/k/v1/app/settings.json"
13+
case appStatus = "/k/v1/app/status.json"
14+
case records = "/k/v1/records.json"
15+
case record = "/k/v1/record.json"
16+
case recordComments = "/k/v1/record/comments.json"
17+
case recordStatus = "/k/v1/record/status.json"
18+
case file = "/k/v1/file.json"
1819
}

Sources/KintoneAPI/KintoneAPI.swift

+20
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,26 @@ public struct KintoneAPI: Sendable {
186186
return RecordIdentity.Read(id: recordIdentity.id, revision: updateRecordResponse.revision)
187187
}
188188

189+
public func fetchRecordComments(
190+
appID: Int,
191+
recordID: Int,
192+
order: Order? = nil,
193+
offset: Int? = nil,
194+
limit: Int? = nil
195+
) async throws -> RecordComments {
196+
var queryItems = [URLQueryItem]()
197+
queryItems.appendQueryItem(name: "app", value: appID.description)
198+
queryItems.appendQueryItem(name: "record", value: recordID.description)
199+
queryItems.appendQueryItem(name: "order", value: order?.rawValue)
200+
queryItems.appendQueryItem(name: "offset", value: offset?.description)
201+
queryItems.appendQueryItem(name: "limit", value: limit?.description)
202+
let request = makeRequest(httpMethod: .get, endpoint: .recordComments, queryItems: queryItems)
203+
let (data, response) = try await dataRequestHandler(request)
204+
try check(response: response)
205+
let fetchRecordCommentsResponse = try JSONDecoder().decode(FetchRecordCommentsResponse.self, from: data)
206+
return fetchRecordCommentsResponse.recordComments
207+
}
208+
189209
@discardableResult
190210
public func updateStatus(
191211
appID: Int,

Sources/KintoneAPI/NameSpace.swift

+2
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ public enum RecordField {}
2020
public enum RecordFieldValue {}
2121

2222
public enum SubtableValue {}
23+
24+
public enum RecordComment {}

Sources/KintoneAPI/Order.swift

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//
2+
// Order.swift
3+
//
4+
//
5+
// Created by ky0me22 on 2025/02/18.
6+
//
7+
8+
import Foundation
9+
10+
public enum Order: String, Sendable {
11+
case ascending = "asc"
12+
case descending = "desc"
13+
}

0 commit comments

Comments
 (0)