1
+ (ns parts.frontend.utils.api
2
+ (:require
3
+ [cljs.core.async :refer [chan put! <!]]
4
+ [cljs.core.async.interop :refer-macros [<p!]])
5
+ (:require-macros
6
+ [cljs.core.async.macros :refer [go]]))
7
+
8
+ (def ^:private token-storage-key " parts-auth-tokens" )
9
+ (def ^:private user-email-key " parts-user-email" )
10
+ (def csrf-token-name " __anti-forgery-token" )
11
+
12
+ (defn save-tokens
13
+ " Save authentication tokens to local storage"
14
+ [tokens]
15
+ (.setItem js/localStorage token-storage-key (js/JSON.stringify (clj->js tokens))))
16
+
17
+ (defn get-tokens
18
+ " Get authentication tokens from local storage"
19
+ []
20
+ (when-let [tokens-str (.getItem js/localStorage token-storage-key)]
21
+ (js->clj (.parse js/JSON tokens-str) :keywordize-keys true )))
22
+
23
+ (defn save-user-email
24
+ " Save user email to local storage"
25
+ [email]
26
+ (.setItem js/localStorage user-email-key email))
27
+
28
+ (defn get-user-email
29
+ " Get user email from local storage"
30
+ []
31
+ (.getItem js/localStorage user-email-key))
32
+
33
+ (defn clear-tokens
34
+ " Clear authentication tokens from local storage"
35
+ []
36
+ (.removeItem js/localStorage token-storage-key)
37
+ (.removeItem js/localStorage user-email-key))
38
+
39
+ (defn get-auth-header
40
+ " Get the Authorization header for authenticated requests"
41
+ []
42
+ (when-let [tokens (get-tokens )]
43
+ (str (:token_type tokens) " " (:access_token tokens))))
44
+
45
+ (defn get-csrf-token
46
+ " Get the CSRF token from the meta tag"
47
+ []
48
+ (when-let [meta-tag (.querySelector js/document " meta[name='csrf-token']" )]
49
+ (.getAttribute meta-tag " content" )))
50
+
51
+ (defn api-request
52
+ " Make a request to the API, optionally with authentication"
53
+ [{:keys [url method data authenticated? as-form?]
54
+ :or {method " GET"
55
+ authenticated? false
56
+ as-form? false }}]
57
+ (let [result-chan (chan )
58
+ csrf-token (get-csrf-token )
59
+ headers (js/Object. )
60
+ _ (when (not as-form?)
61
+ (aset headers " Content-Type" " application/json" ))
62
+ _ (when authenticated?
63
+ (when-let [auth-header (get-auth-header )]
64
+ (aset headers " Authorization" auth-header)))
65
+ _ (js/console.log " API request:" , url, method, as-form?, " data:" , data)]
66
+ (go
67
+ (try
68
+ (let [request-data (if data
69
+ data
70
+ {})
71
+ fetch-opts (if as-form?
72
+ ; ; For form data submission
73
+ (let [form-data (js/URLSearchParams. )]
74
+ ; ; Add all data as form fields
75
+ (doseq [[k v] request-data]
76
+ (when v
77
+ (js/console.log " Adding to form data:" (name k) v)
78
+ (.append form-data (name k) v)))
79
+
80
+ ; ; Set proper content type for form submissions
81
+ (aset headers " Content-Type" " application/x-www-form-urlencoded" )
82
+
83
+ ; ; Create fetch options
84
+ (clj->js {:method method
85
+ :headers headers
86
+ :body form-data}))
87
+ ; ; For JSON, use the standard approach
88
+ (clj->js {:method method
89
+ :headers headers
90
+ :body (js/JSON.stringify (clj->js request-data))}))
91
+ response (<p! (js/fetch url fetch-opts))
92
+ status (.-status response)
93
+ content-type (.get (.-headers response) " content-type" )
94
+ _ (js/console.log " Response content type:" , content-type)
95
+ is-json (when content-type (re-find #"application/json" content-type))
96
+ body (<p! (if is-json
97
+ (.json response)
98
+ (.text response)))
99
+ parsed-body (if is-json (js->clj body :keywordize-keys true ) body)]
100
+ (js/console.log " API Response:" , status, parsed-body)
101
+ (if (< status 400 )
102
+ (do
103
+ (js/console.log " API Success:" , parsed-body)
104
+ (put! result-chan {:success true :data parsed-body}))
105
+ (do
106
+ (js/console.log " API Error:" , status, parsed-body)
107
+ (put! result-chan {:success false :error parsed-body}))))
108
+ (catch js/Error e
109
+ (put! result-chan {:success false :error (.-message e)}))))
110
+ result-chan))
111
+
112
+ (defn login
113
+ " Attempt to log in with email and password"
114
+ [email password csrf-token]
115
+ (js/console.log " Attempting login with:" email " token:" csrf-token)
116
+ (api-request
117
+ {:url " /api/auth/login"
118
+ :method " POST"
119
+ :as-form? true
120
+ :data {" email" email
121
+ " password" password
122
+ " __anti-forgery-token" csrf-token}}))
123
+
124
+ (defn logout
125
+ " Log out the current user"
126
+ []
127
+ (let [tokens (get-tokens )
128
+ csrf-token (get-csrf-token )
129
+ result-chan (chan )]
130
+ (if tokens
131
+ (go
132
+ (let [response (<! (api-request
133
+ {:url " /api/auth/logout"
134
+ :method " POST"
135
+ :authenticated? true
136
+ :as-form? true
137
+ :data {" refresh_token" (:refresh_token tokens)
138
+ " __anti-forgery-token" csrf-token}}))]
139
+ (clear-tokens )
140
+ (put! result-chan {:success true })))
141
+ (put! result-chan {:success true }))
142
+ result-chan))
143
+
144
+ (defn get-user-info
145
+ " Get the current user's information"
146
+ []
147
+ (api-request
148
+ {:url " /api/account"
149
+ :method " GET"
150
+ :authenticated? true }))
0 commit comments