Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions PayForMe/Model/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ class Project: Codable, Identifiable {
var bills: [Bill]
var me: Int?

convenience init(name: String, password: String, token: String, backend: ProjectBackend, url: URL) {
self.init(name: name, password: password, token: token, backend: backend, url: url, id: nil)
let projectId: String

convenience init(name: String, password: String, token: String, backend: ProjectBackend, url: URL, projectId: String) {
self.init(name: name, password: password, token: token, backend: backend, url: url, id: nil, projectId: projectId)
}

fileprivate init(name: String, password: String, token: String, backend: ProjectBackend, url: URL, id: Int?, me: Int? = nil) {
fileprivate init(name: String, password: String, token: String, backend: ProjectBackend, url: URL, id: Int?, me: Int? = nil, projectId: String) {
self.name = name
self.password = password
self.token = token
Expand All @@ -33,6 +35,7 @@ class Project: Codable, Identifiable {
members = [:]
bills = []
self.me = me
self.projectId = projectId
}
}

Expand All @@ -49,15 +52,17 @@ struct StoredProject: Codable {
let backend: ProjectBackend
var id: Int?
let me: Int?
let projectId: String

init(name: String, password: String, token: String, url: URL, backend: ProjectBackend) {
init(name: String, password: String, token: String, url: URL, backend: ProjectBackend, projectId: String) {
self.name = name
self.password = password
self.token = token
self.url = url
self.backend = backend
id = nil
me = nil
self.projectId = projectId
}

init(project: Project) {
Expand All @@ -68,10 +73,11 @@ struct StoredProject: Codable {
backend = project.backend
id = project.id
me = project.me
projectId = project.projectId
}

func toProject() -> Project {
Project(name: name, password: password, token: token, backend: backend, url: url, id: id!, me: me)
Project(name: name, password: password, token: token, backend: backend, url: url, id: id!, me: me, projectId: projectId)
}
}

Expand Down Expand Up @@ -101,10 +107,10 @@ enum ProjectBackend: Int, Codable {
}
}

let previewProject = Project(name: "TestProject", password: "TestPassword", token: "asdasdas", backend: .cospend, url: URL(string: "https://testserver.de")!, id: 0)
let previewProject = Project(name: "TestProject", password: "TestPassword", token: "asdasdas", backend: .cospend, url: URL(string: "https://testserver.de")!, id: 0, projectId: "TestProject")
let previewProjects = [
previewProject,
Project(name: "test1", password: "test23", token: "dasdasa", backend: .cospend, url: URL(string: "https://testserver.de")!, id: 1),
Project(name: "test2", password: "test45", token: "123123122", backend: .cospend, url: URL(string: "https://testserver.de")!, id: 2),
Project(name: "test1", password: "test23", token: "dasdasa", backend: .cospend, url: URL(string: "https://testserver.de")!, id: 1, projectId: "test1"),
Project(name: "test2", password: "test45", token: "123123122", backend: .cospend, url: URL(string: "https://testserver.de")!, id: 2, projectId: "test2"),
]
let demoProject = Project(name: "study-group", password: "no-pass", token: "9da50e410157dc1ca63e594af022f3a2", backend: .cospend, url: URL(string: "https://intranet.mayflower.de")!, id: 1)
let demoProject = Project(name: "study-group", password: "no-pass", token: "9da50e410157dc1ca63e594af022f3a2", backend: .cospend, url: URL(string: "https://intranet.mayflower.de")!, id: 1, projectId: "study-group")
60 changes: 55 additions & 5 deletions PayForMe/Services/NetworkService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,43 @@ class NetworkService {
}
let apiProject = try JSONDecoder().decode(APIProject.self, from: data)

return Project(name: apiProject.name, password: project.password, token: project.token, backend: project.backend, url: project.url)
return Project(name: apiProject.name, password: project.password, token: project.token, backend: project.backend, url: project.url, projectId: apiProject.id)
}

func getProjectName(invite: InviteData) async throws -> Project {
var request = URLRequest(url: URL(string: invite.url + "/api/projects/" + invite.project)!)
request.httpMethod = "GET"
request.setValue(
"Bearer \(invite.token)",
forHTTPHeaderField: "Authorization"
)

let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode / 100 == 2 else {
throw HTTPError.statuscode
}
let apiProject = try JSONDecoder().decode(APIProject.self, from: data)

return Project(name: apiProject.name, password: "", token: invite.token, backend: ProjectBackend.iHateMoney, url: URL(string: invite.url)!, projectId: apiProject.id)
}

func fetchInvite(_ invite: InviteData?) async throws {
guard let invite = invite, let url = URL(string: invite.url) else {
return
}

var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue(
"Bearer \(invite.token)",
forHTTPHeaderField: "Authorization"
)

let (data, response) = try await URLSession.shared.data(for: request)
if let httpResponse = response as? HTTPURLResponse {
print("status:", httpResponse.statusCode)
}
print(String(data: data, encoding: .utf8) ?? "")
}

func postBillPublisher(bill: Bill) -> AnyPublisher<Bool, Never> {
Expand Down Expand Up @@ -147,13 +183,21 @@ class NetworkService {
.replaceError(with: false)
.eraseToAnyPublisher()
}


private func baseURLFor(_ project: Project, suffix: String) -> URL {
var url = project.url
.appendingPathComponent(project.backend.staticPath)
.appendingPathComponent(project.token)

if project.backend == .cospend {
url = url.appendingPathComponent(project.password)
url = url
.appendingPathComponent(project.projectId)
.appendingPathComponent(project.password)
}

if project.backend == .iHateMoney {
url = url
.appendingPathComponent(project.projectId)
}
if suffix.isEmpty {
return url
Expand Down Expand Up @@ -181,8 +225,14 @@ class NetworkService {
request = URLRequest(url: requestURL)

if project.backend == .iHateMoney {
guard let authString = "\(project.token):\(project.password)".data(using: .utf8)?.base64EncodedString() else { fatalError("error generating authString. THIS SHOULD NOT HAPPEN") }
request.setValue("Basic \(authString)", forHTTPHeaderField: "Authorization")
if project.password.isEmpty {
request.setValue("Bearer \(project.token)", forHTTPHeaderField: "Authorization")
} else {
guard let authString = "\(project.token):\(project.password)".data(using: .utf8)?.base64EncodedString() else {
fatalError("error generating authString. THIS SHOULD NOT HAPPEN")
}
request.setValue("Basic \(authString)", forHTTPHeaderField: "Authorization")
}

if !params.isEmpty {
request.httpBody = try? JSONSerialization.data(withJSONObject: params)
Expand Down
6 changes: 1 addition & 5 deletions PayForMe/Services/ProjectManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,7 @@ class ProjectManager: ObservableObject {

func openedByURL(url: URL) {
let data = url.decodeCospendString()
guard let _ = data.server,
let _ = data.project
else {
return
}
guard data != nil else { return }
openedByURL = url
}

Expand Down
8 changes: 7 additions & 1 deletion PayForMe/Services/StorageService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ class StorageService {
table.add(column: "me")
})
}
migrator.registerMigration("v4") { db in
try db.alter(table: "storedProject", body: { table in
table.add(column: "projectId")
})
try db.execute(sql: "UPDATE storedProject SET projectId = name;")
}
// #if DEBUG
//// Speed up development by nuking the database when migrations change
// migrator.eraseDatabaseOnSchemaChange = true
Expand Down Expand Up @@ -153,6 +159,6 @@ private class OldProject: Codable, Identifiable {
var bills: [Bill]

func toProject() -> StoredProject {
return StoredProject(name: name, password: password, token: name, url: url, backend: backend)
return StoredProject(name: name, password: password, token: name, url: url, backend: backend, projectId: name)
}
}
104 changes: 88 additions & 16 deletions PayForMe/Util/Util.swift
Original file line number Diff line number Diff line change
Expand Up @@ -176,43 +176,115 @@ extension StringProtocol {
}
}

typealias ProjectData = (server: URL?, project: String?, passwd: String?)
protocol ProjectData {
var server: URL { get }
var project: String { get }
}

struct ProjectDataWithToken: ProjectData {
var server: URL
var project: String
var token: String

init(server: URL, project: String, token: String) {
self.server = server
self.project = project
self.token = token
}

}

struct ProjectDataWithPassword: ProjectData {
var server: URL
var project: String
var password: String?

init(server: URL, project: String, password: String?) {
self.server = server
self.project = project
self.password = password
}
}

extension URL {
func decodeMoneyBusterString() -> ProjectData {
func decodeMoneyBusterString() -> ProjectDataWithPassword? {
guard absoluteString.hasPrefix("https://net.eneiluj.moneybuster.cospend/"),
pathComponents.count >= 3, pathComponents.count <= 4 else { return (nil, nil, nil) }
return (URL(string: "https://" + pathComponents[1]), pathComponents[2], pathComponents[safe: 3])
pathComponents.count >= 3, pathComponents.count <= 4 else {
return nil
}

guard let hostUrl = URL(string: "https://" + pathComponents[1]) else {
return nil
}

let password = pathComponents[safe: 3]

return ProjectDataWithPassword(
server: hostUrl,
project: pathComponents[2],
password: password
)
}
}

extension URL {
func decodeCospendString() -> ProjectData {
func decodeCospendString() -> ProjectDataWithPassword? {
guard let host = host,
let scheme = scheme,
scheme.localizedCaseInsensitiveContains("cospend")
else {
return (nil, nil, nil)
return nil
}
var hostString = "https://\(host)"

var hostString = host

if let port = port {hostString += ":\(port)"}

if pathComponents.count > 3 {
hostString += "/" + pathComponents[1..<(pathComponents.count - 2)].joined(separator: "/")
}

return (URL(string: hostString),
pathComponents[safe: pathComponents.count - 2],
pathComponents.last)

guard
let hostUrl = URL(string: "https://" + hostString),
let project = pathComponents[safe: pathComponents.count - 2],
let password = pathComponents.last
else { return nil }

return ProjectDataWithPassword(
server: hostUrl,
project: project,
password: password)
}
}

extension URL {
func decodeIHateMoenyString() -> ProjectDataWithToken? {
guard var host = host, let scheme = scheme, scheme.localizedCaseInsensitiveContains("ihatemoney") else {
return nil
}

let hostUrl = "https://" + host

guard
let url = URL(string: hostUrl),
let project = pathComponents[safe: pathComponents.count - 3],
let token = pathComponents.last
else { return nil}

return ProjectDataWithToken(server: url, project: project, token: token)
}
}

extension URL {
func decodeQRCode() -> ProjectData {
guard let scheme = scheme else { return (nil, nil, nil) }
return scheme.contains("cospend") ? decodeCospendString() : decodeMoneyBusterString()
func decodeQRCode() -> ProjectData? {
guard let scheme = scheme else { return nil }
if scheme.contains("cospend") {
return decodeCospendString()
} else if scheme.contains("ihatemoney") {
return decodeIHateMoenyString()
} else {
return decodeMoneyBusterString()
}
}
}

Expand Down
Loading
Loading