@create-lft-app/nextjs 3.1.0 → 3.3.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.
Files changed (49) hide show
  1. package/package.json +1 -1
  2. package/template/.claude/skills/anti-patterns.md +150 -0
  3. package/template/.claude/skills/drizzle-schema.md +178 -0
  4. package/template/.claude/skills/formatting.md +56 -0
  5. package/template/.claude/skills/module-architecture.md +143 -0
  6. package/template/.claude/skills/supabase-server-actions.md +199 -0
  7. package/template/.claude/skills/ui-patterns.md +161 -0
  8. package/template/CLAUDE.md +74 -239
  9. package/template/src/components/layout/sidebar.tsx +4 -9
  10. package/template/src/components/tables/data-table-date-filter.tsx +203 -0
  11. package/template/src/components/tables/data-table-faceted-filter.tsx +185 -0
  12. package/template/src/components/tables/data-table-filters-dropdown.tsx +130 -0
  13. package/template/src/components/tables/data-table-number-filter.tsx +295 -0
  14. package/template/src/components/tables/data-table-toolbar.tsx +115 -25
  15. package/template/src/components/tables/data-table-view-options.tsx +10 -6
  16. package/template/src/components/tables/data-table.tsx +41 -21
  17. package/template/src/components/tables/index.ts +5 -1
  18. package/template/src/components/ui/alert-dialog.tsx +1 -1
  19. package/template/src/components/ui/avatar.tsx +2 -2
  20. package/template/src/components/ui/badge.tsx +2 -2
  21. package/template/src/components/ui/button.tsx +1 -1
  22. package/template/src/components/ui/card.tsx +1 -1
  23. package/template/src/components/ui/command.tsx +4 -4
  24. package/template/src/components/ui/dropdown-menu.tsx +4 -4
  25. package/template/src/components/ui/form.tsx +1 -1
  26. package/template/src/components/ui/icons.tsx +1 -1
  27. package/template/src/components/ui/popover.tsx +1 -1
  28. package/template/src/components/ui/progress.tsx +1 -1
  29. package/template/src/components/ui/select.tsx +3 -3
  30. package/template/src/components/ui/sonner.tsx +1 -1
  31. package/template/src/components/ui/spinner.tsx +1 -1
  32. package/template/src/components/ui/table.tsx +3 -3
  33. package/template/src/components/ui/tooltip.tsx +2 -2
  34. package/template/src/config/navigation.ts +1 -11
  35. package/template/src/config/roles.ts +27 -0
  36. package/template/src/lib/date/config.ts +4 -2
  37. package/template/src/lib/date/formatters.ts +7 -0
  38. package/template/src/lib/date/index.ts +8 -1
  39. package/template/src/lib/supabase/admin.ts +23 -0
  40. package/template/src/lib/supabase/proxy.ts +1 -1
  41. package/template/src/modules/users/actions/users-actions.ts +106 -34
  42. package/template/src/modules/users/columns.tsx +29 -9
  43. package/template/src/modules/users/components/users-list.tsx +27 -1
  44. package/template/src/modules/users/hooks/useUsersMutations.ts +3 -3
  45. package/template/src/modules/users/index.ts +20 -2
  46. package/template/src/modules/users/schemas/users.schema.ts +29 -1
  47. package/template/src/modules/users/types/auth-user.types.ts +42 -0
  48. package/template/src/modules/users/utils/user-mapper.ts +32 -0
  49. package/template/tsconfig.tsbuildinfo +1 -1
