Fetch rules
This commit is contained in:
@ -23,6 +23,7 @@ typealias OperationStatusHandler = (_: OperationStatus) -> Void
|
||||
@Published var isLoading = true
|
||||
@Published var feeds = [Subscription]()
|
||||
@Published var newsletters = [Subscription]()
|
||||
@Published var rules = [Rule]()
|
||||
@Published var hasNetworkError = false
|
||||
@Published var subscriptionNameToCancel: String?
|
||||
@Published var presentingSubscription: Subscription?
|
||||
@ -42,6 +43,9 @@ typealias OperationStatusHandler = (_: OperationStatus) -> Void
|
||||
hasNetworkError = true
|
||||
}
|
||||
|
||||
// Also try to get the rules for auto labeling
|
||||
rules = (try? await dataService.rules()) ?? []
|
||||
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
@ -86,6 +90,21 @@ typealias OperationStatusHandler = (_: OperationStatus) -> Void
|
||||
operationStatus = .failure
|
||||
}
|
||||
}
|
||||
|
||||
func setLabelsRule(dataService: DataService, ruleName: String, filter: String, labelIDs: [String]) async {
|
||||
async {
|
||||
operationMessage = "Creating label rule..."
|
||||
operationStatus = .isPerforming
|
||||
do {
|
||||
try await dataService.createAddLabelsRule(name: ruleName, filter: filter, labelIDs: labelIDs)
|
||||
operationMessage = "Rule created"
|
||||
operationStatus = .success
|
||||
} catch {
|
||||
operationMessage = "Failed to create label rule"
|
||||
operationStatus = .failure
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct OperationToast: View {
|
||||
@ -362,6 +381,20 @@ struct SubscriptionSettingsView: View {
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
var ruleName: String {
|
||||
if let url = subscription.url, subscription.type == .newsletter {
|
||||
return "system.autoLabel.(\(url))"
|
||||
}
|
||||
return "system.autoLabel.(\(subscription.name))"
|
||||
}
|
||||
|
||||
var ruleFilter: String {
|
||||
if let url = subscription.url, subscription.type == .newsletter {
|
||||
return "rss:\"\(url)\""
|
||||
}
|
||||
return "subscription:\"\(subscription.name)\""
|
||||
}
|
||||
|
||||
var folderRow: some View {
|
||||
HStack {
|
||||
Picker("Destination Folder", selection: $folderSelection) {
|
||||
@ -395,6 +428,7 @@ struct SubscriptionSettingsView: View {
|
||||
Text("Add Labels")
|
||||
Spacer()
|
||||
Button(action: { showLabelsSelector = true }, label: {
|
||||
let rule = viewModel.rules.first { $0.name == ruleName }
|
||||
Text("[none]")
|
||||
})
|
||||
}
|
||||
@ -451,10 +485,18 @@ struct SubscriptionSettingsView: View {
|
||||
}
|
||||
.sheet(isPresented: $showLabelsSelector) {
|
||||
ApplyLabelsView(mode: .list([]), onSave: { labels in
|
||||
print("APPLIED LABELSL: ", labels)
|
||||
// showLabelsModal = false
|
||||
// item.labels = NSSet(array: labels)
|
||||
// readerSettingsChangedTransactionID = UUID()
|
||||
Task {
|
||||
viewModel.showOperationToast = true
|
||||
await viewModel.setLabelsRule(
|
||||
dataService: dataService,
|
||||
ruleName: ruleName,
|
||||
filter: ruleFilter,
|
||||
labelIDs: labels.map(\.unwrappedID)
|
||||
)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1500)) {
|
||||
viewModel.showOperationToast = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -6652,6 +6652,136 @@ extension Selection where TypeLock == Never, Type == Never {
|
||||
typealias FeedsSuccess<T> = Selection<T, Objects.FeedsSuccess>
|
||||
}
|
||||
|
||||
extension Objects {
|
||||
struct FetchContentError {
|
||||
let __typename: TypeName = .fetchContentError
|
||||
let errorCodes: [String: [Enums.FetchContentErrorCode]]
|
||||
|
||||
enum TypeName: String, Codable {
|
||||
case fetchContentError = "FetchContentError"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Objects.FetchContentError: Decodable {
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: DynamicCodingKeys.self)
|
||||
|
||||
var map = HashMap()
|
||||
for codingKey in container.allKeys {
|
||||
if codingKey.isTypenameKey { continue }
|
||||
|
||||
let alias = codingKey.stringValue
|
||||
let field = GraphQLField.getFieldNameFromAlias(alias)
|
||||
|
||||
switch field {
|
||||
case "errorCodes":
|
||||
if let value = try container.decode([Enums.FetchContentErrorCode]?.self, forKey: codingKey) {
|
||||
map.set(key: field, hash: alias, value: value as Any)
|
||||
}
|
||||
default:
|
||||
throw DecodingError.dataCorrupted(
|
||||
DecodingError.Context(
|
||||
codingPath: decoder.codingPath,
|
||||
debugDescription: "Unknown key \(field)."
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
errorCodes = map["errorCodes"]
|
||||
}
|
||||
}
|
||||
|
||||
extension Fields where TypeLock == Objects.FetchContentError {
|
||||
func errorCodes() throws -> [Enums.FetchContentErrorCode] {
|
||||
let field = GraphQLField.leaf(
|
||||
name: "errorCodes",
|
||||
arguments: []
|
||||
)
|
||||
select(field)
|
||||
|
||||
switch response {
|
||||
case let .decoding(data):
|
||||
if let data = data.errorCodes[field.alias!] {
|
||||
return data
|
||||
}
|
||||
throw HttpError.badpayload
|
||||
case .mocking:
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Selection where TypeLock == Never, Type == Never {
|
||||
typealias FetchContentError<T> = Selection<T, Objects.FetchContentError>
|
||||
}
|
||||
|
||||
extension Objects {
|
||||
struct FetchContentSuccess {
|
||||
let __typename: TypeName = .fetchContentSuccess
|
||||
let success: [String: Bool]
|
||||
|
||||
enum TypeName: String, Codable {
|
||||
case fetchContentSuccess = "FetchContentSuccess"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Objects.FetchContentSuccess: Decodable {
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: DynamicCodingKeys.self)
|
||||
|
||||
var map = HashMap()
|
||||
for codingKey in container.allKeys {
|
||||
if codingKey.isTypenameKey { continue }
|
||||
|
||||
let alias = codingKey.stringValue
|
||||
let field = GraphQLField.getFieldNameFromAlias(alias)
|
||||
|
||||
switch field {
|
||||
case "success":
|
||||
if let value = try container.decode(Bool?.self, forKey: codingKey) {
|
||||
map.set(key: field, hash: alias, value: value as Any)
|
||||
}
|
||||
default:
|
||||
throw DecodingError.dataCorrupted(
|
||||
DecodingError.Context(
|
||||
codingPath: decoder.codingPath,
|
||||
debugDescription: "Unknown key \(field)."
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
success = map["success"]
|
||||
}
|
||||
}
|
||||
|
||||
extension Fields where TypeLock == Objects.FetchContentSuccess {
|
||||
func success() throws -> Bool {
|
||||
let field = GraphQLField.leaf(
|
||||
name: "success",
|
||||
arguments: []
|
||||
)
|
||||
select(field)
|
||||
|
||||
switch response {
|
||||
case let .decoding(data):
|
||||
if let data = data.success[field.alias!] {
|
||||
return data
|
||||
}
|
||||
throw HttpError.badpayload
|
||||
case .mocking:
|
||||
return Bool.mockValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Selection where TypeLock == Never, Type == Never {
|
||||
typealias FetchContentSuccess<T> = Selection<T, Objects.FetchContentSuccess>
|
||||
}
|
||||
|
||||
extension Objects {
|
||||
struct Filter {
|
||||
let __typename: TypeName = .filter
|
||||
@ -11112,6 +11242,7 @@ extension Objects {
|
||||
let deleteNewsletterEmail: [String: Unions.DeleteNewsletterEmailResult]
|
||||
let deleteRule: [String: Unions.DeleteRuleResult]
|
||||
let deleteWebhook: [String: Unions.DeleteWebhookResult]
|
||||
let fetchContent: [String: Unions.FetchContentResult]
|
||||
let generateApiKey: [String: Unions.GenerateApiKeyResult]
|
||||
let googleLogin: [String: Unions.LoginResult]
|
||||
let googleSignup: [String: Unions.GoogleSignupResult]
|
||||
@ -11239,6 +11370,10 @@ extension Objects.Mutation: Decodable {
|
||||
if let value = try container.decode(Unions.DeleteWebhookResult?.self, forKey: codingKey) {
|
||||
map.set(key: field, hash: alias, value: value as Any)
|
||||
}
|
||||
case "fetchContent":
|
||||
if let value = try container.decode(Unions.FetchContentResult?.self, forKey: codingKey) {
|
||||
map.set(key: field, hash: alias, value: value as Any)
|
||||
}
|
||||
case "generateApiKey":
|
||||
if let value = try container.decode(Unions.GenerateApiKeyResult?.self, forKey: codingKey) {
|
||||
map.set(key: field, hash: alias, value: value as Any)
|
||||
@ -11441,6 +11576,7 @@ extension Objects.Mutation: Decodable {
|
||||
deleteNewsletterEmail = map["deleteNewsletterEmail"]
|
||||
deleteRule = map["deleteRule"]
|
||||
deleteWebhook = map["deleteWebhook"]
|
||||
fetchContent = map["fetchContent"]
|
||||
generateApiKey = map["generateApiKey"]
|
||||
googleLogin = map["googleLogin"]
|
||||
googleSignup = map["googleSignup"]
|
||||
@ -11793,6 +11929,25 @@ extension Fields where TypeLock == Objects.Mutation {
|
||||
}
|
||||
}
|
||||
|
||||
func fetchContent<Type>(id: String, selection: Selection<Type, Unions.FetchContentResult>) throws -> Type {
|
||||
let field = GraphQLField.composite(
|
||||
name: "fetchContent",
|
||||
arguments: [Argument(name: "id", type: "ID!", value: id)],
|
||||
selection: selection.selection
|
||||
)
|
||||
select(field)
|
||||
|
||||
switch response {
|
||||
case let .decoding(data):
|
||||
if let data = data.fetchContent[field.alias!] {
|
||||
return try selection.decode(data: data)
|
||||
}
|
||||
throw HttpError.badpayload
|
||||
case .mocking:
|
||||
return selection.mock()
|
||||
}
|
||||
}
|
||||
|
||||
func generateApiKey<Type>(input: InputObjects.GenerateApiKeyInput, selection: Selection<Type, Unions.GenerateApiKeyResult>) throws -> Type {
|
||||
let field = GraphQLField.composite(
|
||||
name: "generateApiKey",
|
||||
@ -27864,6 +28019,80 @@ extension Selection where TypeLock == Never, Type == Never {
|
||||
typealias FeedsResult<T> = Selection<T, Unions.FeedsResult>
|
||||
}
|
||||
|
||||
extension Unions {
|
||||
struct FetchContentResult {
|
||||
let __typename: TypeName
|
||||
let errorCodes: [String: [Enums.FetchContentErrorCode]]
|
||||
let success: [String: Bool]
|
||||
|
||||
enum TypeName: String, Codable {
|
||||
case fetchContentError = "FetchContentError"
|
||||
case fetchContentSuccess = "FetchContentSuccess"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Unions.FetchContentResult: Decodable {
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: DynamicCodingKeys.self)
|
||||
|
||||
var map = HashMap()
|
||||
for codingKey in container.allKeys {
|
||||
if codingKey.isTypenameKey { continue }
|
||||
|
||||
let alias = codingKey.stringValue
|
||||
let field = GraphQLField.getFieldNameFromAlias(alias)
|
||||
|
||||
switch field {
|
||||
case "errorCodes":
|
||||
if let value = try container.decode([Enums.FetchContentErrorCode]?.self, forKey: codingKey) {
|
||||
map.set(key: field, hash: alias, value: value as Any)
|
||||
}
|
||||
case "success":
|
||||
if let value = try container.decode(Bool?.self, forKey: codingKey) {
|
||||
map.set(key: field, hash: alias, value: value as Any)
|
||||
}
|
||||
default:
|
||||
throw DecodingError.dataCorrupted(
|
||||
DecodingError.Context(
|
||||
codingPath: decoder.codingPath,
|
||||
debugDescription: "Unknown key \(field)."
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
__typename = try container.decode(TypeName.self, forKey: DynamicCodingKeys(stringValue: "__typename")!)
|
||||
|
||||
errorCodes = map["errorCodes"]
|
||||
success = map["success"]
|
||||
}
|
||||
}
|
||||
|
||||
extension Fields where TypeLock == Unions.FetchContentResult {
|
||||
func on<Type>(fetchContentError: Selection<Type, Objects.FetchContentError>, fetchContentSuccess: Selection<Type, Objects.FetchContentSuccess>) throws -> Type {
|
||||
select([GraphQLField.fragment(type: "FetchContentError", selection: fetchContentError.selection), GraphQLField.fragment(type: "FetchContentSuccess", selection: fetchContentSuccess.selection)])
|
||||
|
||||
switch response {
|
||||
case let .decoding(data):
|
||||
switch data.__typename {
|
||||
case .fetchContentError:
|
||||
let data = Objects.FetchContentError(errorCodes: data.errorCodes)
|
||||
return try fetchContentError.decode(data: data)
|
||||
case .fetchContentSuccess:
|
||||
let data = Objects.FetchContentSuccess(success: data.success)
|
||||
return try fetchContentSuccess.decode(data: data)
|
||||
}
|
||||
case .mocking:
|
||||
return fetchContentError.mock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Selection where TypeLock == Never, Type == Never {
|
||||
typealias FetchContentResult<T> = Selection<T, Unions.FetchContentResult>
|
||||
}
|
||||
|
||||
extension Unions {
|
||||
struct FiltersResult {
|
||||
let __typename: TypeName
|
||||
@ -33101,6 +33330,8 @@ extension Enums {
|
||||
enum ArticleSavingRequestStatus: String, CaseIterable, Codable {
|
||||
case archived = "ARCHIVED"
|
||||
|
||||
case contentNotFetched = "CONTENT_NOT_FETCHED"
|
||||
|
||||
case deleted = "DELETED"
|
||||
|
||||
case failed = "FAILED"
|
||||
@ -33414,6 +33645,15 @@ extension Enums {
|
||||
}
|
||||
}
|
||||
|
||||
extension Enums {
|
||||
/// FetchContentErrorCode
|
||||
enum FetchContentErrorCode: String, CaseIterable, Codable {
|
||||
case badRequest = "BAD_REQUEST"
|
||||
|
||||
case unauthorized = "UNAUTHORIZED"
|
||||
}
|
||||
}
|
||||
|
||||
extension Enums {
|
||||
/// FiltersErrorCode
|
||||
enum FiltersErrorCode: String, CaseIterable, Codable {
|
||||
|
||||
@ -3,19 +3,9 @@ import Foundation
|
||||
import Models
|
||||
import SwiftGraphQL
|
||||
|
||||
// input SetRuleInput {
|
||||
// id: ID
|
||||
// name: String!
|
||||
// description: String
|
||||
// filter: String!
|
||||
// actions: [RuleActionInput!]!
|
||||
// enabled: Boolean!
|
||||
// eventTypes: [RuleEventType!]!
|
||||
// }
|
||||
|
||||
public struct Rule {
|
||||
let id: String
|
||||
let name: String
|
||||
public let id: String
|
||||
public let name: String
|
||||
}
|
||||
|
||||
let ruleSelection = Selection.Rule {
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
import Foundation
|
||||
import Models
|
||||
import SwiftGraphQL
|
||||
|
||||
public extension DataService {
|
||||
func rules() async throws -> [Rule] {
|
||||
enum QueryResult {
|
||||
case success(result: [Rule])
|
||||
case error(error: String)
|
||||
}
|
||||
|
||||
let selection = Selection<QueryResult, Unions.RulesResult> {
|
||||
try $0.on(
|
||||
rulesError: .init {
|
||||
QueryResult.error(error: try $0.errorCodes().description)
|
||||
},
|
||||
rulesSuccess: .init {
|
||||
QueryResult.success(result: try $0.rules(selection: ruleSelection.list))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
let query = Selection.Query {
|
||||
try $0.rules(selection: selection)
|
||||
}
|
||||
|
||||
let path = appEnvironment.graphqlPath
|
||||
let headers = networker.defaultHeaders
|
||||
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
send(query, to: path, headers: headers) { queryResult in
|
||||
guard let payload = try? queryResult.get() else {
|
||||
continuation.resume(throwing: BasicError.message(messageText: "network request failed"))
|
||||
return
|
||||
}
|
||||
|
||||
switch payload.data {
|
||||
case let .success(result: result):
|
||||
continuation.resume(returning: result)
|
||||
case .error:
|
||||
continuation.resume(throwing: BasicError.message(messageText: "Rules fetch error"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user