Migrate API endpoints from CFML to PHP

- Replace all .cfm endpoints with .php (PHP backend migration)
- Update debug strings and test walkthrough documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
John Pinkyfloyd 2026-03-14 17:16:36 -07:00
parent bbdc5a91c2
commit 8d9fa1caa6
6 changed files with 79 additions and 45 deletions

View file

@ -9,6 +9,9 @@ struct OrderLineItem: Identifiable {
let quantity: Int
let remark: String
let isModifier: Bool
let isCheckedByDefault: Bool
let isInvertedGroup: Bool
let removedDefaults: [String] // Array of item names that were removed from inverted groups
var id: Int { lineItemId }
@ -26,5 +29,21 @@ struct OrderLineItem: Identifiable {
if let b = json["IsModifier"] as? Bool { isModifier = b }
else if let i = json["IsModifier"] as? Int { isModifier = i == 1 }
else { isModifier = false }
// Inverted group fields
if let b = json["IsCheckedByDefault"] as? Bool { isCheckedByDefault = b }
else if let i = json["IsCheckedByDefault"] as? Int { isCheckedByDefault = i == 1 }
else { isCheckedByDefault = false }
if let b = json["IsInvertedGroup"] as? Bool { isInvertedGroup = b }
else if let i = json["IsInvertedGroup"] as? Int { isInvertedGroup = i == 1 }
else { isInvertedGroup = false }
// Removed defaults array
if let arr = json["RemovedDefaults"] as? [String] {
removedDefaults = arr
} else {
removedDefaults = []
}
}
}

View file

