@veristone/nuxt-v-app 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/composables/useVCrud.ts +58 -35
- package/app/composables/useVFetch.ts +27 -2
- package/app/pages/test-api-auth.vue +223 -0
- package/package.json +1 -1
|
@@ -207,12 +207,28 @@ export const useVCrud = <T = any>(endpoint: string, options: VCrudOptions<T> = {
|
|
|
207
207
|
showError(title, message)
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
|
|
210
|
+
/**
|
|
211
|
+
* Get auth token from cookies
|
|
212
|
+
* Reads the stack_auth_access cookie for Bearer token
|
|
213
|
+
*/
|
|
214
|
+
const getAuthToken = (): string | null => {
|
|
215
|
+
const tokenCookie = useCookie('stack_auth_access')
|
|
216
|
+
return tokenCookie.value || null
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Get auth headers with Bearer token
|
|
211
220
|
const getAuthHeaders = () => {
|
|
212
|
-
|
|
221
|
+
const headers: Record<string, string> = {
|
|
213
222
|
'Content-Type': 'application/json',
|
|
214
223
|
'X-Client-Version': 'v-app-1.0'
|
|
215
224
|
}
|
|
225
|
+
|
|
226
|
+
const token = getAuthToken()
|
|
227
|
+
if (token) {
|
|
228
|
+
headers['Authorization'] = `Bearer ${token}`
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return headers
|
|
216
232
|
}
|
|
217
233
|
|
|
218
234
|
// Get base URL
|
|
@@ -342,11 +358,12 @@ export const useVCrud = <T = any>(endpoint: string, options: VCrudOptions<T> = {
|
|
|
342
358
|
})
|
|
343
359
|
}
|
|
344
360
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
361
|
+
const result = await $fetch<T>(constructUrl(endpoint), {
|
|
362
|
+
method: 'POST',
|
|
363
|
+
baseURL: getBaseUrl(),
|
|
364
|
+
headers: getMutationHeaders(body),
|
|
365
|
+
body
|
|
366
|
+
})
|
|
350
367
|
|
|
351
368
|
notifyOk('Success', 'Record created successfully.')
|
|
352
369
|
if (onCreated) onCreated(result)
|
|
@@ -394,11 +411,12 @@ export const useVCrud = <T = any>(endpoint: string, options: VCrudOptions<T> = {
|
|
|
394
411
|
mergedParams.order = sortConfig.value.direction
|
|
395
412
|
}
|
|
396
413
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
414
|
+
const response = await $fetch<any>(constructUrl(endpoint), {
|
|
415
|
+
method: 'GET',
|
|
416
|
+
baseURL: getBaseUrl(),
|
|
417
|
+
headers: getAuthHeaders(),
|
|
418
|
+
params: mergedParams
|
|
419
|
+
})
|
|
402
420
|
|
|
403
421
|
// Handle paginated responses: { data: [...], pagination: {...} }
|
|
404
422
|
// Return just the data array if response is paginated, otherwise return as-is
|
|
@@ -429,10 +447,11 @@ export const useVCrud = <T = any>(endpoint: string, options: VCrudOptions<T> = {
|
|
|
429
447
|
lastError.value = null
|
|
430
448
|
|
|
431
449
|
try {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
450
|
+
const result = await $fetch<T>(constructUrl(endpoint, id), {
|
|
451
|
+
method: 'GET',
|
|
452
|
+
baseURL: getBaseUrl(),
|
|
453
|
+
headers: getAuthHeaders()
|
|
454
|
+
})
|
|
436
455
|
|
|
437
456
|
detailData.value = result
|
|
438
457
|
syncFormData(result)
|
|
@@ -464,11 +483,12 @@ export const useVCrud = <T = any>(endpoint: string, options: VCrudOptions<T> = {
|
|
|
464
483
|
})
|
|
465
484
|
}
|
|
466
485
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
486
|
+
const result = await $fetch<T>(constructUrl(endpoint, id), {
|
|
487
|
+
method: 'PUT',
|
|
488
|
+
baseURL: getBaseUrl(),
|
|
489
|
+
headers: getMutationHeaders(body),
|
|
490
|
+
body
|
|
491
|
+
})
|
|
472
492
|
|
|
473
493
|
detailData.value = result
|
|
474
494
|
notifyOk('Saved', 'Changes saved successfully.')
|
|
@@ -502,11 +522,12 @@ export const useVCrud = <T = any>(endpoint: string, options: VCrudOptions<T> = {
|
|
|
502
522
|
})
|
|
503
523
|
}
|
|
504
524
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
525
|
+
const result = await $fetch<T>(constructUrl(endpoint, id), {
|
|
526
|
+
method: 'PATCH',
|
|
527
|
+
baseURL: getBaseUrl(),
|
|
528
|
+
headers: getMutationHeaders(body),
|
|
529
|
+
body
|
|
530
|
+
})
|
|
510
531
|
|
|
511
532
|
detailData.value = result
|
|
512
533
|
notifyOk('Saved', 'Record updated.')
|
|
@@ -528,10 +549,11 @@ export const useVCrud = <T = any>(endpoint: string, options: VCrudOptions<T> = {
|
|
|
528
549
|
isDeleting.value = true
|
|
529
550
|
|
|
530
551
|
try {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
552
|
+
await $fetch(constructUrl(endpoint, id), {
|
|
553
|
+
method: 'DELETE',
|
|
554
|
+
baseURL: getBaseUrl(),
|
|
555
|
+
headers: getAuthHeaders()
|
|
556
|
+
})
|
|
535
557
|
|
|
536
558
|
notifyOk('Deleted', 'Record removed permanently.')
|
|
537
559
|
if (onDeleted) onDeleted()
|
|
@@ -559,11 +581,12 @@ export const useVCrud = <T = any>(endpoint: string, options: VCrudOptions<T> = {
|
|
|
559
581
|
isDeleting.value = true
|
|
560
582
|
|
|
561
583
|
try {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
584
|
+
await $fetch(constructUrl(endpoint) + '/bulk-delete', {
|
|
585
|
+
method: 'DELETE',
|
|
586
|
+
baseURL: getBaseUrl(),
|
|
587
|
+
headers: getAuthHeaders(),
|
|
588
|
+
body: { ids }
|
|
589
|
+
})
|
|
567
590
|
|
|
568
591
|
notifyOk('Batch Deleted', `${ids.length} records removed.`)
|
|
569
592
|
if (onDeleted) onDeleted()
|
|
@@ -5,6 +5,31 @@
|
|
|
5
5
|
import type { UseFetchOptions } from 'nuxt/app'
|
|
6
6
|
import { useVToast } from './useVToast'
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Get auth token from cookies
|
|
10
|
+
* Reads the stack_auth_access cookie for Bearer token
|
|
11
|
+
*/
|
|
12
|
+
function getAuthToken(): string | null {
|
|
13
|
+
const tokenCookie = useCookie('stack_auth_access')
|
|
14
|
+
return tokenCookie.value || null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get default auth headers including Bearer token
|
|
19
|
+
*/
|
|
20
|
+
function getAuthHeaders(): Record<string, string> {
|
|
21
|
+
const headers: Record<string, string> = {
|
|
22
|
+
'X-Client-Version': 'v-app-1.0'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const token = getAuthToken()
|
|
26
|
+
if (token) {
|
|
27
|
+
headers['Authorization'] = `Bearer ${token}`
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return headers
|
|
31
|
+
}
|
|
32
|
+
|
|
8
33
|
export async function useVFetch<T>(
|
|
9
34
|
endpoint: string,
|
|
10
35
|
opts: UseFetchOptions<T> = {},
|
|
@@ -13,13 +38,13 @@ export async function useVFetch<T>(
|
|
|
13
38
|
const { error: notifyError } = useVToast()
|
|
14
39
|
const runtimeConfig = useRuntimeConfig()
|
|
15
40
|
|
|
16
|
-
// Construct Base Config
|
|
41
|
+
// Construct Base Config with auth headers
|
|
17
42
|
const fetchConfig: UseFetchOptions<T> = {
|
|
18
43
|
baseURL: (runtimeConfig.public.apiUrl as string) || '/',
|
|
19
44
|
retry: 1,
|
|
20
45
|
retryStatusCodes: [408, 409, 429, 500, 502, 503, 504], // Extended retry codes
|
|
21
46
|
headers: {
|
|
22
|
-
|
|
47
|
+
...getAuthHeaders(),
|
|
23
48
|
...opts.headers
|
|
24
49
|
},
|
|
25
50
|
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const endpoint = 'http://localhost:3002/admin/portfolio/loans'
|
|
3
|
+
|
|
4
|
+
const crud = useVCrud(endpoint, { immediate: false })
|
|
5
|
+
|
|
6
|
+
// Store results for each method
|
|
7
|
+
const results = ref<Record<string, any>>({})
|
|
8
|
+
const loading = ref<Record<string, boolean>>({})
|
|
9
|
+
const errors = ref<Record<string, string>>({})
|
|
10
|
+
|
|
11
|
+
// Test GET (findAll)
|
|
12
|
+
const testGet = async () => {
|
|
13
|
+
loading.value.get = true
|
|
14
|
+
errors.value.get = ''
|
|
15
|
+
try {
|
|
16
|
+
const data = await crud.findAll()
|
|
17
|
+
results.value.get = data
|
|
18
|
+
} catch (err: any) {
|
|
19
|
+
errors.value.get = err.message || 'Failed'
|
|
20
|
+
} finally {
|
|
21
|
+
loading.value.get = false
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Test GET single (findOne) - using first ID if available
|
|
26
|
+
const testGetOne = async () => {
|
|
27
|
+
const testId = 'test-id-123'
|
|
28
|
+
loading.value.getOne = true
|
|
29
|
+
errors.value.getOne = ''
|
|
30
|
+
try {
|
|
31
|
+
const data = await crud.findOne(testId)
|
|
32
|
+
results.value.getOne = data
|
|
33
|
+
} catch (err: any) {
|
|
34
|
+
errors.value.getOne = err.message || 'Failed'
|
|
35
|
+
} finally {
|
|
36
|
+
loading.value.getOne = false
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Test POST (create)
|
|
41
|
+
const testPost = async () => {
|
|
42
|
+
loading.value.post = true
|
|
43
|
+
errors.value.post = ''
|
|
44
|
+
try {
|
|
45
|
+
const data = await crud.create({ name: 'Test Loan', amount: 1000 })
|
|
46
|
+
results.value.post = data
|
|
47
|
+
} catch (err: any) {
|
|
48
|
+
errors.value.post = err.message || 'Failed'
|
|
49
|
+
} finally {
|
|
50
|
+
loading.value.post = false
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Test PUT (update)
|
|
55
|
+
const testPut = async () => {
|
|
56
|
+
const testId = 'test-id-123'
|
|
57
|
+
loading.value.put = true
|
|
58
|
+
errors.value.put = ''
|
|
59
|
+
try {
|
|
60
|
+
const data = await crud.update(testId, { name: 'Updated Loan', amount: 2000 })
|
|
61
|
+
results.value.put = data
|
|
62
|
+
} catch (err: any) {
|
|
63
|
+
errors.value.put = err.message || 'Failed'
|
|
64
|
+
} finally {
|
|
65
|
+
loading.value.put = false
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Test PATCH
|
|
70
|
+
const testPatch = async () => {
|
|
71
|
+
const testId = 'test-id-123'
|
|
72
|
+
loading.value.patch = true
|
|
73
|
+
errors.value.patch = ''
|
|
74
|
+
try {
|
|
75
|
+
const data = await crud.patch(testId, { amount: 1500 })
|
|
76
|
+
results.value.patch = data
|
|
77
|
+
} catch (err: any) {
|
|
78
|
+
errors.value.patch = err.message || 'Failed'
|
|
79
|
+
} finally {
|
|
80
|
+
loading.value.patch = false
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Test DELETE
|
|
85
|
+
const testDelete = async () => {
|
|
86
|
+
const testId = 'test-id-123'
|
|
87
|
+
loading.value.delete = true
|
|
88
|
+
errors.value.delete = ''
|
|
89
|
+
try {
|
|
90
|
+
const data = await crud.remove(testId)
|
|
91
|
+
results.value.delete = data
|
|
92
|
+
} catch (err: any) {
|
|
93
|
+
errors.value.delete = err.message || 'Failed'
|
|
94
|
+
} finally {
|
|
95
|
+
loading.value.delete = false
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Run all tests
|
|
100
|
+
const testAll = async () => {
|
|
101
|
+
await testGet()
|
|
102
|
+
await testGetOne()
|
|
103
|
+
await testPost()
|
|
104
|
+
await testPut()
|
|
105
|
+
await testPatch()
|
|
106
|
+
await testDelete()
|
|
107
|
+
}
|
|
108
|
+
</script>
|
|
109
|
+
|
|
110
|
+
<template>
|
|
111
|
+
<div class="p-8 space-y-8">
|
|
112
|
+
<div class="flex items-center justify-between">
|
|
113
|
+
<h1 class="text-2xl font-bold">API Methods Test</h1>
|
|
114
|
+
<button
|
|
115
|
+
@click="testAll"
|
|
116
|
+
class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
|
|
117
|
+
>
|
|
118
|
+
Test All Methods
|
|
119
|
+
</button>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<!-- GET (findAll) -->
|
|
123
|
+
<div class="border p-4 rounded">
|
|
124
|
+
<div class="flex items-center justify-between mb-4">
|
|
125
|
+
<h2 class="text-xl font-semibold">GET (findAll)</h2>
|
|
126
|
+
<button
|
|
127
|
+
@click="testGet"
|
|
128
|
+
:disabled="loading.get"
|
|
129
|
+
class="px-3 py-1 bg-green-500 text-white rounded hover:bg-green-600 disabled:opacity-50"
|
|
130
|
+
>
|
|
131
|
+
{{ loading.get ? 'Loading...' : 'Test GET' }}
|
|
132
|
+
</button>
|
|
133
|
+
</div>
|
|
134
|
+
<div v-if="errors.get" class="text-red-500 mb-2">Error: {{ errors.get }}</div>
|
|
135
|
+
<pre v-if="results.get" class="text-sm bg-gray-100 p-4 rounded overflow-auto max-h-48">{{ JSON.stringify(results.get, null, 2) }}</pre>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<!-- GET One (findOne) -->
|
|
139
|
+
<div class="border p-4 rounded">
|
|
140
|
+
<div class="flex items-center justify-between mb-4">
|
|
141
|
+
<h2 class="text-xl font-semibold">GET Single (findOne)</h2>
|
|
142
|
+
<button
|
|
143
|
+
@click="testGetOne"
|
|
144
|
+
:disabled="loading.getOne"
|
|
145
|
+
class="px-3 py-1 bg-green-500 text-white rounded hover:bg-green-600 disabled:opacity-50"
|
|
146
|
+
>
|
|
147
|
+
{{ loading.getOne ? 'Loading...' : 'Test GET One' }}
|
|
148
|
+
</button>
|
|
149
|
+
</div>
|
|
150
|
+
<div class="text-sm text-gray-500 mb-2">Testing with ID: test-id-123</div>
|
|
151
|
+
<div v-if="errors.getOne" class="text-red-500 mb-2">Error: {{ errors.getOne }}</div>
|
|
152
|
+
<pre v-if="results.getOne" class="text-sm bg-gray-100 p-4 rounded overflow-auto max-h-48">{{ JSON.stringify(results.getOne, null, 2) }}</pre>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<!-- POST (create) -->
|
|
156
|
+
<div class="border p-4 rounded">
|
|
157
|
+
<div class="flex items-center justify-between mb-4">
|
|
158
|
+
<h2 class="text-xl font-semibold">POST (create)</h2>
|
|
159
|
+
<button
|
|
160
|
+
@click="testPost"
|
|
161
|
+
:disabled="loading.post"
|
|
162
|
+
class="px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50"
|
|
163
|
+
>
|
|
164
|
+
{{ loading.post ? 'Loading...' : 'Test POST' }}
|
|
165
|
+
</button>
|
|
166
|
+
</div>
|
|
167
|
+
<div class="text-sm text-gray-500 mb-2">Payload: { name: 'Test Loan', amount: 1000 }</div>
|
|
168
|
+
<div v-if="errors.post" class="text-red-500 mb-2">Error: {{ errors.post }}</div>
|
|
169
|
+
<pre v-if="results.post" class="text-sm bg-gray-100 p-4 rounded overflow-auto max-h-48">{{ JSON.stringify(results.post, null, 2) }}</pre>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<!-- PUT (update) -->
|
|
173
|
+
<div class="border p-4 rounded">
|
|
174
|
+
<div class="flex items-center justify-between mb-4">
|
|
175
|
+
<h2 class="text-xl font-semibold">PUT (update)</h2>
|
|
176
|
+
<button
|
|
177
|
+
@click="testPut"
|
|
178
|
+
:disabled="loading.put"
|
|
179
|
+
class="px-3 py-1 bg-yellow-500 text-white rounded hover:bg-yellow-600 disabled:opacity-50"
|
|
180
|
+
>
|
|
181
|
+
{{ loading.put ? 'Loading...' : 'Test PUT' }}
|
|
182
|
+
</button>
|
|
183
|
+
</div>
|
|
184
|
+
<div class="text-sm text-gray-500 mb-2">Testing with ID: test-id-123 | Payload: { name: 'Updated Loan', amount: 2000 }</div>
|
|
185
|
+
<div v-if="errors.put" class="text-red-500 mb-2">Error: {{ errors.put }}</div>
|
|
186
|
+
<pre v-if="results.put" class="text-sm bg-gray-100 p-4 rounded overflow-auto max-h-48">{{ JSON.stringify(results.put, null, 2) }}</pre>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<!-- PATCH -->
|
|
190
|
+
<div class="border p-4 rounded">
|
|
191
|
+
<div class="flex items-center justify-between mb-4">
|
|
192
|
+
<h2 class="text-xl font-semibold">PATCH</h2>
|
|
193
|
+
<button
|
|
194
|
+
@click="testPatch"
|
|
195
|
+
:disabled="loading.patch"
|
|
196
|
+
class="px-3 py-1 bg-purple-500 text-white rounded hover:bg-purple-600 disabled:opacity-50"
|
|
197
|
+
>
|
|
198
|
+
{{ loading.patch ? 'Loading...' : 'Test PATCH' }}
|
|
199
|
+
</button>
|
|
200
|
+
</div>
|
|
201
|
+
<div class="text-sm text-gray-500 mb-2">Testing with ID: test-id-123 | Payload: { amount: 1500 }</div>
|
|
202
|
+
<div v-if="errors.patch" class="text-red-500 mb-2">Error: {{ errors.patch }}</div>
|
|
203
|
+
<pre v-if="results.patch" class="text-sm bg-gray-100 p-4 rounded overflow-auto max-h-48">{{ JSON.stringify(results.patch, null, 2) }}</pre>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<!-- DELETE -->
|
|
207
|
+
<div class="border p-4 rounded">
|
|
208
|
+
<div class="flex items-center justify-between mb-4">
|
|
209
|
+
<h2 class="text-xl font-semibold">DELETE (remove)</h2>
|
|
210
|
+
<button
|
|
211
|
+
@click="testDelete"
|
|
212
|
+
:disabled="loading.delete"
|
|
213
|
+
class="px-3 py-1 bg-red-500 text-white rounded hover:bg-red-600 disabled:opacity-50"
|
|
214
|
+
>
|
|
215
|
+
{{ loading.delete ? 'Loading...' : 'Test DELETE' }}
|
|
216
|
+
</button>
|
|
217
|
+
</div>
|
|
218
|
+
<div class="text-sm text-gray-500 mb-2">Testing with ID: test-id-123</div>
|
|
219
|
+
<div v-if="errors.delete" class="text-red-500 mb-2">Error: {{ errors.delete }}</div>
|
|
220
|
+
<pre v-if="results.delete" class="text-sm bg-gray-100 p-4 rounded overflow-auto max-h-48">{{ JSON.stringify(results.delete, null, 2) }}</pre>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
</template>
|