@codingfactory/socialkit-vue 0.1.0
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/dist/composables/useAuth.d.ts +27 -0
- package/dist/composables/useAuth.d.ts.map +1 -0
- package/dist/composables/useAuth.js +137 -0
- package/dist/composables/useAuth.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/terminology/index.d.ts +11 -0
- package/dist/plugins/terminology/index.d.ts.map +1 -0
- package/dist/plugins/terminology/index.js +91 -0
- package/dist/plugins/terminology/index.js.map +1 -0
- package/dist/plugins/terminology/terms.d.ts +15 -0
- package/dist/plugins/terminology/terms.d.ts.map +1 -0
- package/dist/plugins/terminology/terms.js +72 -0
- package/dist/plugins/terminology/terms.js.map +1 -0
- package/dist/plugins/terminology/types.d.ts +32 -0
- package/dist/plugins/terminology/types.d.ts.map +1 -0
- package/dist/plugins/terminology/types.js +2 -0
- package/dist/plugins/terminology/types.js.map +1 -0
- package/dist/services/api.d.ts +50 -0
- package/dist/services/api.d.ts.map +1 -0
- package/dist/services/api.js +305 -0
- package/dist/services/api.js.map +1 -0
- package/dist/services/auth.d.ts +127 -0
- package/dist/services/auth.d.ts.map +1 -0
- package/dist/services/auth.js +562 -0
- package/dist/services/auth.js.map +1 -0
- package/dist/stores/auth.d.ts +174 -0
- package/dist/stores/auth.d.ts.map +1 -0
- package/dist/stores/auth.js +262 -0
- package/dist/stores/auth.js.map +1 -0
- package/dist/types/api.d.ts +52 -0
- package/dist/types/api.d.ts.map +1 -0
- package/dist/types/api.js +7 -0
- package/dist/types/api.js.map +1 -0
- package/dist/types/user.d.ts +42 -0
- package/dist/types/user.d.ts.map +1 -0
- package/dist/types/user.js +45 -0
- package/dist/types/user.js.map +1 -0
- package/dist/utils/tokenStorage.d.ts +41 -0
- package/dist/utils/tokenStorage.d.ts.map +1 -0
- package/dist/utils/tokenStorage.js +300 -0
- package/dist/utils/tokenStorage.js.map +1 -0
- package/package.json +40 -0
- package/src/composables/useAuth.ts +164 -0
- package/src/index.ts +118 -0
- package/src/plugins/terminology/index.ts +114 -0
- package/src/plugins/terminology/terms.ts +104 -0
- package/src/plugins/terminology/types.ts +28 -0
- package/src/services/api.ts +472 -0
- package/src/services/auth.ts +874 -0
- package/src/stores/auth.ts +400 -0
- package/src/types/api.ts +56 -0
- package/src/types/user.ts +94 -0
- package/src/utils/tokenStorage.ts +394 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic auth store factory for SocialKit-powered frontends.
|
|
3
|
+
*
|
|
4
|
+
* Use `createAuthStoreDefinition()` to produce a Pinia store definition
|
|
5
|
+
* that each site frontend can register with its own configuration.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { defineStore } from 'pinia'
|
|
9
|
+
import { ref, computed } from 'vue'
|
|
10
|
+
import type { ComputedRef, Ref } from 'vue'
|
|
11
|
+
import type { Router } from 'vue-router'
|
|
12
|
+
import type { AuthServiceInstance, LoginResponse, RegisterData } from '../services/auth.js'
|
|
13
|
+
import type { ApiService } from '../services/api.js'
|
|
14
|
+
import type { TokenStorage } from '../utils/tokenStorage.js'
|
|
15
|
+
import type { User } from '../types/user.js'
|
|
16
|
+
|
|
17
|
+
export interface AuthStoreStateRefs {
|
|
18
|
+
user: Ref<User | null>
|
|
19
|
+
isAuthenticated: Ref<boolean>
|
|
20
|
+
loading: Ref<boolean>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface AuthStoreGetters {
|
|
24
|
+
currentUser: ComputedRef<User | null>
|
|
25
|
+
isLoggedIn: ComputedRef<boolean>
|
|
26
|
+
token: ComputedRef<string | null>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface AuthStoreActions {
|
|
30
|
+
login: (loginCredential: string, password: string, redirectPath?: string, remember?: boolean) => Promise<LoginResponse>
|
|
31
|
+
register: (data: RegisterData) => Promise<LoginResponse>
|
|
32
|
+
logout: () => Promise<void>
|
|
33
|
+
refreshSessionAndReplay: () => Promise<boolean>
|
|
34
|
+
fetchUser: () => Promise<User | null>
|
|
35
|
+
checkAuth: (forceValidation?: boolean) => Promise<void>
|
|
36
|
+
setUser: (newUser: User) => void
|
|
37
|
+
hasStoredToken: () => boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface AuthStoreExtensionContext {
|
|
41
|
+
state: AuthStoreStateRefs
|
|
42
|
+
getters: AuthStoreGetters
|
|
43
|
+
actions: AuthStoreActions
|
|
44
|
+
services: {
|
|
45
|
+
authService: AuthServiceInstance
|
|
46
|
+
apiService: ApiService
|
|
47
|
+
tokenStorage: TokenStorage
|
|
48
|
+
router: Router
|
|
49
|
+
}
|
|
50
|
+
config: {
|
|
51
|
+
storeId: string
|
|
52
|
+
defaultLoginRedirect: string
|
|
53
|
+
}
|
|
54
|
+
resolveSafeRedirectPath: (redirectPath?: string) => string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type AuthStoreExtension = Record<string, unknown>
|
|
58
|
+
|
|
59
|
+
/** Configuration for creating the auth store definition. */
|
|
60
|
+
export interface AuthStoreConfig<TExtension extends AuthStoreExtension = Record<string, never>> {
|
|
61
|
+
/** Pinia store id (default: 'auth'). */
|
|
62
|
+
storeId?: string
|
|
63
|
+
/** Auth service instance. */
|
|
64
|
+
authService: AuthServiceInstance
|
|
65
|
+
/** API service instance (for pending auth request management). */
|
|
66
|
+
apiService: ApiService
|
|
67
|
+
/** Token storage instance. */
|
|
68
|
+
tokenStorage: TokenStorage
|
|
69
|
+
/** Vue Router instance. */
|
|
70
|
+
router: Router
|
|
71
|
+
/** Default redirect path after login (default: '/'). */
|
|
72
|
+
defaultLoginRedirect?: string
|
|
73
|
+
/** Extra Pinia store options (e.g. persist config from pinia-plugin-persistedstate). */
|
|
74
|
+
storeOptions?: Record<string, unknown>
|
|
75
|
+
/** Hook called during logout to reset app-specific stores. */
|
|
76
|
+
onLogout?: () => Promise<void> | void
|
|
77
|
+
/** Hook called after the user object is updated. */
|
|
78
|
+
onUserUpdated?: (prev: User | null, next: User) => void
|
|
79
|
+
/** Callback to set the validating-auth flag on the auth service layer. */
|
|
80
|
+
setValidatingAuth?: (value: boolean) => void
|
|
81
|
+
/** Add app-specific state, getters, and actions without forking the generic auth core. */
|
|
82
|
+
extend?: (context: AuthStoreExtensionContext) => TExtension
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type AuthStoreReturn = ReturnType<ReturnType<typeof createAuthStoreDefinition>>
|
|
86
|
+
|
|
87
|
+
/** Create a Pinia auth store definition configured for a specific site. */
|
|
88
|
+
export function createAuthStoreDefinition<TExtension extends AuthStoreExtension = Record<string, never>>(config: AuthStoreConfig<TExtension>) {
|
|
89
|
+
const {
|
|
90
|
+
storeId = 'auth',
|
|
91
|
+
authService,
|
|
92
|
+
apiService,
|
|
93
|
+
tokenStorage,
|
|
94
|
+
router,
|
|
95
|
+
defaultLoginRedirect = '/',
|
|
96
|
+
storeOptions = {},
|
|
97
|
+
onLogout,
|
|
98
|
+
onUserUpdated,
|
|
99
|
+
setValidatingAuth,
|
|
100
|
+
extend
|
|
101
|
+
} = config
|
|
102
|
+
|
|
103
|
+
const EXTERNAL_PROTOCOL_PATTERN = /^[a-z][a-z\d+\-.]*:/i
|
|
104
|
+
|
|
105
|
+
return defineStore(storeId, () => {
|
|
106
|
+
// State
|
|
107
|
+
const user = ref<User | null>(null)
|
|
108
|
+
const isAuthenticated = ref(false)
|
|
109
|
+
const loading = ref(false)
|
|
110
|
+
|
|
111
|
+
// Getters
|
|
112
|
+
const currentUser = computed(() => user.value)
|
|
113
|
+
const isLoggedIn = computed(() => isAuthenticated.value)
|
|
114
|
+
const token = computed<string | null>(() => tokenStorage.getToken())
|
|
115
|
+
|
|
116
|
+
function resolveSafeRedirectPath(redirectPath?: string): string {
|
|
117
|
+
if (!redirectPath) {
|
|
118
|
+
return defaultLoginRedirect
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const trimmed = redirectPath.trim()
|
|
122
|
+
|
|
123
|
+
if (trimmed === '') {
|
|
124
|
+
return defaultLoginRedirect
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (EXTERNAL_PROTOCOL_PATTERN.test(trimmed)) {
|
|
128
|
+
return defaultLoginRedirect
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!trimmed.startsWith('/') || trimmed.startsWith('//')) {
|
|
132
|
+
return defaultLoginRedirect
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return trimmed
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Actions
|
|
139
|
+
async function login(loginCredential: string, password: string, redirectPath?: string, remember = false) {
|
|
140
|
+
loading.value = true
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const response = await authService.login(loginCredential, password, remember)
|
|
144
|
+
user.value = response.user
|
|
145
|
+
isAuthenticated.value = true
|
|
146
|
+
|
|
147
|
+
await fetchUser()
|
|
148
|
+
|
|
149
|
+
const destination = resolveSafeRedirectPath(redirectPath)
|
|
150
|
+
router.push(destination)
|
|
151
|
+
|
|
152
|
+
return response
|
|
153
|
+
} catch (error) {
|
|
154
|
+
isAuthenticated.value = false
|
|
155
|
+
user.value = null
|
|
156
|
+
throw error
|
|
157
|
+
} finally {
|
|
158
|
+
loading.value = false
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function register(data: {
|
|
163
|
+
handle: string
|
|
164
|
+
name: string
|
|
165
|
+
email: string
|
|
166
|
+
password: string
|
|
167
|
+
password_confirmation: string
|
|
168
|
+
}) {
|
|
169
|
+
loading.value = true
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const response = await authService.register(data)
|
|
173
|
+
user.value = response.user
|
|
174
|
+
isAuthenticated.value = true
|
|
175
|
+
|
|
176
|
+
await fetchUser()
|
|
177
|
+
router.push(defaultLoginRedirect)
|
|
178
|
+
|
|
179
|
+
return response
|
|
180
|
+
} catch (error) {
|
|
181
|
+
isAuthenticated.value = false
|
|
182
|
+
user.value = null
|
|
183
|
+
throw error
|
|
184
|
+
} finally {
|
|
185
|
+
loading.value = false
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function logout() {
|
|
190
|
+
loading.value = true
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
await authService.logout()
|
|
194
|
+
} finally {
|
|
195
|
+
apiService.clearPendingAuthRequest()
|
|
196
|
+
|
|
197
|
+
if (onLogout) {
|
|
198
|
+
try {
|
|
199
|
+
await onLogout()
|
|
200
|
+
} catch {
|
|
201
|
+
// Swallow store-reset errors during logout
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
user.value = null
|
|
206
|
+
isAuthenticated.value = false
|
|
207
|
+
loading.value = false
|
|
208
|
+
|
|
209
|
+
const currentPath = window.location.pathname
|
|
210
|
+
if (currentPath !== '/login' && currentPath !== '/register') {
|
|
211
|
+
router.push('/login')
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function refreshSessionAndReplay(): Promise<boolean> {
|
|
217
|
+
const refreshedToken = await tokenStorage.tryRefreshToken()
|
|
218
|
+
|
|
219
|
+
if (!refreshedToken) {
|
|
220
|
+
apiService.clearPendingAuthRequest()
|
|
221
|
+
user.value = null
|
|
222
|
+
isAuthenticated.value = false
|
|
223
|
+
tokenStorage.removeToken()
|
|
224
|
+
return false
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
await fetchUser()
|
|
229
|
+
} catch {
|
|
230
|
+
apiService.clearPendingAuthRequest()
|
|
231
|
+
user.value = null
|
|
232
|
+
isAuthenticated.value = false
|
|
233
|
+
tokenStorage.removeToken()
|
|
234
|
+
return false
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
apiService.markAuthRecoveryHandled()
|
|
238
|
+
|
|
239
|
+
if (apiService.hasPendingAuthRequest()) {
|
|
240
|
+
try {
|
|
241
|
+
await apiService.replayPendingAuthRequest(refreshedToken)
|
|
242
|
+
} catch {
|
|
243
|
+
// Keep session; callers can retry replay manually
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return true
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function fetchUser() {
|
|
251
|
+
loading.value = true
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
const prev = user.value ? { ...user.value } : null
|
|
255
|
+
const next = await authService.getUser()
|
|
256
|
+
|
|
257
|
+
user.value = { ...next }
|
|
258
|
+
isAuthenticated.value = true
|
|
259
|
+
|
|
260
|
+
if (onUserUpdated) {
|
|
261
|
+
onUserUpdated(prev, next)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return user.value
|
|
265
|
+
} catch (error) {
|
|
266
|
+
isAuthenticated.value = false
|
|
267
|
+
user.value = null
|
|
268
|
+
throw error
|
|
269
|
+
} finally {
|
|
270
|
+
loading.value = false
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function checkAuth(forceValidation = false) {
|
|
275
|
+
const storedToken = tokenStorage.getToken()
|
|
276
|
+
|
|
277
|
+
if (!storedToken) {
|
|
278
|
+
isAuthenticated.value = false
|
|
279
|
+
user.value = null
|
|
280
|
+
return
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (!forceValidation && isAuthenticated.value && user.value) {
|
|
284
|
+
return
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (setValidatingAuth) {
|
|
288
|
+
setValidatingAuth(true)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
await fetchUser()
|
|
293
|
+
} catch {
|
|
294
|
+
isAuthenticated.value = false
|
|
295
|
+
user.value = null
|
|
296
|
+
tokenStorage.removeToken()
|
|
297
|
+
} finally {
|
|
298
|
+
if (setValidatingAuth) {
|
|
299
|
+
setValidatingAuth(false)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function setUser(newUser: User) {
|
|
305
|
+
const prev = user.value ? { ...user.value } : null
|
|
306
|
+
user.value = { ...newUser }
|
|
307
|
+
isAuthenticated.value = true
|
|
308
|
+
|
|
309
|
+
if (onUserUpdated) {
|
|
310
|
+
onUserUpdated(prev, newUser)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function hasStoredToken(): boolean {
|
|
315
|
+
const storedToken = tokenStorage.getToken()
|
|
316
|
+
|
|
317
|
+
if (storedToken === null) {
|
|
318
|
+
isAuthenticated.value = false
|
|
319
|
+
user.value = null
|
|
320
|
+
return false
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return true
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const baseState: AuthStoreStateRefs = {
|
|
327
|
+
user,
|
|
328
|
+
isAuthenticated,
|
|
329
|
+
loading
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const baseGetters: AuthStoreGetters = {
|
|
333
|
+
currentUser,
|
|
334
|
+
isLoggedIn,
|
|
335
|
+
token
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const baseActions: AuthStoreActions = {
|
|
339
|
+
login,
|
|
340
|
+
register,
|
|
341
|
+
logout,
|
|
342
|
+
refreshSessionAndReplay,
|
|
343
|
+
fetchUser,
|
|
344
|
+
checkAuth,
|
|
345
|
+
setUser,
|
|
346
|
+
hasStoredToken
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const baseStore = {
|
|
350
|
+
// State (as computed refs for readonly exposure)
|
|
351
|
+
user: computed(() => user.value),
|
|
352
|
+
isAuthenticated: computed(() => isAuthenticated.value && token.value !== null),
|
|
353
|
+
loading: computed(() => loading.value),
|
|
354
|
+
|
|
355
|
+
// Getters
|
|
356
|
+
...baseGetters,
|
|
357
|
+
|
|
358
|
+
// Actions
|
|
359
|
+
...baseActions
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const extensionContext: AuthStoreExtensionContext = {
|
|
363
|
+
state: baseState,
|
|
364
|
+
getters: baseGetters,
|
|
365
|
+
actions: baseActions,
|
|
366
|
+
services: {
|
|
367
|
+
authService,
|
|
368
|
+
apiService,
|
|
369
|
+
tokenStorage,
|
|
370
|
+
router
|
|
371
|
+
},
|
|
372
|
+
config: {
|
|
373
|
+
storeId,
|
|
374
|
+
defaultLoginRedirect
|
|
375
|
+
},
|
|
376
|
+
resolveSafeRedirectPath
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const extension = extend?.(extensionContext)
|
|
380
|
+
|
|
381
|
+
if (extension) {
|
|
382
|
+
for (const key of Object.keys(extension)) {
|
|
383
|
+
if (key in baseStore) {
|
|
384
|
+
throw new Error(`Auth store extension key "${key}" conflicts with the shared auth store surface.`)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (extension) {
|
|
390
|
+
return {
|
|
391
|
+
...baseStore,
|
|
392
|
+
...extension
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return {
|
|
397
|
+
...baseStore
|
|
398
|
+
}
|
|
399
|
+
}, storeOptions as Record<string, never>)
|
|
400
|
+
}
|
package/src/types/api.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for SocialKit API services.
|
|
3
|
+
*
|
|
4
|
+
* These types are generic and shared across all SocialKit-powered frontends.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Standard API response wrapper. */
|
|
8
|
+
export interface ApiResponse<T> {
|
|
9
|
+
data: T
|
|
10
|
+
meta?: {
|
|
11
|
+
current_page?: number
|
|
12
|
+
total?: number
|
|
13
|
+
per_page?: number
|
|
14
|
+
last_page?: number
|
|
15
|
+
next_cursor?: string
|
|
16
|
+
prev_cursor?: string
|
|
17
|
+
has_more?: boolean
|
|
18
|
+
}
|
|
19
|
+
message?: string
|
|
20
|
+
status?: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Paginated response shape. */
|
|
24
|
+
export interface PaginatedResponse<T> {
|
|
25
|
+
items: T[]
|
|
26
|
+
total: number
|
|
27
|
+
per_page: number
|
|
28
|
+
current_page: number
|
|
29
|
+
last_page: number
|
|
30
|
+
next_cursor?: string
|
|
31
|
+
prev_cursor?: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** API error shape. */
|
|
35
|
+
export interface ApiError {
|
|
36
|
+
message: string
|
|
37
|
+
code?: string
|
|
38
|
+
status?: number
|
|
39
|
+
details?: Record<string, unknown>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Upload progress event. */
|
|
43
|
+
export interface UploadProgressEvent {
|
|
44
|
+
loaded: number
|
|
45
|
+
total?: number
|
|
46
|
+
progress?: number
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Request configuration accepted by ApiService methods. */
|
|
50
|
+
export interface RequestConfig {
|
|
51
|
+
headers?: Record<string, string>
|
|
52
|
+
params?: Record<string, string | number | boolean>
|
|
53
|
+
timeout?: number
|
|
54
|
+
onUploadProgress?: (progressEvent: UploadProgressEvent) => void
|
|
55
|
+
signal?: AbortSignal
|
|
56
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User type definitions shared across SocialKit frontends.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** Base user type used by the auth system. */
|
|
6
|
+
export interface User {
|
|
7
|
+
id: string
|
|
8
|
+
name: string
|
|
9
|
+
email: string
|
|
10
|
+
avatar?: string | Record<string, unknown> | null
|
|
11
|
+
avatar_url?: string | null
|
|
12
|
+
cover_photo?: string | null
|
|
13
|
+
handle?: string
|
|
14
|
+
role: 'admin' | 'moderator' | 'user'
|
|
15
|
+
tenant_id?: string
|
|
16
|
+
roles?: string[]
|
|
17
|
+
permissions?: string[]
|
|
18
|
+
capabilities?: Record<string, boolean>
|
|
19
|
+
last_login_at?: string
|
|
20
|
+
created_at?: string
|
|
21
|
+
status?: 'active' | 'suspended' | 'banned'
|
|
22
|
+
model_type?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Identity user type from the identity service. */
|
|
26
|
+
export interface IdentityUser {
|
|
27
|
+
id: string
|
|
28
|
+
handle?: string
|
|
29
|
+
name: string
|
|
30
|
+
avatar?: string | null
|
|
31
|
+
avatar_url?: string | null
|
|
32
|
+
cover_photo?: string | null
|
|
33
|
+
bio?: string | null
|
|
34
|
+
email?: string
|
|
35
|
+
created_at?: string
|
|
36
|
+
updated_at?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Type guard for User. */
|
|
40
|
+
export function isUser(obj: unknown): obj is User {
|
|
41
|
+
return (
|
|
42
|
+
obj !== null &&
|
|
43
|
+
typeof obj === 'object' &&
|
|
44
|
+
'id' in obj &&
|
|
45
|
+
'name' in obj &&
|
|
46
|
+
'email' in obj &&
|
|
47
|
+
'role' in obj
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Type guard for IdentityUser. */
|
|
52
|
+
export function isIdentityUser(obj: unknown): obj is IdentityUser {
|
|
53
|
+
return (
|
|
54
|
+
obj !== null &&
|
|
55
|
+
typeof obj === 'object' &&
|
|
56
|
+
'id' in obj &&
|
|
57
|
+
'name' in obj
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Convert an IdentityUser to the base User type. */
|
|
62
|
+
export function identityUserToUser(
|
|
63
|
+
identity: IdentityUser,
|
|
64
|
+
role: 'admin' | 'moderator' | 'user' = 'user'
|
|
65
|
+
): User {
|
|
66
|
+
const user: User = {
|
|
67
|
+
id: identity.id,
|
|
68
|
+
name: identity.name,
|
|
69
|
+
email: identity.email ?? '',
|
|
70
|
+
role
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (identity.avatar !== null && identity.avatar !== undefined) {
|
|
74
|
+
user.avatar = identity.avatar
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (identity.avatar_url !== null && identity.avatar_url !== undefined) {
|
|
78
|
+
user.avatar_url = identity.avatar_url
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (identity.cover_photo !== null && identity.cover_photo !== undefined) {
|
|
82
|
+
user.cover_photo = identity.cover_photo
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (identity.handle !== undefined) {
|
|
86
|
+
user.handle = identity.handle
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (identity.created_at !== undefined) {
|
|
90
|
+
user.created_at = identity.created_at
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return user
|
|
94
|
+
}
|