@@ -1,7 +1,14 @@
1
- export { dayjs, setDefaultTimezone } from './config'
1
+ export {
2
+ dayjs,
3
+ setDefaultTimezone,
4
+ DEFAULT_LOCALE,
5
+ DEFAULT_TIMEZONE,
6
+ DEFAULT_CURRENCY,
7
+ } from './config'
2
8
  export {
3
9
  formatDate,
4
10
  formatDateLong,
11
+ formatDateShort,
5
12
  formatTime,
6
13
  formatTimeWithSeconds,
7
14
  formatDateTime,
@@ -0,0 +1,23 @@
1
+ import { createClient } from '@supabase/supabase-js'
2
+
3
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
4
+ const secretKey = process.env.SUPABASE_SECRET_DEFAULT_KEY!
5
+
6
+ /**
7
+ * Cliente admin para operaciones que requieren privilegios elevados.
8
+ * Usa la nueva Secret Key de Supabase (sb_secret_...).
9
+ * SOLO usar en server-side (actions, API routes).
10
+ * NUNCA exponer en el cliente.
11
+ */
12
+ export function createAdminClient() {
13
+ if (!secretKey) {
14
+ throw new Error('SUPABASE_SECRET_DEFAULT_KEY is not defined')
15
+ }
16
+
17
+ return createClient(supabaseUrl, secretKey, {
18
+ auth: {
19
+ autoRefreshToken: false,
20
+ persistSession: false,
21
+ },
22
+ })
23
+ }
@@ -46,7 +46,7 @@ export async function updateSession(request: NextRequest) {
46
46
  // Define route protection
47
47
  const pathname = request.nextUrl.pathname
48
48
 
49
- const protectedPaths = ['/dashboard', '/users', '/settings', '/reports']
49
+ const protectedPaths = ['/dashboard', '/users', '/reports']
50
50
  const publicOnlyPaths = ['/login', '/register']
51
51
 
52
52
  const isProtectedPath = protectedPaths.some((path) => pathname.startsWith(path))
@@ -1,41 +1,54 @@
1
1
  'use server'
2
2
 
3
3
  import { createClient } from '@/lib/supabase/server'
4
- import { createUserSchema, updateUserSchema, type CreateUserInput, type UpdateUserInput } from '../schemas/users.schema'
5
-
4
+ import { createAdminClient } from '@/lib/supabase/admin'
5
+ import {
6
+ createAuthUserSchema,
7
+ updateAuthUserSchema,
8
+ type CreateAuthUserInput,
9
+ type UpdateAuthUserInput,
10
+ } from '../schemas/users.schema'
11
+ import { mapAuthUserToUser, mapAuthUsersToUsers } from '../utils/user-mapper'
12
+
13
+ /**
14
+ * Obtiene todos los usuarios desde auth.users
15
+ */
6
16
  export async function getUsers() {
7
17
  const supabase = await createClient()
8
18
  const { data: { user } } = await supabase.auth.getUser()
9
19
 
10
20
  if (!user) throw new Error('Unauthorized')
11
21
 
12
- const { data, error } = await supabase
13
- .from('users')
14
- .select('*')
15
- .order('created_at', { ascending: false })
22
+ const adminClient = createAdminClient()
23
+ const { data, error } = await adminClient.auth.admin.listUsers()
16
24
 
17
25
  if (error) throw error
18
- return data
26
+
27
+ return mapAuthUsersToUsers(data.users)
19
28
  }
20
29
 
30
+ /**
31
+ * Obtiene un usuario por ID desde auth.users
32
+ */
21
33
  export async function getUserById(id: string) {
22
34
  const supabase = await createClient()
23
35
  const { data: { user } } = await supabase.auth.getUser()
24
36
 
25
37
  if (!user) throw new Error('Unauthorized')
26
38
 
27
- const { data, error } = await supabase
28
- .from('users')
29
- .select('*')
30
- .eq('id', id)
31
- .single()
39
+ const adminClient = createAdminClient()
40
+ const { data, error } = await adminClient.auth.admin.getUserById(id)
32
41
 
33
42
  if (error) throw error
34
- return data
43
+
44
+ return mapAuthUserToUser(data.user)
35
45
  }
36
46
 
37
- export async function createUser(input: CreateUserInput) {
38
- const parsed = createUserSchema.safeParse(input)
47
+ /**
48
+ * Crea un nuevo usuario en auth.users
49
+ */
50
+ export async function createUser(input: CreateAuthUserInput) {
51
+ const parsed = createAuthUserSchema.safeParse(input)
39
52
 
40
53
  if (!parsed.success) {
41
54
  throw new Error(parsed.error.errors[0].message)
@@ -46,18 +59,36 @@ export async function createUser(input: CreateUserInput) {
46
59
 
47
60
  if (!user) throw new Error('Unauthorized')
48
61
 
49
- const { data, error } = await supabase
50
- .from('users')
51
- .insert(parsed.data)
52
- .select()
53
- .single()
62
+ const adminClient = createAdminClient()
63
+
64
+ const { data, error } = await adminClient.auth.admin.createUser({
65
+ email: parsed.data.email,
66
+ password: parsed.data.password,
67
+ email_confirm: !parsed.data.send_invite,
68
+ app_metadata: {
69
+ role: parsed.data.role,
70
+ },
71
+ user_metadata: {
72
+ name: parsed.data.name,
73
+ avatar_url: parsed.data.avatar_url ?? null,
74
+ },
75
+ })
54
76
 
55
77
  if (error) throw error
56
- return data
78
+
79
+ // Enviar email de invitación si se solicita y no se proporcionó password
80
+ if (parsed.data.send_invite && !parsed.data.password) {
81
+ await adminClient.auth.admin.inviteUserByEmail(parsed.data.email)
82
+ }
83
+
84
+ return mapAuthUserToUser(data.user)
57
85
  }
58
86
 
59
- export async function updateUser(id: string, input: UpdateUserInput) {
60
- const parsed = updateUserSchema.safeParse(input)
87
+ /**
88
+ * Actualiza un usuario en auth.users
89
+ */
90
+ export async function updateUser(id: string, input: UpdateAuthUserInput) {
91
+ const parsed = updateAuthUserSchema.safeParse(input)
61
92
 
62
93
  if (!parsed.success) {
63
94
  throw new Error(parsed.error.errors[0].message)
@@ -68,27 +99,68 @@ export async function updateUser(id: string, input: UpdateUserInput) {
68
99
 
69
100
  if (!user) throw new Error('Unauthorized')
70
101
 
71
- const { data, error } = await supabase
72
- .from('users')
73
- .update(parsed.data)
74
- .eq('id', id)
75
- .select()
76
- .single()
102
+ const adminClient = createAdminClient()
103
+
104
+ // Obtener usuario actual para merge de metadata
105
+ const { data: currentUser, error: fetchError } = await adminClient.auth.admin.getUserById(id)
106
+
107
+ if (fetchError) throw fetchError
108
+ if (!currentUser.user) throw new Error('User not found')
109
+
110
+ const updatePayload: Parameters<typeof adminClient.auth.admin.updateUserById>[1] = {}
111
+
112
+ if (parsed.data.email) {
113
+ updatePayload.email = parsed.data.email
114
+ }
115
+
116
+ if (parsed.data.password) {
117
+ updatePayload.password = parsed.data.password
118
+ }
119
+
120
+ // Merge app_metadata (solo role)
121
+ if (parsed.data.role) {
122
+ updatePayload.app_metadata = {
123
+ ...currentUser.user.app_metadata,
124
+ role: parsed.data.role,
125
+ }
126
+ }
127
+
128
+ // Merge user_metadata
129
+ const hasUserMetadataChanges =
130
+ parsed.data.name !== undefined ||
131
+ parsed.data.avatar_url !== undefined
132
+
133
+ if (hasUserMetadataChanges) {
134
+ updatePayload.user_metadata = {
135
+ ...currentUser.user.user_metadata,
136
+ ...(parsed.data.name !== undefined && { name: parsed.data.name }),
137
+ ...(parsed.data.avatar_url !== undefined && { avatar_url: parsed.data.avatar_url }),
138
+ }
139
+ }
140
+
141
+ const { data, error } = await adminClient.auth.admin.updateUserById(id, updatePayload)
77
142
 
78
143
  if (error) throw error
79
- return data
144
+
145
+ return mapAuthUserToUser(data.user)
80
146
  }
81
147
 
148
+ /**
149
+ * Elimina un usuario de auth.users
150
+ */
82
151
  export async function deleteUser(id: string) {
83
152
  const supabase = await createClient()
84
153
  const { data: { user } } = await supabase.auth.getUser()
85
154
 
86
155
  if (!user) throw new Error('Unauthorized')
87
156
 
88
- const { error } = await supabase
89
- .from('users')
90
- .delete()
91
- .eq('id', id)
157
+ // Prevenir auto-eliminación
158
+ if (user.id === id) {
159
+ throw new Error('No puedes eliminar tu propio usuario')
160
+ }
161
+
162
+ const adminClient = createAdminClient()
163
+ const { error } = await adminClient.auth.admin.deleteUser(id)
92
164
 
93
165
  if (error) throw error
94
166
  }
@@ -12,7 +12,12 @@ import {
12
12
  DropdownMenuTrigger,
13
13
  } from '@/components/ui/dropdown-menu'
14
14
  import { DataTableColumnHeader } from '@/components/tables/data-table-column-header'
15
+ import { dateRangeFilterFn } from '@/components/tables/data-table-date-filter'
16
+ // numberRangeFilterFn disponible para campos numéricos: import { numberRangeFilterFn } from '@/components/tables/data-table-number-filter'
17
+ import { formatDate } from '@/lib/date'
18
+ import { ROLE_LABELS } from '@/config/roles'
15
19
  import type { User } from './schemas/users.schema'
20
+ import type { UserRole } from '@/config/roles'
16
21
 
17
22
  export const columns: ColumnDef<User>[] = [
18
23
  {
@@ -33,13 +38,11 @@ export const columns: ColumnDef<User>[] = [
33
38
  <DataTableColumnHeader column={column} title="Rol" />
34
39
  ),
35
40
  cell: ({ row }) => {
36
- const role = row.getValue('role') as string
37
- const labels: Record<string, string> = {
38
- admin: 'Administrador',
39
- user: 'Usuario',
40
- viewer: 'Visualizador',
41
- }
42
- return labels[role] ?? role
41
+ const role = row.getValue('role') as UserRole
42
+ return ROLE_LABELS[role] ?? role
43
+ },
44
+ filterFn: (row, id, value) => {
45
+ return value.includes(row.getValue(id))
43
46
  },
44
47
  },
45
48
  {
@@ -48,10 +51,27 @@ export const columns: ColumnDef<User>[] = [
48
51
  <DataTableColumnHeader column={column} title="Creado" />
49
52
  ),
50
53
  cell: ({ row }) => {
51
- const date = new Date(row.getValue('created_at'))
52
- return date.toLocaleDateString('es-ES')
54
+ return formatDate(row.getValue('created_at'))
53
55
  },
56
+ filterFn: dateRangeFilterFn,
54
57
  },
58
+ // Ejemplo de columna numérica con filtro de rango:
59
+ // import { DEFAULT_LOCALE, DEFAULT_CURRENCY } from '@/lib/date'
60
+ // {
61
+ // accessorKey: 'balance',
62
+ // header: ({ column }) => (
63
+ // <DataTableColumnHeader column={column} title="Balance" />
64
+ // ),
65
+ // cell: ({ row }) => {
66
+ // const balance = row.getValue('balance') as number | undefined
67
+ // if (balance === undefined || balance === null) return '-'
68
+ // return new Intl.NumberFormat(DEFAULT_LOCALE, {
69
+ // style: 'currency',
70
+ // currency: DEFAULT_CURRENCY,
71
+ // }).format(balance)
72
+ // },
73
+ // filterFn: numberRangeFilterFn,
74
+ // },
55
75
  {
56
76
  id: 'actions',
57
77
  cell: ({ row }) => {
@@ -1,9 +1,33 @@
1
1
  'use client'
2
2
 
3
- import { DataTable } from '@/components/tables/data-table'
3
+ import { DataTable, type FilterConfig } from '@/components/tables/data-table'
4
+ import { ROLE_OPTIONS } from '@/config/roles'
4
5
  import { useUsers } from '../hooks/useUsers'
5
6
  import { columns } from '../columns'
6
7
 
8
+ // Configuración de filtros para la tabla de usuarios
9
+ const filters: FilterConfig[] = [
10
+ {
11
+ columnId: 'role',
12
+ type: 'faceted',
13
+ title: 'Rol',
14
+ options: ROLE_OPTIONS,
15
+ },
16
+ {
17
+ columnId: 'created_at',
18
+ type: 'date-range',
19
+ title: 'Fecha',
20
+ },
21
+ // Ejemplo de filtro numérico (rango):
22
+ // {
23
+ // columnId: 'balance',
24
+ // type: 'number-range',
25
+ // title: 'Balance',
26
+ // format: 'currency',
27
+ // currencySymbol: '$',
28
+ // },
29
+ ]
30
+
7
31
  export function UsersList() {
8
32
  const { users, isLoading } = useUsers()
9
33
 
@@ -17,6 +41,8 @@ export function UsersList() {
17
41
  data={users}
18
42
  searchKey="name"
19
43
  searchPlaceholder="Buscar por nombre..."
44
+ filters={filters}
45
+ filterMode="inline"
20
46
  />
21
47
  )
22
48
  }
@@ -4,13 +4,13 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'
4
4
  import { toast } from 'sonner'
5
5
  import { createUser, updateUser, deleteUser } from '../actions/users-actions'
6
6
  import { usersKeys } from './useUsersQueries'
7
- import type { CreateUserInput, UpdateUserInput } from '../schemas/users.schema'
7
+ import type { CreateAuthUserInput, UpdateAuthUserInput } from '../schemas/users.schema'
8
8
 
9
9
  export function useUsersMutations() {
10
10
  const queryClient = useQueryClient()
11
11
 
12
12
  const createMutation = useMutation({
13
- mutationFn: (input: CreateUserInput) => createUser(input),
13
+ mutationFn: (input: CreateAuthUserInput) => createUser(input),
14
14
  onSuccess: () => {
15
15
  queryClient.invalidateQueries({ queryKey: usersKeys.lists() })
16
16
  toast.success('Usuario creado correctamente')
@@ -21,7 +21,7 @@ export function useUsersMutations() {
21
21
  })
22
22
 
23
23
  const updateMutation = useMutation({
24
- mutationFn: ({ id, input }: { id: string; input: UpdateUserInput }) =>
24
+ mutationFn: ({ id, input }: { id: string; input: UpdateAuthUserInput }) =>
25
25
  updateUser(id, input),
26
26
  onSuccess: (_, { id }) => {
27
27
  queryClient.invalidateQueries({ queryKey: usersKeys.lists() })
@@ -5,8 +5,26 @@ export { UsersList } from './components/users-list'
5
5
  export { useUsers } from './hooks/useUsers'
6
6
 
7
7
  // Schemas
8
- export { userSchema, createUserSchema, updateUserSchema } from './schemas/users.schema'
9
- export type { User, CreateUserInput, UpdateUserInput } from './schemas/users.schema'
8
+ export {
9
+ userSchema,
10
+ createUserSchema,
11
+ updateUserSchema,
12
+ createAuthUserSchema,
13
+ updateAuthUserSchema,
14
+ } from './schemas/users.schema'
15
+ export type {
16
+ User,
17
+ CreateUserInput,
18
+ UpdateUserInput,
19
+ CreateAuthUserInput,
20
+ UpdateAuthUserInput,
21
+ } from './schemas/users.schema'
22
+
23
+ // Types - Auth User types
24
+ export type { UserRole, UserAppMetadata, UserMetadata } from './types/auth-user.types'
25
+
26
+ // Utils - Mappers
27
+ export { mapAuthUserToUser, mapAuthUsersToUsers } from './utils/user-mapper'
10
28
 
11
29
  // Columns
12
30
  export { columns as usersColumns } from './columns'
@@ -1,15 +1,40 @@
1
1
  import { z } from 'zod'
2
+ import { USER_ROLES, DEFAULT_ROLE } from '@/config/roles'
2
3
 
4
+ // Schema de rol reutilizable
5
+ const roleSchema = z.enum(USER_ROLES)
6
+
7
+ // Schema base del usuario (estructura que devuelve el mapper)
3
8
  export const userSchema = z.object({
4
9
  id: z.string().uuid(),
5
10
  email: z.string().email(),
6
11
  name: z.string().min(1, 'El nombre es requerido'),
7
- role: z.enum(['admin', 'user', 'viewer']).default('user'),
12
+ role: roleSchema.default(DEFAULT_ROLE),
8
13
  avatar_url: z.string().url().nullable().optional(),
9
14
  created_at: z.string().datetime(),
10
15
  updated_at: z.string().datetime(),
11
16
  })
12
17
 
18
+ // Schema para crear usuario via Supabase Auth Admin
19
+ export const createAuthUserSchema = z.object({
20
+ email: z.string().email('Email inválido'),
21
+ password: z.string().min(8, 'Mínimo 8 caracteres').optional(),
22
+ name: z.string().min(1, 'El nombre es requerido'),
23
+ role: roleSchema.default(DEFAULT_ROLE),
24
+ avatar_url: z.string().url().nullable().optional(),
25
+ send_invite: z.boolean().default(true),
26
+ })
27
+
28
+ // Schema para actualizar usuario via Supabase Auth Admin
29
+ export const updateAuthUserSchema = z.object({
30
+ email: z.string().email('Email inválido').optional(),
31
+ password: z.string().min(8, 'Mínimo 8 caracteres').optional(),
32
+ name: z.string().min(1, 'El nombre es requerido').optional(),
33
+ role: roleSchema.optional(),
34
+ avatar_url: z.string().url().nullable().optional(),
35
+ })
36
+
37
+ // Schemas legacy (mantener compatibilidad)
13
38
  export const createUserSchema = userSchema.omit({
14
39
  id: true,
15
40
  created_at: true,
@@ -18,6 +43,9 @@ export const createUserSchema = userSchema.omit({
18
43
 
19
44
  export const updateUserSchema = createUserSchema.partial()
20
45
 
46
+ // Types
21
47
  export type User = z.infer<typeof userSchema>
22
48
  export type CreateUserInput = z.infer<typeof createUserSchema>
23
49
  export type UpdateUserInput = z.infer<typeof updateUserSchema>
50
+ export type CreateAuthUserInput = z.infer<typeof createAuthUserSchema>
51
+ export type UpdateAuthUserInput = z.infer<typeof updateAuthUserSchema>
@@ -0,0 +1,42 @@
1
+ import type { User as SupabaseAuthUser } from '@supabase/supabase-js'
2
+ import type { UserRole } from '@/config/roles'
3
+
4
+ // Re-exportar desde config para mantener compatibilidad
5
+ export type { UserRole } from '@/config/roles'
6
+
7
+ export interface UserAppMetadata {
8
+ role: UserRole
9
+ }
10
+
11
+ export interface UserMetadata {
12
+ name: string
13
+ avatar_url?: string | null
14
+ }
15
+
16
+ export interface AuthUserWithMetadata extends SupabaseAuthUser {
17
+ app_metadata: UserAppMetadata
18
+ user_metadata: UserMetadata
19
+ }
20
+
21
+ /**
22
+ * Input para crear usuario via auth.admin.createUser
23
+ */
24
+ export interface CreateAuthUserInput {
25
+ email: string
26
+ password?: string
27
+ name: string
28
+ role?: UserRole
29
+ avatar_url?: string | null
30
+ send_invite?: boolean
31
+ }
32
+
33
+ /**
34
+ * Input para actualizar usuario via auth.admin.updateUserById
35
+ */
36
+ export interface UpdateAuthUserInput {
37
+ email?: string
38
+ password?: string
39
+ name?: string
40
+ role?: UserRole
41
+ avatar_url?: string | null
42
+ }
@@ -0,0 +1,32 @@
1
+ import type { User as SupabaseAuthUser } from '@supabase/supabase-js'
2
+ import type { User } from '../schemas/users.schema'
3
+ import { DEFAULT_ROLE, type UserRole } from '@/config/roles'
4
+
5
+ /**
6
+ * Mapea un usuario de auth.users al tipo User de la aplicación.
7
+ * Extrae datos de app_metadata (role) y user_metadata (name, avatar_url).
8
+ */
9
+ export function mapAuthUserToUser(authUser: SupabaseAuthUser): User {
10
+ const appMetadata = authUser.app_metadata as { role?: UserRole } | undefined
11
+ const userMetadata = authUser.user_metadata as {
12
+ name?: string
13
+ avatar_url?: string | null
14
+ } | undefined
15
+
16
+ return {
17
+ id: authUser.id,
18
+ email: authUser.email ?? '',
19
+ name: userMetadata?.name ?? '',
20
+ role: appMetadata?.role ?? DEFAULT_ROLE,
21
+ avatar_url: userMetadata?.avatar_url ?? null,
22
+ created_at: authUser.created_at ?? new Date().toISOString(),
23
+ updated_at: authUser.updated_at ?? new Date().toISOString(),
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Mapea array de usuarios de auth.users al tipo User[]
29
+ */
30
+ export function mapAuthUsersToUsers(authUsers: SupabaseAuthUser[]): User[] {
31
+ return authUsers.map(mapAuthUserToUser)
32
+ }