@ -1,6 +1,6 @@
import Foundation
// MARK: - Tier Status (from tierStatus.cfm)
// MARK: - Tier Status (from tierStatus.php)
struct TierStatus: Codable {
var tier: Int
@ -98,7 +98,7 @@ struct TierStatus: Codable {
}
}
// MARK: - Ledger Response (from ledger.cfm)
// MARK: - Ledger Response (from ledger.php)
struct LedgerResponse: Codable {
var entries: [LedgerEntry]

View file

@ -253,7 +253,7 @@ actor APIService {
// MARK: - Auth
func login(username: String, password: String) async throws -> LoginResponse {
let json = try await postJSON("/auth/login.cfm", payload: [
let json = try await postJSON("/auth/login.php", payload: [
"username": username,
"password": password
])
@ -293,7 +293,7 @@ actor APIService {
// MARK: - OTP Login
func sendLoginOtp(phone: String) async throws -> SendOtpResponse {
let json = try await postJSON("/auth/loginOTP.cfm", payload: [
let json = try await postJSON("/auth/loginOTP.php", payload: [
"phone": phone
])
@ -309,7 +309,7 @@ actor APIService {
}
func verifyLoginOtp(uuid: String, otp: String) async throws -> LoginResponse {
let json = try await postJSON("/auth/verifyLoginOTP.cfm", payload: [
let json = try await postJSON("/auth/verifyLoginOTP.php", payload: [
"uuid": uuid,
"otp": otp
])
@ -363,7 +363,7 @@ actor APIService {
throw APIError.serverError("User not logged in")
}
let json = try await postJSON("/auth/profile.cfm", payload: [
let json = try await postJSON("/auth/profile.php", payload: [
"UserID": uid
])
@ -393,7 +393,7 @@ actor APIService {
if let em = email { payload["Email"] = em }
if let ph = phone { payload["Phone"] = ph }
let json = try await postJSON("/auth/profile.cfm", payload: payload)
let json = try await postJSON("/auth/profile.php", payload: payload)
guard ok(json) else {
throw APIError.serverError("Failed to update profile: \(err(json))")
@ -407,7 +407,7 @@ actor APIService {
throw APIError.serverError("User not logged in")
}
let json = try await postJSON("/workers/myBusinesses.cfm", payload: [
let json = try await postJSON("/workers/myBusinesses.php", payload: [
"UserID": uid
])
@ -429,7 +429,7 @@ actor APIService {
payload["CategoryID"] = cid
}
let json = try await postJSON("/tasks/listPending.cfm", payload: payload)
let json = try await postJSON("/tasks/listPending.php", payload: payload)
guard ok(json) else {
throw APIError.serverError("Failed to load tasks: \(err(json))")
}
@ -440,7 +440,7 @@ actor APIService {
func acceptTask(taskId: Int) async throws {
// Flutter only sends TaskID + BusinessID (no UserID)
let json = try await postJSON("/tasks/accept.cfm", payload: [
let json = try await postJSON("/tasks/accept.php", payload: [
"TaskID": taskId,
"BusinessID": businessId
])
@ -463,7 +463,7 @@ actor APIService {
payload["BusinessID"] = businessId
}
let json = try await postJSON("/tasks/listMine.cfm", payload: payload)
let json = try await postJSON("/tasks/listMine.php", payload: payload)
guard ok(json) else {
throw APIError.serverError("Failed to load my tasks: \(err(json))")
}
@ -483,7 +483,7 @@ actor APIService {
if cancelOrder {
payload["CancelOrder"] = true
}
let json = try await postJSON("/tasks/complete.cfm", payload: payload)
let json = try await postJSON("/tasks/complete.php", payload: payload)
guard ok(json) else {
throw APIError.serverError("Failed to complete task: \(err(json))")
}
@ -493,14 +493,14 @@ actor APIService {
let payload: [String: Any] = [
"TaskID": taskId
]
let json = try await postJSON("/tasks/completeChat.cfm", payload: payload)
let json = try await postJSON("/tasks/completeChat.php", payload: payload)
guard ok(json) else {
throw APIError.serverError("Close chat failed: \(err(json))")
}
}
func getTaskDetails(taskId: Int) async throws -> TaskDetails {
let json = try await postJSON("/tasks/getDetails.cfm", payload: [
let json = try await postJSON("/tasks/getDetails.php", payload: [
"TaskID": taskId
])
guard ok(json) else {
@ -553,7 +553,7 @@ actor APIService {
payload["AfterMessageID"] = after
}
let json = try await postJSON("/chat/getMessages.cfm", payload: payload)
let json = try await postJSON("/chat/getMessages.php", payload: payload)
guard ok(json) else {
throw APIError.serverError("Failed to load chat messages")
}
@ -573,7 +573,7 @@ actor APIService {
if let uid = userId { payload["UserID"] = uid }
if let st = senderType { payload["SenderType"] = st }
let json = try await postJSON("/chat/sendMessage.cfm", payload: payload)
let json = try await postJSON("/chat/sendMessage.php", payload: payload)
guard ok(json) else {
throw APIError.serverError("Failed to send message")
}
@ -582,7 +582,7 @@ actor APIService {
}
func markChatMessagesRead(taskId: Int, readerType: String) async throws {
let json = try await postJSON("/chat/markRead.cfm", payload: [
let json = try await postJSON("/chat/markRead.php", payload: [
"TaskID": taskId,
"ReaderType": readerType
])
@ -594,7 +594,7 @@ actor APIService {
// MARK: - Payout / Tier Endpoints
func getTierStatus() async throws -> TierStatus {
let json = try await postJSON("/workers/tierStatus.cfm", payload: [
let json = try await postJSON("/workers/tierStatus.php", payload: [
"UserID": userId ?? 0
])
guard ok(json) else {
@ -605,7 +605,7 @@ actor APIService {
}
func createStripeAccount() async throws -> String {
let json = try await postJSON("/workers/createAccount.cfm", payload: [
let json = try await postJSON("/workers/createAccount.php", payload: [
"UserID": userId ?? 0
])
guard ok(json) else {
@ -615,7 +615,7 @@ actor APIService {
}
func getOnboardingLink() async throws -> String {
let json = try await postJSON("/workers/onboardingLink.cfm", payload: [
let json = try await postJSON("/workers/onboardingLink.php", payload: [
"UserID": userId ?? 0
])
guard ok(json) else {
@ -625,7 +625,7 @@ actor APIService {
}
func getEarlyUnlockUrl() async throws -> String {
let json = try await postJSON("/workers/earlyUnlock.cfm", payload: [
let json = try await postJSON("/workers/earlyUnlock.php", payload: [
"UserID": userId ?? 0
])
guard ok(json) else {
@ -635,7 +635,7 @@ actor APIService {
}
func getLedger() async throws -> LedgerResponse {
let json = try await postJSON("/workers/ledger.cfm", payload: [
let json = try await postJSON("/workers/ledger.php", payload: [
"UserID": userId ?? 0
])
guard ok(json) else {
@ -648,7 +648,7 @@ actor APIService {
// MARK: - Avatar
func getAvatarUrl() async throws -> String? {
let json = try await getJSON("/auth/avatar.cfm")
let json = try await getJSON("/auth/avatar.php")
print("[Avatar] Response: \(json)")
guard ok(json) else {
print("[Avatar] Response not OK")
@ -681,7 +681,7 @@ actor APIService {
/// Get avatar URL for any user by their userId
func getUserAvatarUrl(userId: Int) async throws -> String? {
// Try the avatar endpoint with UserID
let json = try await postJSON("/auth/avatar.cfm", payload: ["UserID": userId])
let json = try await postJSON("/auth/avatar.php", payload: ["UserID": userId])
print("[Avatar] getUserAvatarUrl(\(userId)) Response: \(json)")
let data = json["DATA"] as? [String: Any] ?? json
@ -712,7 +712,7 @@ actor APIService {
// MARK: - Beacon Sharding
func resolveServicePoint(uuid: String, major: Int, minor: Int) async throws -> Int {
let json = try await postJSON("/beacon-sharding/resolve_servicepoint.cfm", payload: [
let json = try await postJSON("/beacon-sharding/resolve_servicepoint.php", payload: [
"UUID": uuid,
"Major": major,
"Minor": minor
@ -732,7 +732,7 @@ actor APIService {
}
func resolveBusiness(uuid: String, major: Int) async throws -> (businessId: Int, businessName: String) {
let json = try await postJSON("/beacon-sharding/resolve_business.cfm", payload: [
let json = try await postJSON("/beacon-sharding/resolve_business.php", payload: [
"UUID": uuid,
"Major": major
])
@ -774,7 +774,7 @@ actor APIService {
// MARK: - About Info
func getAboutInfo() async throws -> AboutInfo {
let json = try await getJSON("app/about.cfm")
let json = try await getJSON("app/about.php")
guard ok(json) else {
throw APIError.serverError(err(json))

View file

@ -309,15 +309,15 @@ struct BusinessSelectionScreen: View {
var text = "=== RAW API RESPONSES ===\n\n"
text += "UserID: \(uid), BusinessID: \(bid)\n\n"
text += "--- /workers/myBusinesses.cfm ---\n"
text += "--- /workers/myBusinesses.php ---\n"
let bizRaw = await APIService.shared.debugRawJSON(
"/workers/myBusinesses.cfm", payload: ["UserID": uid])
"/workers/myBusinesses.php", payload: ["UserID": uid])
text += bizRaw + "\n\n"
if bid > 0 {
text += "--- /tasks/listPending.cfm ---\n"
text += "--- /tasks/listPending.php ---\n"
let taskRaw = await APIService.shared.debugRawJSON(
"/tasks/listPending.cfm", payload: ["BusinessID": bid])
"/tasks/listPending.php", payload: ["BusinessID": bid])
text += taskRaw + "\n\n"
}

View file

@ -518,14 +518,29 @@ struct TaskDetailScreen: View {
}
ForEach(modifiers) { mod in
// For inverted groups: show removed defaults as "NO {name}"
if mod.isInvertedGroup && !mod.removedDefaults.isEmpty {
ForEach(mod.removedDefaults, id: \.self) { removedName in
Text("- NO \(removedName)")
.font(.caption)
.foregroundColor(.red)
}
}
let children = d.lineItems.filter { $0.parentLineItemId == mod.lineItemId }
if !children.isEmpty {
ForEach(children) { child in
// Skip default items in inverted groups (they're unchanged)
if mod.isInvertedGroup && child.isCheckedByDefault {
// Don't display - this is an unchanged default
} else {
Text("- \(mod.itemName): \(child.itemName)")
.font(.caption)
.foregroundColor(.secondary)
}
} else {
}
} else if !mod.isInvertedGroup {
// Regular modifier (not inverted group)
Text("- \(mod.itemName)")
.font(.caption)
.foregroundColor(.secondary)

View file

@ -461,7 +461,7 @@ const sections = [
preconditions: 'Logged in, user has businesses.',
action: 'After login, observe Business Selection screen.',
expected: 'Nav title "Select Business". List of businesses with: initial letter in colored square, business name, address/city, status badge (Pending/Active/Inactive), task count or green checkmark. Toolbar: refresh button and ellipsis menu.',
onFail: 'Screenshot. Check API response from /workers/myBusinesses.cfm.'
onFail: 'Screenshot. Check API response from /workers/myBusinesses.php.'
},
{
id: 'BS02',
@ -532,7 +532,7 @@ const sections = [
title: 'Toolbar menu — Debug API',
preconditions: 'Business Selection screen.',
action: 'Tap ellipsis menu → "Debug API".',
expected: 'Modal sheet appears showing raw JSON from /workers/myBusinesses.cfm and /tasks/listPending.cfm. Monospaced text, "Done" button to dismiss.',
expected: 'Modal sheet appears showing raw JSON from /workers/myBusinesses.php and /tasks/listPending.php. Monospaced text, "Done" button to dismiss.',
onFail: 'Screenshot.'
},
{
@ -570,7 +570,7 @@ const sections = [
preconditions: 'Selected a business with pending tasks.',
action: 'Observe the task list screen.',
expected: 'Nav title = business name. List of task cards with: color bar, category icon (chat = bubble icon), title, location (mappin or bicycle icon), category badge, chat badge if applicable, time ago label.',
onFail: 'Screenshot. Check /tasks/listPending.cfm response.'
onFail: 'Screenshot. Check /tasks/listPending.php response.'
},
{
id: 'TL02',
@ -655,7 +655,7 @@ const sections = [
preconditions: 'Tapped a task from task list.',
action: 'Observe the task detail screen.',
expected: 'Loading spinner, then content: customer section (avatar/name/phone), location section (table or delivery), nav bar colored with category color. "Accept Task" button at bottom.',
onFail: 'Screenshot. Check /tasks/getDetails.cfm response.'
onFail: 'Screenshot. Check /tasks/getDetails.php response.'
},
{
id: 'TD02',
@ -750,7 +750,7 @@ const sections = [
title: 'Accept Task — confirm',
preconditions: 'Accept alert shown.',
action: 'Tap "Accept".',
expected: 'API call to /tasks/accept.cfm. On success, screen dismisses back to task list. Task removed from pending list.',
expected: 'API call to /tasks/accept.php. On success, screen dismisses back to task list. Task removed from pending list.',
onFail: 'Screenshot. Check API response.'
},
{
@ -782,7 +782,7 @@ const sections = [
title: 'Complete Task — confirm',
preconditions: 'Complete alert shown.',
action: 'Tap "Complete".',
expected: 'API call to /tasks/complete.cfm. On success, dismisses back to My Tasks. Task moves to Completed filter.',
expected: 'API call to /tasks/complete.php. On success, dismisses back to My Tasks. Task moves to Completed filter.',
onFail: 'Screenshot.'
},
{
@ -844,7 +844,7 @@ const sections = [
preconditions: 'Worker has accepted tasks.',
action: 'Select Active tab.',
expected: 'Shows tasks with status Accepted/In Progress. Each card shows: color bar, title, location, category badge, time ago, status badge.',
onFail: 'Screenshot. Verify /tasks/listMine.cfm?FilterType=active response.'
onFail: 'Screenshot. Verify /tasks/listMine.php?FilterType=active response.'
},
{
id: 'MT03',
@ -1008,7 +1008,7 @@ const sections = [
title: 'HTTP fallback when WebSocket disconnected',
preconditions: 'WebSocket not connected.',
action: 'Send a message.',
expected: 'Message sent via HTTP POST to /chat/sendMessage.cfm. Messages poll via 3-second timer.',
expected: 'Message sent via HTTP POST to /chat/sendMessage.php. Messages poll via 3-second timer.',
onFail: 'Note if message fails or gets lost.'
},
{
@ -1030,7 +1030,7 @@ const sections = [
preconditions: 'Navigated to Account from menu.',
action: 'Observe Account screen.',
expected: 'Nav title "Account". Loading spinner, then: Payout Status card, Activation card, Earnings card, Logout button. FAB in bottom-right.',
onFail: 'Screenshot. Check /workers/tierStatus.cfm response.'
onFail: 'Screenshot. Check /workers/tierStatus.php response.'
},
{
id: 'AC02',
@ -1085,7 +1085,7 @@ const sections = [
title: 'Early unlock button',
preconditions: 'Activation not complete.',
action: 'Tap "Complete activation now (optional)".',
expected: 'API call to /workers/earlyUnlock.cfm. Opens URL in Safari. Reloads after return.',
expected: 'API call to /workers/earlyUnlock.php. Opens URL in Safari. Reloads after return.',
onFail: 'Screenshot.'
},
{