@open-mercato/core 0.4.2-canary-f6b7824b47 → 0.4.2-canary-70c8402224
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/generated/entities/notification/index.js +57 -0
- package/dist/generated/entities/notification/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +5 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/api_docs/frontend/docs/api/page.js +3 -2
- package/dist/modules/api_docs/frontend/docs/api/page.js.map +2 -2
- package/dist/modules/auth/api/admin/nav.js +4 -3
- package/dist/modules/auth/api/admin/nav.js.map +2 -2
- package/dist/modules/auth/api/profile/route.js +155 -0
- package/dist/modules/auth/api/profile/route.js.map +7 -0
- package/dist/modules/auth/api/reset/confirm.js +25 -2
- package/dist/modules/auth/api/reset/confirm.js.map +2 -2
- package/dist/modules/auth/api/reset.js +23 -0
- package/dist/modules/auth/api/reset.js.map +2 -2
- package/dist/modules/auth/api/sidebar/preferences/route.js +14 -9
- package/dist/modules/auth/api/sidebar/preferences/route.js.map +2 -2
- package/dist/modules/auth/backend/auth/profile/page.js +99 -0
- package/dist/modules/auth/backend/auth/profile/page.js.map +7 -0
- package/dist/modules/auth/backend/auth/profile/page.meta.js +12 -0
- package/dist/modules/auth/backend/auth/profile/page.meta.js.map +7 -0
- package/dist/modules/auth/commands/users.js +55 -0
- package/dist/modules/auth/commands/users.js.map +2 -2
- package/dist/modules/auth/lib/setup-app.js +1 -0
- package/dist/modules/auth/lib/setup-app.js.map +2 -2
- package/dist/modules/auth/notifications.js +112 -0
- package/dist/modules/auth/notifications.js.map +7 -0
- package/dist/modules/auth/services/authService.js +3 -3
- package/dist/modules/auth/services/authService.js.map +2 -2
- package/dist/modules/business_rules/notifications.js +28 -0
- package/dist/modules/business_rules/notifications.js.map +7 -0
- package/dist/modules/business_rules/subscribers/rule-execution-failed-notification.js +37 -0
- package/dist/modules/business_rules/subscribers/rule-execution-failed-notification.js.map +7 -0
- package/dist/modules/catalog/notifications.js +28 -0
- package/dist/modules/catalog/notifications.js.map +7 -0
- package/dist/modules/catalog/subscribers/low-stock-notification.js +38 -0
- package/dist/modules/catalog/subscribers/low-stock-notification.js.map +7 -0
- package/dist/modules/configs/cli.js +6 -0
- package/dist/modules/configs/cli.js.map +2 -2
- package/dist/modules/customers/commands/deals.js +31 -0
- package/dist/modules/customers/commands/deals.js.map +2 -2
- package/dist/modules/customers/notifications.js +48 -0
- package/dist/modules/customers/notifications.js.map +7 -0
- package/dist/modules/notifications/acl.js +11 -0
- package/dist/modules/notifications/acl.js.map +7 -0
- package/dist/modules/notifications/api/[id]/action/route.js +69 -0
- package/dist/modules/notifications/api/[id]/action/route.js.map +7 -0
- package/dist/modules/notifications/api/[id]/dismiss/route.js +15 -0
- package/dist/modules/notifications/api/[id]/dismiss/route.js.map +7 -0
- package/dist/modules/notifications/api/[id]/read/route.js +15 -0
- package/dist/modules/notifications/api/[id]/read/route.js.map +7 -0
- package/dist/modules/notifications/api/[id]/restore/route.js +53 -0
- package/dist/modules/notifications/api/[id]/restore/route.js.map +7 -0
- package/dist/modules/notifications/api/batch/route.js +17 -0
- package/dist/modules/notifications/api/batch/route.js.map +7 -0
- package/dist/modules/notifications/api/feature/route.js +17 -0
- package/dist/modules/notifications/api/feature/route.js.map +7 -0
- package/dist/modules/notifications/api/mark-all-read/route.js +35 -0
- package/dist/modules/notifications/api/mark-all-read/route.js.map +7 -0
- package/dist/modules/notifications/api/openapi.js +57 -0
- package/dist/modules/notifications/api/openapi.js.map +7 -0
- package/dist/modules/notifications/api/role/route.js +17 -0
- package/dist/modules/notifications/api/role/route.js.map +7 -0
- package/dist/modules/notifications/api/route.js +85 -0
- package/dist/modules/notifications/api/route.js.map +7 -0
- package/dist/modules/notifications/api/settings/route.js +96 -0
- package/dist/modules/notifications/api/settings/route.js.map +7 -0
- package/dist/modules/notifications/api/unread-count/route.js +38 -0
- package/dist/modules/notifications/api/unread-count/route.js.map +7 -0
- package/dist/modules/notifications/backend/config/notifications/page.js +10 -0
- package/dist/modules/notifications/backend/config/notifications/page.js.map +7 -0
- package/dist/modules/notifications/backend/config/notifications/page.meta.js +24 -0
- package/dist/modules/notifications/backend/config/notifications/page.meta.js.map +7 -0
- package/dist/modules/notifications/cli.js +16 -0
- package/dist/modules/notifications/cli.js.map +7 -0
- package/dist/modules/notifications/data/entities.js +112 -0
- package/dist/modules/notifications/data/entities.js.map +7 -0
- package/dist/modules/notifications/data/validators.js +94 -0
- package/dist/modules/notifications/data/validators.js.map +7 -0
- package/dist/modules/notifications/di.js +13 -0
- package/dist/modules/notifications/di.js.map +7 -0
- package/dist/modules/notifications/emails/NotificationEmail.js +58 -0
- package/dist/modules/notifications/emails/NotificationEmail.js.map +7 -0
- package/dist/modules/notifications/frontend/NotificationInboxPageClient.js +44 -0
- package/dist/modules/notifications/frontend/NotificationInboxPageClient.js.map +7 -0
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +219 -0
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +7 -0
- package/dist/modules/notifications/index.js +14 -0
- package/dist/modules/notifications/index.js.map +7 -0
- package/dist/modules/notifications/lib/deliveryConfig.js +105 -0
- package/dist/modules/notifications/lib/deliveryConfig.js.map +7 -0
- package/dist/modules/notifications/lib/events.js +12 -0
- package/dist/modules/notifications/lib/events.js.map +7 -0
- package/dist/modules/notifications/lib/notificationBuilder.js +66 -0
- package/dist/modules/notifications/lib/notificationBuilder.js.map +7 -0
- package/dist/modules/notifications/lib/notificationFactory.js +54 -0
- package/dist/modules/notifications/lib/notificationFactory.js.map +7 -0
- package/dist/modules/notifications/lib/notificationMapper.js +34 -0
- package/dist/modules/notifications/lib/notificationMapper.js.map +7 -0
- package/dist/modules/notifications/lib/notificationRecipients.js +35 -0
- package/dist/modules/notifications/lib/notificationRecipients.js.map +7 -0
- package/dist/modules/notifications/lib/notificationService.js +279 -0
- package/dist/modules/notifications/lib/notificationService.js.map +7 -0
- package/dist/modules/notifications/lib/routeHelpers.js +101 -0
- package/dist/modules/notifications/lib/routeHelpers.js.map +7 -0
- package/dist/modules/notifications/lib/safeHref.js +24 -0
- package/dist/modules/notifications/lib/safeHref.js.map +7 -0
- package/dist/modules/notifications/migrations/Migration20260123000001.js +70 -0
- package/dist/modules/notifications/migrations/Migration20260123000001.js.map +7 -0
- package/dist/modules/notifications/migrations/Migration20260126150000.js +37 -0
- package/dist/modules/notifications/migrations/Migration20260126150000.js.map +7 -0
- package/dist/modules/notifications/subscribers/deliver-notification.js +139 -0
- package/dist/modules/notifications/subscribers/deliver-notification.js.map +7 -0
- package/dist/modules/notifications/workers/create-notification.worker.js +70 -0
- package/dist/modules/notifications/workers/create-notification.worker.js.map +7 -0
- package/dist/modules/sales/commands/documents.js +53 -0
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/payments.js +26 -0
- package/dist/modules/sales/commands/payments.js.map +2 -2
- package/dist/modules/sales/notifications.client.js +51 -0
- package/dist/modules/sales/notifications.client.js.map +7 -0
- package/dist/modules/sales/notifications.js +88 -0
- package/dist/modules/sales/notifications.js.map +7 -0
- package/dist/modules/sales/subscribers/quote-expiring-notification.js +38 -0
- package/dist/modules/sales/subscribers/quote-expiring-notification.js.map +7 -0
- package/dist/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.js +137 -0
- package/dist/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.js.map +7 -0
- package/dist/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.js +137 -0
- package/dist/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.js.map +7 -0
- package/dist/modules/sales/widgets/notifications/index.js +7 -0
- package/dist/modules/sales/widgets/notifications/index.js.map +7 -0
- package/dist/modules/sales/widgets/notifications/useSalesDocumentTotals.js +60 -0
- package/dist/modules/sales/widgets/notifications/useSalesDocumentTotals.js.map +7 -0
- package/dist/modules/staff/commands/leave-requests.js +79 -0
- package/dist/modules/staff/commands/leave-requests.js.map +2 -2
- package/dist/modules/staff/notifications.js +75 -0
- package/dist/modules/staff/notifications.js.map +7 -0
- package/dist/modules/workflows/notifications.js +28 -0
- package/dist/modules/workflows/notifications.js.map +7 -0
- package/dist/modules/workflows/subscribers/task-assigned-notification.js +38 -0
- package/dist/modules/workflows/subscribers/task-assigned-notification.js.map +7 -0
- package/generated/entities/notification/index.ts +27 -0
- package/generated/entities.ids.generated.ts +5 -1
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +2 -2
- package/src/modules/api_docs/frontend/docs/api/page.tsx +3 -2
- package/src/modules/auth/api/admin/nav.ts +10 -6
- package/src/modules/auth/api/profile/route.ts +160 -0
- package/src/modules/auth/api/reset/confirm.ts +25 -2
- package/src/modules/auth/api/reset.ts +23 -0
- package/src/modules/auth/api/sidebar/preferences/route.ts +21 -12
- package/src/modules/auth/backend/auth/profile/page.meta.ts +8 -0
- package/src/modules/auth/backend/auth/profile/page.tsx +127 -0
- package/src/modules/auth/commands/users.ts +68 -0
- package/src/modules/auth/i18n/de.json +29 -1
- package/src/modules/auth/i18n/en.json +29 -1
- package/src/modules/auth/i18n/es.json +29 -1
- package/src/modules/auth/i18n/pl.json +29 -1
- package/src/modules/auth/lib/setup-app.ts +1 -0
- package/src/modules/auth/notifications.ts +109 -0
- package/src/modules/auth/services/authService.ts +4 -4
- package/src/modules/business_rules/i18n/en.json +3 -1
- package/src/modules/business_rules/notifications.ts +25 -0
- package/src/modules/business_rules/subscribers/rule-execution-failed-notification.ts +50 -0
- package/src/modules/catalog/i18n/en.json +3 -1
- package/src/modules/catalog/notifications.ts +25 -0
- package/src/modules/catalog/subscribers/low-stock-notification.ts +52 -0
- package/src/modules/configs/cli.ts +6 -0
- package/src/modules/customers/commands/deals.ts +39 -0
- package/src/modules/customers/i18n/en.json +5 -1
- package/src/modules/customers/notifications.ts +44 -0
- package/src/modules/notifications/acl.ts +7 -0
- package/src/modules/notifications/api/[id]/action/route.ts +70 -0
- package/src/modules/notifications/api/[id]/dismiss/route.ts +12 -0
- package/src/modules/notifications/api/[id]/read/route.ts +12 -0
- package/src/modules/notifications/api/[id]/restore/route.ts +53 -0
- package/src/modules/notifications/api/batch/route.ts +14 -0
- package/src/modules/notifications/api/feature/route.ts +14 -0
- package/src/modules/notifications/api/mark-all-read/route.ts +34 -0
- package/src/modules/notifications/api/openapi.ts +52 -0
- package/src/modules/notifications/api/role/route.ts +14 -0
- package/src/modules/notifications/api/route.ts +92 -0
- package/src/modules/notifications/api/settings/route.ts +98 -0
- package/src/modules/notifications/api/unread-count/route.ts +38 -0
- package/src/modules/notifications/backend/config/notifications/page.meta.ts +22 -0
- package/src/modules/notifications/backend/config/notifications/page.tsx +12 -0
- package/src/modules/notifications/cli.ts +18 -0
- package/src/modules/notifications/data/entities.ts +99 -0
- package/src/modules/notifications/data/validators.ts +110 -0
- package/src/modules/notifications/di.ts +11 -0
- package/src/modules/notifications/emails/NotificationEmail.tsx +98 -0
- package/src/modules/notifications/frontend/NotificationInboxPageClient.tsx +42 -0
- package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +231 -0
- package/src/modules/notifications/i18n/de.json +50 -0
- package/src/modules/notifications/i18n/en.json +50 -0
- package/src/modules/notifications/i18n/es.json +50 -0
- package/src/modules/notifications/i18n/pl.json +50 -0
- package/src/modules/notifications/index.ts +12 -0
- package/src/modules/notifications/lib/deliveryConfig.ts +145 -0
- package/src/modules/notifications/lib/events.ts +48 -0
- package/src/modules/notifications/lib/notificationBuilder.ts +121 -0
- package/src/modules/notifications/lib/notificationFactory.ts +76 -0
- package/src/modules/notifications/lib/notificationMapper.ts +33 -0
- package/src/modules/notifications/lib/notificationRecipients.ts +83 -0
- package/src/modules/notifications/lib/notificationService.ts +414 -0
- package/src/modules/notifications/lib/routeHelpers.ts +151 -0
- package/src/modules/notifications/lib/safeHref.ts +29 -0
- package/src/modules/notifications/migrations/.snapshot-open-mercato.json +300 -0
- package/src/modules/notifications/migrations/Migration20260123000001.ts +73 -0
- package/src/modules/notifications/migrations/Migration20260126150000.ts +39 -0
- package/src/modules/notifications/subscribers/deliver-notification.ts +175 -0
- package/src/modules/notifications/workers/create-notification.worker.ts +122 -0
- package/src/modules/sales/commands/documents.ts +65 -0
- package/src/modules/sales/commands/payments.ts +33 -0
- package/src/modules/sales/i18n/de.json +20 -0
- package/src/modules/sales/i18n/en.json +25 -1
- package/src/modules/sales/i18n/es.json +20 -0
- package/src/modules/sales/i18n/pl.json +20 -0
- package/src/modules/sales/notifications.client.ts +65 -0
- package/src/modules/sales/notifications.ts +82 -0
- package/src/modules/sales/subscribers/quote-expiring-notification.ts +53 -0
- package/src/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.tsx +156 -0
- package/src/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.tsx +156 -0
- package/src/modules/sales/widgets/notifications/index.ts +2 -0
- package/src/modules/sales/widgets/notifications/useSalesDocumentTotals.ts +81 -0
- package/src/modules/staff/commands/leave-requests.ts +94 -0
- package/src/modules/staff/i18n/de.json +4 -0
- package/src/modules/staff/i18n/en.json +9 -1
- package/src/modules/staff/i18n/es.json +4 -0
- package/src/modules/staff/i18n/pl.json +4 -0
- package/src/modules/staff/notifications.ts +71 -0
- package/src/modules/workflows/i18n/en.json +3 -1
- package/src/modules/workflows/notifications.ts +25 -0
- package/src/modules/workflows/subscribers/task-assigned-notification.ts +53 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
|
+
import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
|
|
5
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
7
|
+
import { signJwt } from '@open-mercato/shared/lib/auth/jwt'
|
|
8
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
9
|
+
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
10
|
+
import { AuthService } from '@open-mercato/core/modules/auth/services/authService'
|
|
11
|
+
import { User } from '@open-mercato/core/modules/auth/data/entities'
|
|
12
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
13
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
14
|
+
|
|
15
|
+
const profileResponseSchema = z.object({
|
|
16
|
+
email: z.string().email(),
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const updateSchema = z.object({
|
|
20
|
+
email: z.string().email().optional(),
|
|
21
|
+
password: z.string().min(6).optional(),
|
|
22
|
+
}).refine((data) => Boolean(data.email || data.password), {
|
|
23
|
+
message: 'Provide an email or password.',
|
|
24
|
+
path: ['email'],
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const profileUpdateResponseSchema = z.object({
|
|
28
|
+
ok: z.literal(true),
|
|
29
|
+
email: z.string().email(),
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
export const metadata = {
|
|
33
|
+
GET: { requireAuth: true },
|
|
34
|
+
PUT: { requireAuth: true },
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function buildCommandContext(container: Awaited<ReturnType<typeof createRequestContainer>>, auth: NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>, req: Request): CommandRuntimeContext {
|
|
38
|
+
return {
|
|
39
|
+
container,
|
|
40
|
+
auth,
|
|
41
|
+
organizationScope: null,
|
|
42
|
+
selectedOrganizationId: auth.orgId ?? null,
|
|
43
|
+
organizationIds: auth.orgId ? [auth.orgId] : null,
|
|
44
|
+
request: req,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function GET(req: Request) {
|
|
49
|
+
const { translate } = await resolveTranslations()
|
|
50
|
+
const auth = await getAuthFromRequest(req)
|
|
51
|
+
if (!auth?.sub) {
|
|
52
|
+
return NextResponse.json({ error: translate('api.errors.unauthorized', 'Unauthorized') }, { status: 401 })
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const container = await createRequestContainer()
|
|
56
|
+
const em = (container.resolve('em') as EntityManager)
|
|
57
|
+
const user = await findOneWithDecryption(
|
|
58
|
+
em,
|
|
59
|
+
User,
|
|
60
|
+
{ id: auth.sub, deletedAt: null },
|
|
61
|
+
undefined,
|
|
62
|
+
{ tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },
|
|
63
|
+
)
|
|
64
|
+
if (!user) {
|
|
65
|
+
return NextResponse.json({ error: translate('auth.users.form.errors.notFound', 'User not found') }, { status: 404 })
|
|
66
|
+
}
|
|
67
|
+
return NextResponse.json({ email: String(user.email) })
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.error('auth.profile.load failed', err)
|
|
70
|
+
return NextResponse.json({ error: translate('auth.profile.form.errors.load', 'Failed to load profile.') }, { status: 400 })
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function PUT(req: Request) {
|
|
75
|
+
const { translate } = await resolveTranslations()
|
|
76
|
+
const auth = await getAuthFromRequest(req)
|
|
77
|
+
if (!auth?.sub) {
|
|
78
|
+
return NextResponse.json({ error: translate('api.errors.unauthorized', 'Unauthorized') }, { status: 401 })
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const body = await req.json().catch(() => ({}))
|
|
82
|
+
const parsed = updateSchema.safeParse(body)
|
|
83
|
+
if (!parsed.success) {
|
|
84
|
+
return NextResponse.json(
|
|
85
|
+
{
|
|
86
|
+
error: translate('auth.profile.form.errors.invalid', 'Invalid profile update.'),
|
|
87
|
+
issues: parsed.error.issues,
|
|
88
|
+
},
|
|
89
|
+
{ status: 400 },
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
const container = await createRequestContainer()
|
|
93
|
+
const commandBus = (container.resolve('commandBus') as CommandBus)
|
|
94
|
+
const ctx = buildCommandContext(container, auth, req)
|
|
95
|
+
const { result } = await commandBus.execute<{ id: string; email?: string; password?: string }, User>(
|
|
96
|
+
'auth.users.update',
|
|
97
|
+
{
|
|
98
|
+
input: {
|
|
99
|
+
id: auth.sub,
|
|
100
|
+
email: parsed.data.email,
|
|
101
|
+
password: parsed.data.password,
|
|
102
|
+
},
|
|
103
|
+
ctx,
|
|
104
|
+
},
|
|
105
|
+
)
|
|
106
|
+
const authService = container.resolve('authService') as AuthService
|
|
107
|
+
const roles = await authService.getUserRoles(result, result.tenantId ? String(result.tenantId) : null)
|
|
108
|
+
const jwt = signJwt({
|
|
109
|
+
sub: String(result.id),
|
|
110
|
+
tenantId: result.tenantId ? String(result.tenantId) : null,
|
|
111
|
+
orgId: result.organizationId ? String(result.organizationId) : null,
|
|
112
|
+
email: result.email,
|
|
113
|
+
roles,
|
|
114
|
+
})
|
|
115
|
+
const res = NextResponse.json({ ok: true, email: String(result.email) })
|
|
116
|
+
res.cookies.set('auth_token', jwt, {
|
|
117
|
+
httpOnly: true,
|
|
118
|
+
path: '/',
|
|
119
|
+
sameSite: 'lax',
|
|
120
|
+
secure: process.env.NODE_ENV === 'production',
|
|
121
|
+
maxAge: 60 * 60 * 8,
|
|
122
|
+
})
|
|
123
|
+
return res
|
|
124
|
+
} catch (err) {
|
|
125
|
+
if (err instanceof CrudHttpError) {
|
|
126
|
+
return NextResponse.json(err.body, { status: err.status })
|
|
127
|
+
}
|
|
128
|
+
console.error('auth.profile.update failed', err)
|
|
129
|
+
return NextResponse.json({ error: translate('auth.profile.form.errors.save', 'Failed to update profile.') }, { status: 400 })
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export const openApi: OpenApiRouteDoc = {
|
|
134
|
+
tag: 'Authentication & Accounts',
|
|
135
|
+
summary: 'Profile settings',
|
|
136
|
+
methods: {
|
|
137
|
+
GET: {
|
|
138
|
+
summary: 'Get current profile',
|
|
139
|
+
description: 'Returns the email address for the signed-in user.',
|
|
140
|
+
responses: [
|
|
141
|
+
{ status: 200, description: 'Profile payload', schema: profileResponseSchema },
|
|
142
|
+
{ status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },
|
|
143
|
+
{ status: 404, description: 'User not found', schema: z.object({ error: z.string() }) },
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
PUT: {
|
|
147
|
+
summary: 'Update current profile',
|
|
148
|
+
description: 'Updates the email address or password for the signed-in user.',
|
|
149
|
+
requestBody: {
|
|
150
|
+
contentType: 'application/json',
|
|
151
|
+
schema: updateSchema,
|
|
152
|
+
},
|
|
153
|
+
responses: [
|
|
154
|
+
{ status: 200, description: 'Profile updated', schema: profileUpdateResponseSchema },
|
|
155
|
+
{ status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },
|
|
156
|
+
{ status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
}
|
|
@@ -3,6 +3,9 @@ import { NextResponse } from 'next/server'
|
|
|
3
3
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
4
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
5
5
|
import { AuthService } from '@open-mercato/core/modules/auth/services/authService'
|
|
6
|
+
import { buildNotificationFromType } from '@open-mercato/core/modules/notifications/lib/notificationBuilder'
|
|
7
|
+
import { resolveNotificationService } from '@open-mercato/core/modules/notifications/lib/notificationService'
|
|
8
|
+
import notificationTypes from '@open-mercato/core/modules/auth/notifications'
|
|
6
9
|
import { z } from 'zod'
|
|
7
10
|
|
|
8
11
|
// validation via confirmPasswordResetSchema
|
|
@@ -15,8 +18,28 @@ export async function POST(req: Request) {
|
|
|
15
18
|
if (!parsed.success) return NextResponse.json({ ok: false, error: 'Invalid request' }, { status: 400 })
|
|
16
19
|
const c = await createRequestContainer()
|
|
17
20
|
const auth = c.resolve<AuthService>('authService')
|
|
18
|
-
const
|
|
19
|
-
if (!
|
|
21
|
+
const user = await auth.confirmPasswordReset(parsed.data.token, parsed.data.password)
|
|
22
|
+
if (!user) return NextResponse.json({ ok: false, error: 'Invalid or expired token' }, { status: 400 })
|
|
23
|
+
try {
|
|
24
|
+
const tenantId = user.tenantId ? String(user.tenantId) : null
|
|
25
|
+
if (tenantId) {
|
|
26
|
+
const notificationService = resolveNotificationService(c)
|
|
27
|
+
const typeDef = notificationTypes.find((type) => type.type === 'auth.password_reset.completed')
|
|
28
|
+
if (typeDef) {
|
|
29
|
+
const notificationInput = buildNotificationFromType(typeDef, {
|
|
30
|
+
recipientUserId: String(user.id),
|
|
31
|
+
sourceEntityType: 'auth:user',
|
|
32
|
+
sourceEntityId: String(user.id),
|
|
33
|
+
})
|
|
34
|
+
await notificationService.create(notificationInput, {
|
|
35
|
+
tenantId,
|
|
36
|
+
organizationId: user.organizationId ? String(user.organizationId) : null,
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error('[auth.reset.confirm] Failed to create notification:', err)
|
|
42
|
+
}
|
|
20
43
|
return NextResponse.json({ ok: true, redirect: '/login' })
|
|
21
44
|
}
|
|
22
45
|
|
|
@@ -6,6 +6,9 @@ import { AuthService } from '@open-mercato/core/modules/auth/services/authServic
|
|
|
6
6
|
import { sendEmail } from '@open-mercato/shared/lib/email/send'
|
|
7
7
|
import ResetPasswordEmail from '@open-mercato/core/modules/auth/emails/ResetPasswordEmail'
|
|
8
8
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
9
|
+
import { buildNotificationFromType } from '@open-mercato/core/modules/notifications/lib/notificationBuilder'
|
|
10
|
+
import { resolveNotificationService } from '@open-mercato/core/modules/notifications/lib/notificationService'
|
|
11
|
+
import notificationTypes from '@open-mercato/core/modules/auth/notifications'
|
|
9
12
|
import { z } from 'zod'
|
|
10
13
|
|
|
11
14
|
// validation via requestPasswordResetSchema
|
|
@@ -35,6 +38,26 @@ export async function POST(req: Request) {
|
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
await sendEmail({ to: user.email, subject, react: ResetPasswordEmail({ resetUrl, copy }) })
|
|
41
|
+
try {
|
|
42
|
+
const tenantId = user.tenantId ? String(user.tenantId) : null
|
|
43
|
+
if (tenantId) {
|
|
44
|
+
const notificationService = resolveNotificationService(c)
|
|
45
|
+
const typeDef = notificationTypes.find((type) => type.type === 'auth.password_reset.requested')
|
|
46
|
+
if (typeDef) {
|
|
47
|
+
const notificationInput = buildNotificationFromType(typeDef, {
|
|
48
|
+
recipientUserId: String(user.id),
|
|
49
|
+
sourceEntityType: 'auth:user',
|
|
50
|
+
sourceEntityId: String(user.id),
|
|
51
|
+
})
|
|
52
|
+
await notificationService.create(notificationInput, {
|
|
53
|
+
tenantId,
|
|
54
|
+
organizationId: user.organizationId ? String(user.organizationId) : null,
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.error('[auth.reset] Failed to create notification:', err)
|
|
60
|
+
}
|
|
38
61
|
return NextResponse.json({ ok: true })
|
|
39
62
|
}
|
|
40
63
|
|
|
@@ -64,12 +64,16 @@ export async function GET(req: Request) {
|
|
|
64
64
|
{ tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },
|
|
65
65
|
) ?? false
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
// For API key auth, use userId (the actual user) if available
|
|
68
|
+
const effectiveUserId = auth.isApiKey ? auth.userId : auth.sub
|
|
69
|
+
const settings = effectiveUserId
|
|
70
|
+
? await loadSidebarPreference(em, {
|
|
71
|
+
userId: effectiveUserId,
|
|
72
|
+
tenantId: auth.tenantId ?? null,
|
|
73
|
+
organizationId: auth.orgId ?? null,
|
|
74
|
+
locale,
|
|
75
|
+
})
|
|
76
|
+
: null
|
|
73
77
|
|
|
74
78
|
let rolesPayload: Array<{ id: string; name: string; hasPreference: boolean }> = []
|
|
75
79
|
if (canApplyToRoles) {
|
|
@@ -92,11 +96,11 @@ export async function GET(req: Request) {
|
|
|
92
96
|
return NextResponse.json({
|
|
93
97
|
locale,
|
|
94
98
|
settings: {
|
|
95
|
-
version: settings
|
|
96
|
-
groupOrder: settings
|
|
97
|
-
groupLabels: settings
|
|
98
|
-
itemLabels: settings
|
|
99
|
-
hiddenItems: settings
|
|
99
|
+
version: settings?.version ?? SIDEBAR_PREFERENCES_VERSION,
|
|
100
|
+
groupOrder: settings?.groupOrder ?? [],
|
|
101
|
+
groupLabels: settings?.groupLabels ?? {},
|
|
102
|
+
itemLabels: settings?.itemLabels ?? {},
|
|
103
|
+
hiddenItems: settings?.hiddenItems ?? [],
|
|
100
104
|
},
|
|
101
105
|
canApplyToRoles,
|
|
102
106
|
roles: rolesPayload,
|
|
@@ -106,6 +110,11 @@ export async function GET(req: Request) {
|
|
|
106
110
|
export async function PUT(req: Request) {
|
|
107
111
|
const auth = await getAuthFromRequest(req)
|
|
108
112
|
if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
113
|
+
// For API key auth, use userId (the actual user) if available
|
|
114
|
+
const effectiveUserId = auth.isApiKey ? auth.userId : auth.sub
|
|
115
|
+
if (!effectiveUserId) {
|
|
116
|
+
return NextResponse.json({ error: 'Cannot save preferences: no user associated with this API key' }, { status: 403 })
|
|
117
|
+
}
|
|
109
118
|
|
|
110
119
|
let parsedBody: unknown
|
|
111
120
|
try {
|
|
@@ -182,7 +191,7 @@ export async function PUT(req: Request) {
|
|
|
182
191
|
}
|
|
183
192
|
|
|
184
193
|
const settings = await saveSidebarPreference(em, {
|
|
185
|
-
userId:
|
|
194
|
+
userId: effectiveUserId,
|
|
186
195
|
tenantId: auth.tenantId ?? null,
|
|
187
196
|
organizationId: auth.orgId ?? null,
|
|
188
197
|
locale,
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
import { useRouter } from 'next/navigation'
|
|
4
|
+
import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
5
|
+
import { CrudForm, type CrudField } from '@open-mercato/ui/backend/CrudForm'
|
|
6
|
+
import { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
|
|
7
|
+
import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'
|
|
8
|
+
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
9
|
+
import { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'
|
|
10
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
11
|
+
|
|
12
|
+
type ProfileResponse = {
|
|
13
|
+
email?: string | null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type ProfileUpdateResponse = {
|
|
17
|
+
ok?: boolean
|
|
18
|
+
email?: string | null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type ProfileFormValues = {
|
|
22
|
+
email: string
|
|
23
|
+
password: string
|
|
24
|
+
confirmPassword: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default function AuthProfilePage() {
|
|
28
|
+
const t = useT()
|
|
29
|
+
const router = useRouter()
|
|
30
|
+
const [loading, setLoading] = React.useState(true)
|
|
31
|
+
const [error, setError] = React.useState<string | null>(null)
|
|
32
|
+
const [email, setEmail] = React.useState('')
|
|
33
|
+
const [formKey, setFormKey] = React.useState(0)
|
|
34
|
+
|
|
35
|
+
React.useEffect(() => {
|
|
36
|
+
let cancelled = false
|
|
37
|
+
async function load() {
|
|
38
|
+
setLoading(true)
|
|
39
|
+
setError(null)
|
|
40
|
+
try {
|
|
41
|
+
const { ok, result } = await apiCall<ProfileResponse>('/api/auth/profile')
|
|
42
|
+
if (!ok) throw new Error('load_failed')
|
|
43
|
+
const resolvedEmail = typeof result?.email === 'string' ? result.email : ''
|
|
44
|
+
if (!cancelled) setEmail(resolvedEmail)
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error('Failed to load auth profile', err)
|
|
47
|
+
if (!cancelled) setError(t('auth.profile.form.errors.load', 'Failed to load profile.'))
|
|
48
|
+
} finally {
|
|
49
|
+
if (!cancelled) setLoading(false)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
load()
|
|
53
|
+
return () => { cancelled = true }
|
|
54
|
+
}, [t])
|
|
55
|
+
|
|
56
|
+
const fields = React.useMemo<CrudField[]>(() => [
|
|
57
|
+
{ id: 'email', label: t('auth.profile.form.email', 'Email'), type: 'text', required: true },
|
|
58
|
+
{ id: 'password', label: t('auth.profile.form.password', 'New password'), type: 'text' },
|
|
59
|
+
{ id: 'confirmPassword', label: t('auth.profile.form.confirmPassword', 'Confirm new password'), type: 'text' },
|
|
60
|
+
], [t])
|
|
61
|
+
|
|
62
|
+
const handleSubmit = React.useCallback(async (values: ProfileFormValues) => {
|
|
63
|
+
const nextEmail = values.email?.trim() ?? ''
|
|
64
|
+
const password = values.password?.trim() ?? ''
|
|
65
|
+
const confirmPassword = values.confirmPassword?.trim() ?? ''
|
|
66
|
+
|
|
67
|
+
if (!nextEmail) {
|
|
68
|
+
const message = t('auth.profile.form.errors.emailRequired', 'Email is required.')
|
|
69
|
+
throw createCrudFormError(message, { email: message })
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (password || confirmPassword) {
|
|
73
|
+
if (password !== confirmPassword) {
|
|
74
|
+
const message = t('auth.profile.form.errors.passwordMismatch', 'Passwords do not match.')
|
|
75
|
+
throw createCrudFormError(message, { confirmPassword: message })
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!password && nextEmail === email) {
|
|
80
|
+
throw createCrudFormError(t('auth.profile.form.errors.noChanges', 'No changes to save.'))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const payload: { email: string; password?: string } = { email: nextEmail }
|
|
84
|
+
if (password) payload.password = password
|
|
85
|
+
|
|
86
|
+
const result = await readApiResultOrThrow<ProfileUpdateResponse>(
|
|
87
|
+
'/api/auth/profile',
|
|
88
|
+
{
|
|
89
|
+
method: 'PUT',
|
|
90
|
+
headers: { 'content-type': 'application/json' },
|
|
91
|
+
body: JSON.stringify(payload),
|
|
92
|
+
},
|
|
93
|
+
{ errorMessage: t('auth.profile.form.errors.save', 'Failed to update profile.') },
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
const resolvedEmail = typeof result?.email === 'string' ? result.email : nextEmail
|
|
97
|
+
setEmail(resolvedEmail)
|
|
98
|
+
setFormKey((prev) => prev + 1)
|
|
99
|
+
flash(t('auth.profile.form.success', 'Profile updated.'), 'success')
|
|
100
|
+
router.refresh()
|
|
101
|
+
}, [email, router, t])
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<Page>
|
|
105
|
+
<PageBody>
|
|
106
|
+
{loading ? (
|
|
107
|
+
<LoadingMessage label={t('auth.profile.form.loading', 'Loading profile...')} />
|
|
108
|
+
) : error ? (
|
|
109
|
+
<ErrorMessage label={error} />
|
|
110
|
+
) : (
|
|
111
|
+
<CrudForm<ProfileFormValues>
|
|
112
|
+
key={formKey}
|
|
113
|
+
title={t('auth.profile.title', 'Profile')}
|
|
114
|
+
fields={fields}
|
|
115
|
+
initialValues={{
|
|
116
|
+
email,
|
|
117
|
+
password: '',
|
|
118
|
+
confirmPassword: '',
|
|
119
|
+
}}
|
|
120
|
+
submitLabel={t('auth.profile.form.save', 'Save changes')}
|
|
121
|
+
onSubmit={handleSubmit}
|
|
122
|
+
/>
|
|
123
|
+
)}
|
|
124
|
+
</PageBody>
|
|
125
|
+
</Page>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
@@ -27,6 +27,9 @@ import {
|
|
|
27
27
|
import { normalizeTenantId } from '@open-mercato/core/modules/auth/lib/tenantAccess'
|
|
28
28
|
import { computeEmailHash } from '@open-mercato/core/modules/auth/lib/emailHash'
|
|
29
29
|
import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
30
|
+
import { buildNotificationFromType } from '@open-mercato/core/modules/notifications/lib/notificationBuilder'
|
|
31
|
+
import { resolveNotificationService } from '@open-mercato/core/modules/notifications/lib/notificationService'
|
|
32
|
+
import notificationTypes from '@open-mercato/core/modules/auth/notifications'
|
|
30
33
|
|
|
31
34
|
type SerializedUser = {
|
|
32
35
|
email: string
|
|
@@ -105,6 +108,46 @@ export const userCrudIndexer: CrudIndexerConfig = {
|
|
|
105
108
|
}),
|
|
106
109
|
}
|
|
107
110
|
|
|
111
|
+
async function notifyRoleChanges(
|
|
112
|
+
ctx: CommandRuntimeContext,
|
|
113
|
+
user: User,
|
|
114
|
+
assignedRoles: string[],
|
|
115
|
+
revokedRoles: string[],
|
|
116
|
+
): Promise<void> {
|
|
117
|
+
const tenantId = user.tenantId ? String(user.tenantId) : null
|
|
118
|
+
if (!tenantId) return
|
|
119
|
+
const organizationId = user.organizationId ? String(user.organizationId) : null
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const notificationService = resolveNotificationService(ctx.container)
|
|
123
|
+
if (assignedRoles.length) {
|
|
124
|
+
const assignedType = notificationTypes.find((type) => type.type === 'auth.role.assigned')
|
|
125
|
+
if (assignedType) {
|
|
126
|
+
const notificationInput = buildNotificationFromType(assignedType, {
|
|
127
|
+
recipientUserId: String(user.id),
|
|
128
|
+
sourceEntityType: 'auth:user',
|
|
129
|
+
sourceEntityId: String(user.id),
|
|
130
|
+
})
|
|
131
|
+
await notificationService.create(notificationInput, { tenantId, organizationId })
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (revokedRoles.length) {
|
|
136
|
+
const revokedType = notificationTypes.find((type) => type.type === 'auth.role.revoked')
|
|
137
|
+
if (revokedType) {
|
|
138
|
+
const notificationInput = buildNotificationFromType(revokedType, {
|
|
139
|
+
recipientUserId: String(user.id),
|
|
140
|
+
sourceEntityType: 'auth:user',
|
|
141
|
+
sourceEntityId: String(user.id),
|
|
142
|
+
})
|
|
143
|
+
await notificationService.create(notificationInput, { tenantId, organizationId })
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} catch (err) {
|
|
147
|
+
console.error('[auth.users.roles] Failed to create notification:', err)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
108
151
|
const createUserCommand: CommandHandler<Record<string, unknown>, User> = {
|
|
109
152
|
id: 'auth.users.create',
|
|
110
153
|
async execute(rawInput, ctx) {
|
|
@@ -147,8 +190,10 @@ const createUserCommand: CommandHandler<Record<string, unknown>, User> = {
|
|
|
147
190
|
throw error
|
|
148
191
|
}
|
|
149
192
|
|
|
193
|
+
let assignedRoles: string[] = []
|
|
150
194
|
if (Array.isArray(parsed.roles) && parsed.roles.length) {
|
|
151
195
|
await syncUserRoles(em, user, parsed.roles, tenantId)
|
|
196
|
+
assignedRoles = await loadUserRoleNames(em, String(user.id))
|
|
152
197
|
}
|
|
153
198
|
|
|
154
199
|
await setCustomFieldsIfAny({
|
|
@@ -173,6 +218,10 @@ const createUserCommand: CommandHandler<Record<string, unknown>, User> = {
|
|
|
173
218
|
indexer: userCrudIndexer,
|
|
174
219
|
})
|
|
175
220
|
|
|
221
|
+
if (assignedRoles.length) {
|
|
222
|
+
await notifyRoleChanges(ctx, user, assignedRoles, [])
|
|
223
|
+
}
|
|
224
|
+
|
|
176
225
|
return user
|
|
177
226
|
},
|
|
178
227
|
captureAfter: async (_input, result, ctx) => {
|
|
@@ -288,6 +337,9 @@ const updateUserCommand: CommandHandler<Record<string, unknown>, User> = {
|
|
|
288
337
|
async execute(rawInput, ctx) {
|
|
289
338
|
const { parsed, custom } = parseWithCustomFields(updateSchema, rawInput)
|
|
290
339
|
const em = (ctx.container.resolve('em') as EntityManager)
|
|
340
|
+
const rolesBefore = Array.isArray(parsed.roles)
|
|
341
|
+
? await loadUserRoleNames(em, parsed.id)
|
|
342
|
+
: null
|
|
291
343
|
|
|
292
344
|
if (parsed.email !== undefined) {
|
|
293
345
|
const emailHash = computeEmailHash(parsed.email)
|
|
@@ -377,6 +429,14 @@ const updateUserCommand: CommandHandler<Record<string, unknown>, User> = {
|
|
|
377
429
|
indexer: userCrudIndexer,
|
|
378
430
|
})
|
|
379
431
|
|
|
432
|
+
if (Array.isArray(parsed.roles) && rolesBefore) {
|
|
433
|
+
const rolesAfter = await loadUserRoleNames(em, String(user.id))
|
|
434
|
+
const { assigned, revoked } = diffRoleChanges(rolesBefore, rolesAfter)
|
|
435
|
+
if (assigned.length || revoked.length) {
|
|
436
|
+
await notifyRoleChanges(ctx, user, assigned, revoked)
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
380
440
|
await invalidateUserCache(ctx, parsed.id)
|
|
381
441
|
|
|
382
442
|
return user
|
|
@@ -772,6 +832,14 @@ async function invalidateUserCache(ctx: CommandRuntimeContext, userId: string) {
|
|
|
772
832
|
}
|
|
773
833
|
}
|
|
774
834
|
|
|
835
|
+
function diffRoleChanges(before: string[], after: string[]) {
|
|
836
|
+
const beforeSet = new Set(before)
|
|
837
|
+
const afterSet = new Set(after)
|
|
838
|
+
const assigned = after.filter((role) => !beforeSet.has(role))
|
|
839
|
+
const revoked = before.filter((role) => !afterSet.has(role))
|
|
840
|
+
return { assigned, revoked }
|
|
841
|
+
}
|
|
842
|
+
|
|
775
843
|
function arrayEquals(left: string[] | undefined, right: string[]): boolean {
|
|
776
844
|
if (!left) return false
|
|
777
845
|
if (left.length !== right.length) return false
|
|
@@ -80,6 +80,19 @@
|
|
|
80
80
|
"auth.users.form.errors.load": "Benutzerdaten konnten nicht geladen werden",
|
|
81
81
|
"auth.users.form.errors.aclUpdate": "Aktualisierung der Benutzerberechtigungen fehlgeschlagen",
|
|
82
82
|
"auth.users.form.errors.delete": "Benutzer konnte nicht gelöscht werden",
|
|
83
|
+
"auth.profile.title": "Profil",
|
|
84
|
+
"auth.profile.form.email": "E-Mail",
|
|
85
|
+
"auth.profile.form.password": "Neues Passwort",
|
|
86
|
+
"auth.profile.form.confirmPassword": "Neues Passwort bestätigen",
|
|
87
|
+
"auth.profile.form.save": "Änderungen speichern",
|
|
88
|
+
"auth.profile.form.loading": "Profil wird geladen...",
|
|
89
|
+
"auth.profile.form.errors.load": "Profil konnte nicht geladen werden.",
|
|
90
|
+
"auth.profile.form.errors.save": "Profil konnte nicht aktualisiert werden.",
|
|
91
|
+
"auth.profile.form.errors.invalid": "Ungültige Profilaktualisierung.",
|
|
92
|
+
"auth.profile.form.errors.passwordMismatch": "Die Passwörter stimmen nicht überein.",
|
|
93
|
+
"auth.profile.form.errors.noChanges": "Keine Änderungen zu speichern.",
|
|
94
|
+
"auth.profile.form.errors.emailRequired": "E-Mail ist erforderlich.",
|
|
95
|
+
"auth.profile.form.success": "Profil aktualisiert.",
|
|
83
96
|
"auth.users.list.error.load": "Benutzer konnten nicht geladen werden",
|
|
84
97
|
"auth.users.list.error.delete": "Benutzer konnte nicht gelöscht werden",
|
|
85
98
|
"auth.users.flash.created": "Benutzer erstellt",
|
|
@@ -95,5 +108,20 @@
|
|
|
95
108
|
"auth.email.resetPassword.title": "Passwort zurücksetzen",
|
|
96
109
|
"auth.email.resetPassword.body": "Klicken Sie auf den Link unten, um ein neues Passwort festzulegen. Dieser Link läuft in 60 Minuten ab.",
|
|
97
110
|
"auth.email.resetPassword.cta": "Neues Passwort festlegen",
|
|
98
|
-
"auth.email.resetPassword.hint": "Wenn Sie dies nicht angefordert haben, können Sie diese E-Mail ignorieren."
|
|
111
|
+
"auth.email.resetPassword.hint": "Wenn Sie dies nicht angefordert haben, können Sie diese E-Mail ignorieren.",
|
|
112
|
+
"auth.notifications.passwordReset.requested.title": "Passwort-Zurücksetzung angefordert",
|
|
113
|
+
"auth.notifications.passwordReset.requested.body": "Ein Link zum Zurücksetzen des Passworts wurde an Ihre E-Mail gesendet",
|
|
114
|
+
"auth.notifications.passwordReset.completed.title": "Passwort erfolgreich geändert",
|
|
115
|
+
"auth.notifications.passwordReset.completed.body": "Ihr Passwort wurde erfolgreich aktualisiert",
|
|
116
|
+
"auth.notifications.account.locked.title": "Konto gesperrt",
|
|
117
|
+
"auth.notifications.account.locked.body": "Ihr Konto wurde aus Sicherheitsgründen gesperrt. Bitte wenden Sie sich an den Support.",
|
|
118
|
+
"auth.notifications.login.newDevice.title": "Neues Gerät erkannt",
|
|
119
|
+
"auth.notifications.login.newDevice.body": "Es wurde eine Anmeldung von einem unbekannten Gerät für Ihr Konto erkannt",
|
|
120
|
+
"auth.notifications.role.assigned.title": "Neue Rolle zugewiesen",
|
|
121
|
+
"auth.notifications.role.assigned.body": "Ihnen wurde eine neue Rolle mit zusätzlichen Berechtigungen zugewiesen",
|
|
122
|
+
"auth.notifications.role.revoked.title": "Rolle entfernt",
|
|
123
|
+
"auth.notifications.role.revoked.body": "Eine Rolle wurde von Ihrem Konto entfernt",
|
|
124
|
+
"auth.actions.contactSupport": "Support kontaktieren",
|
|
125
|
+
"auth.actions.viewSessions": "Sitzungen anzeigen",
|
|
126
|
+
"auth.actions.viewPermissions": "Berechtigungen anzeigen"
|
|
99
127
|
}
|
|
@@ -80,6 +80,19 @@
|
|
|
80
80
|
"auth.users.form.errors.load": "Failed to load user data",
|
|
81
81
|
"auth.users.form.errors.aclUpdate": "Failed to update user access control",
|
|
82
82
|
"auth.users.form.errors.delete": "Failed to delete user",
|
|
83
|
+
"auth.profile.title": "Profile",
|
|
84
|
+
"auth.profile.form.email": "Email",
|
|
85
|
+
"auth.profile.form.password": "New password",
|
|
86
|
+
"auth.profile.form.confirmPassword": "Confirm new password",
|
|
87
|
+
"auth.profile.form.save": "Save changes",
|
|
88
|
+
"auth.profile.form.loading": "Loading profile...",
|
|
89
|
+
"auth.profile.form.errors.load": "Failed to load profile.",
|
|
90
|
+
"auth.profile.form.errors.save": "Failed to update profile.",
|
|
91
|
+
"auth.profile.form.errors.invalid": "Invalid profile update.",
|
|
92
|
+
"auth.profile.form.errors.passwordMismatch": "Passwords do not match.",
|
|
93
|
+
"auth.profile.form.errors.noChanges": "No changes to save.",
|
|
94
|
+
"auth.profile.form.errors.emailRequired": "Email is required.",
|
|
95
|
+
"auth.profile.form.success": "Profile updated.",
|
|
83
96
|
"auth.users.list.error.load": "Failed to load users",
|
|
84
97
|
"auth.users.list.error.delete": "Failed to delete user",
|
|
85
98
|
"auth.users.flash.created": "User created",
|
|
@@ -95,5 +108,20 @@
|
|
|
95
108
|
"auth.email.resetPassword.title": "Reset your password",
|
|
96
109
|
"auth.email.resetPassword.body": "Click the link below to set a new password. This link will expire in 60 minutes.",
|
|
97
110
|
"auth.email.resetPassword.cta": "Set a new password",
|
|
98
|
-
"auth.email.resetPassword.hint": "If you didn't request this, you can safely ignore this email."
|
|
111
|
+
"auth.email.resetPassword.hint": "If you didn't request this, you can safely ignore this email.",
|
|
112
|
+
"auth.notifications.passwordReset.requested.title": "Password reset requested",
|
|
113
|
+
"auth.notifications.passwordReset.requested.body": "A password reset link has been sent to your email",
|
|
114
|
+
"auth.notifications.passwordReset.completed.title": "Password successfully changed",
|
|
115
|
+
"auth.notifications.passwordReset.completed.body": "Your password has been updated successfully",
|
|
116
|
+
"auth.notifications.account.locked.title": "Account locked",
|
|
117
|
+
"auth.notifications.account.locked.body": "Your account has been locked due to security reasons. Please contact support.",
|
|
118
|
+
"auth.notifications.login.newDevice.title": "New device login detected",
|
|
119
|
+
"auth.notifications.login.newDevice.body": "A new login from an unrecognized device was detected on your account",
|
|
120
|
+
"auth.notifications.role.assigned.title": "New role assigned",
|
|
121
|
+
"auth.notifications.role.assigned.body": "You have been assigned a new role with additional permissions",
|
|
122
|
+
"auth.notifications.role.revoked.title": "Role removed",
|
|
123
|
+
"auth.notifications.role.revoked.body": "A role has been removed from your account",
|
|
124
|
+
"auth.actions.contactSupport": "Contact Support",
|
|
125
|
+
"auth.actions.viewSessions": "View Sessions",
|
|
126
|
+
"auth.actions.viewPermissions": "View Permissions"
|
|
99
127
|
}
|