feat(fullstack): Add notifications, user is now an entity, add testcontainers, rework custom permissions, get user from JWT in endpoints

This commit is contained in:
2025-08-09 10:09:00 +02:00
parent a5eae07eaf
commit 7e55a336f2
44 changed files with 1571 additions and 139 deletions

View File

@@ -0,0 +1,118 @@
import type { NotificationDto } from '~/.api-client'
export const useNotification = () => {
const {
getNotifications,
getUnreadNotifications,
getUnreadNotificationCount,
markAllNotificationsAsRead,
markNotificationAsRead
} = useNotificationApi()
const notifications = ref<NotificationDto[]>([])
const unreadNotifications = ref<NotificationDto[]>([])
const unreadCount = ref<number>(0)
const isLoading = ref(false)
const fetchNotifications = async (page: number = 0, size: number = 20) => {
isLoading.value = true
try {
const response = await getNotifications(page, size)
notifications.value = response.content || []
return response
} catch (error) {
console.error('Failed to fetch notifications:', error)
throw error
} finally {
isLoading.value = false
}
}
const fetchUnreadNotifications = async () => {
try {
const response = await getUnreadNotifications()
unreadNotifications.value = response || []
return response
} catch (error) {
console.error('Failed to fetch unread notifications:', error)
throw error
}
}
const fetchUnreadCount = async () => {
try {
const count = await getUnreadNotificationCount()
unreadCount.value = count || 0
return count
} catch (error) {
console.error('Failed to fetch unread count:', error)
throw error
}
}
const markAllAsRead = async () => {
try {
await markAllNotificationsAsRead()
unreadCount.value = 0
unreadNotifications.value = []
notifications.value = notifications.value.map((n) => ({ ...n, isRead: true }))
} catch (error) {
console.error('Failed to mark all as read:', error)
throw error
}
}
const markAsRead = async (notificationId: string) => {
try {
await markNotificationAsRead(notificationId)
const index = notifications.value.findIndex((n) => n.id === notificationId)
if (index !== -1) {
notifications.value[index].isRead = true
}
// Remove from unread notifications
unreadNotifications.value = unreadNotifications.value.filter((n) => n.id !== notificationId)
if (unreadCount.value > 0) {
unreadCount.value--
}
} catch (error) {
console.error('Failed to mark notification as read:', error)
throw error
}
}
const handleNotificationClick = async (notification: NotificationDto) => {
if (!notification.isRead) {
await markAsRead(notification.id)
}
if (notification.clickTarget) {
await navigateTo(notification.clickTarget)
}
}
const startPeriodicRefresh = (intervalMs: number = 30000) => {
const interval = setInterval(() => {
fetchUnreadCount()
}, intervalMs)
onUnmounted(() => {
clearInterval(interval)
})
return interval
}
return {
notifications: readonly(notifications),
unreadNotifications: readonly(unreadNotifications),
unreadCount: readonly(unreadCount),
isLoading: readonly(isLoading),
fetchNotifications,
fetchUnreadNotifications,
fetchUnreadCount,
markAllAsRead,
markAsRead,
handleNotificationClick,
startPeriodicRefresh
}
}

View File

@@ -0,0 +1,55 @@
import {
NotificationApi,
Configuration,
type NotificationDto,
type PagedNotificationDto,
type CreateNotificationDto
} from '~/.api-client'
import { cleanDoubleSlashes, withoutTrailingSlash } from 'ufo'
export function useNotificationApi() {
const appBaseUrl = useRuntimeConfig().app.baseURL
const { serverApiBaseUrl, serverApiBasePath, clientProxyBasePath } = useRuntimeConfig().public
const { jwt } = useAuth()
const basePath = withoutTrailingSlash(
cleanDoubleSlashes(import.meta.client ? appBaseUrl + clientProxyBasePath : serverApiBaseUrl + serverApiBasePath)
)
const notificationApiClient = new NotificationApi(
new Configuration({ basePath, headers: { Authorization: jwt.value ? `Bearer ${jwt.value}` : '' } })
)
async function createNotification(createNotificationDto: CreateNotificationDto): Promise<NotificationDto> {
return notificationApiClient.createNotification({ createNotificationDto })
}
async function getNotifications(page?: number, size?: number): Promise<PagedNotificationDto> {
return notificationApiClient.getNotifications({ page, size })
}
async function getUnreadNotifications(): Promise<NotificationDto[]> {
return notificationApiClient.getUnreadNotifications()
}
async function getUnreadNotificationCount(): Promise<number> {
return notificationApiClient.getUnreadNotificationCount()
}
async function markAllNotificationsAsRead(): Promise<void> {
return notificationApiClient.markAllNotificationsAsRead()
}
async function markNotificationAsRead(id: string): Promise<NotificationDto> {
return notificationApiClient.markNotificationAsRead({ id })
}
return {
createNotification,
getNotifications,
getUnreadNotifications,
getUnreadNotificationCount,
markAllNotificationsAsRead,
markNotificationAsRead
}
}