feat(admin/users): editable member drawer + mailbox & ownership management
ci / typecheck (map[dir:apps/booking name:booking]) (push) Has been cancelled
ci / typecheck (map[dir:apps/portal name:portal]) (push) Has been cancelled
ci / typecheck (map[dir:apps/website name:website]) (push) Has been cancelled
ci / typecheck (map[dir:services/platform-api name:platform-api]) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / typecheck (map[dir:apps/booking name:booking]) (push) Has been cancelled
ci / typecheck (map[dir:apps/portal name:portal]) (push) Has been cancelled
ci / typecheck (map[dir:apps/website name:website]) (push) Has been cancelled
ci / typecheck (map[dir:services/platform-api name:platform-api]) (push) Has been cancelled
ci / test (push) Has been cancelled
Rebuild the /admin/users detail drawer from a read-only profile into an editable, Office 365-style panel with four sections: - Username & mail: read-only primary for mailbox users; editable sign-in (Authentik-only) for mailbox-less identities; "Create mailbox" provisions a Stalwart inbox for an external-login admin - Aliases: list/add/remove mailbox aliases (Stalwart), domain-scoped - Role: member/admin toggle with a primary-account lock (owner, mailbox-less bootstrap admin, self) and a last-admin guard - Contact information: display name, first/last name, phone, alternative email — mirrored best-effort to Authentik attributes + mailbox name Ownership transfer: "Make owner" (row menu + drawer) plus an owner-side "Transfer ownership" picker, gated to tenant admins / platform admins so a departed owner can be replaced; promotes the target and demotes the prior owner to admin. Backend (platform-api): contact fields on User; AuthentikClient.updateUser; StalwartClient.setMailboxName; UsersService updateTenantMember, changeMemberPrimaryEmail, list/add/removeMemberAlias, createMailboxForMember, transferOwnership; new DTOs and tenant-member routes. All mutations audited. Portal: Nuxt proxies for the new endpoints + extended TenantUserDoc.
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
// Remove a mailbox alias. Proxies DELETE /tenants/:slug/users/:userId/aliases
|
||||
// (address in body) and returns the refreshed alias view.
|
||||
import { getUserSession } from 'nuxt-oidc-auth/runtime/server/utils/session.js'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const session = await getUserSession(event).catch(() => null)
|
||||
const accessToken = (session as { accessToken?: string } | null)?.accessToken
|
||||
if (!accessToken) throw createError({ statusCode: 401, statusMessage: 'Not signed in' })
|
||||
const slug = getRouterParam(event, 'slug')
|
||||
const userId = getRouterParam(event, 'userId')
|
||||
const body = await readBody(event)
|
||||
const base = process.env.PLATFORM_API_INTERNAL_URL ?? 'http://platform-api:3001'
|
||||
return $fetch(`${base}/tenants/${slug}/users/${userId}/aliases`, {
|
||||
method: 'DELETE',
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
body,
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,15 @@
|
||||
// List a member's mailbox aliases. Proxies GET /tenants/:slug/users/:userId/aliases
|
||||
// → { hasMailbox, primary, aliases }.
|
||||
import { getUserSession } from 'nuxt-oidc-auth/runtime/server/utils/session.js'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const session = await getUserSession(event).catch(() => null)
|
||||
const accessToken = (session as { accessToken?: string } | null)?.accessToken
|
||||
if (!accessToken) throw createError({ statusCode: 401, statusMessage: 'Not signed in' })
|
||||
const slug = getRouterParam(event, 'slug')
|
||||
const userId = getRouterParam(event, 'userId')
|
||||
const base = process.env.PLATFORM_API_INTERNAL_URL ?? 'http://platform-api:3001'
|
||||
return $fetch(`${base}/tenants/${slug}/users/${userId}/aliases`, {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,18 @@
|
||||
// Add a mailbox alias. Proxies POST /tenants/:slug/users/:userId/aliases and
|
||||
// returns the refreshed alias view.
|
||||
import { getUserSession } from 'nuxt-oidc-auth/runtime/server/utils/session.js'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const session = await getUserSession(event).catch(() => null)
|
||||
const accessToken = (session as { accessToken?: string } | null)?.accessToken
|
||||
if (!accessToken) throw createError({ statusCode: 401, statusMessage: 'Not signed in' })
|
||||
const slug = getRouterParam(event, 'slug')
|
||||
const userId = getRouterParam(event, 'userId')
|
||||
const body = await readBody(event)
|
||||
const base = process.env.PLATFORM_API_INTERNAL_URL ?? 'http://platform-api:3001'
|
||||
return $fetch(`${base}/tenants/${slug}/users/${userId}/aliases`, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
body,
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,18 @@
|
||||
// Provision a mailbox for a member who has none. Proxies
|
||||
// POST /tenants/:slug/users/:userId/mailbox → { email, tempPassword, user }.
|
||||
import { getUserSession } from 'nuxt-oidc-auth/runtime/server/utils/session.js'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const session = await getUserSession(event).catch(() => null)
|
||||
const accessToken = (session as { accessToken?: string } | null)?.accessToken
|
||||
if (!accessToken) throw createError({ statusCode: 401, statusMessage: 'Not signed in' })
|
||||
const slug = getRouterParam(event, 'slug')
|
||||
const userId = getRouterParam(event, 'userId')
|
||||
const body = await readBody(event)
|
||||
const base = process.env.PLATFORM_API_INTERNAL_URL ?? 'http://platform-api:3001'
|
||||
return $fetch(`${base}/tenants/${slug}/users/${userId}/mailbox`, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
body,
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,16 @@
|
||||
// Transfer workspace ownership to this member. Proxies
|
||||
// POST /tenants/:slug/users/:userId/make-owner → { newOwner, previousOwners }.
|
||||
import { getUserSession } from 'nuxt-oidc-auth/runtime/server/utils/session.js'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const session = await getUserSession(event).catch(() => null)
|
||||
const accessToken = (session as { accessToken?: string } | null)?.accessToken
|
||||
if (!accessToken) throw createError({ statusCode: 401, statusMessage: 'Not signed in' })
|
||||
const slug = getRouterParam(event, 'slug')
|
||||
const userId = getRouterParam(event, 'userId')
|
||||
const base = process.env.PLATFORM_API_INTERNAL_URL ?? 'http://platform-api:3001'
|
||||
return $fetch(`${base}/tenants/${slug}/users/${userId}/make-owner`, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,18 @@
|
||||
// Change a mailbox-less member's primary email. Proxies
|
||||
// PATCH /tenants/:slug/users/:userId/primary-email and returns the fresh user doc.
|
||||
import { getUserSession } from 'nuxt-oidc-auth/runtime/server/utils/session.js'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const session = await getUserSession(event).catch(() => null)
|
||||
const accessToken = (session as { accessToken?: string } | null)?.accessToken
|
||||
if (!accessToken) throw createError({ statusCode: 401, statusMessage: 'Not signed in' })
|
||||
const slug = getRouterParam(event, 'slug')
|
||||
const userId = getRouterParam(event, 'userId')
|
||||
const body = await readBody(event)
|
||||
const base = process.env.PLATFORM_API_INTERNAL_URL ?? 'http://platform-api:3001'
|
||||
return $fetch(`${base}/tenants/${slug}/users/${userId}/primary-email`, {
|
||||
method: 'PATCH',
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
body,
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user