From 942143ddc73b62507ae6c003c36cd512838de725 Mon Sep 17 00:00:00 2001
From: ReLoia
Date: Tue, 16 Jun 2026 18:30:26 +0200
Subject: [PATCH 1/6] refactor(auth): centralize role checks into utility
Removed direct user role string checks and replaced with type-safe utils.
---
app/components/Header.vue | 2 +-
app/middleware/auth.ts | 15 ++--------
app/pages/admin/users.vue | 2 +-
app/pages/dashboard/index.vue | 17 +++++-------
app/pages/operator/reports/[id].vue | 6 +++-
app/pages/profile.vue | 2 +-
app/utils/roles.ts | 43 +++++++++++++++++++++++++++++
7 files changed, 61 insertions(+), 26 deletions(-)
create mode 100644 app/utils/roles.ts
diff --git a/app/components/Header.vue b/app/components/Header.vue
index 7db4408..89fb08d 100644
--- a/app/components/Header.vue
+++ b/app/components/Header.vue
@@ -34,7 +34,7 @@ const navLinks = [
]
const { isAuthenticated, user, logout } = useAuth()
-const panelLabel = computed(() => user.value?.role?.toUpperCase() === 'ADMIN' ? 'Pannello' : 'Dashboard')
+const panelLabel = computed(() => isAdmin(user.value?.role) ? 'Pannello' : 'Dashboard')
diff --git a/app/middleware/auth.ts b/app/middleware/auth.ts
index 9cd2cd5..9292130 100644
--- a/app/middleware/auth.ts
+++ b/app/middleware/auth.ts
@@ -20,19 +20,10 @@ export default defineNuxtRouteMiddleware(async (to) => {
const requiredRole = to.meta.auth?.role
if (requiredRole && auth.user.value) {
- const userRole = auth.user.value.role.toUpperCase()
+ const userRole = auth.user.value.role
- const roleLevels: Record = {
- 'CITIZEN': 1,
- 'OPERATOR': 2,
- 'ADMIN': 3
- }
-
- const userLevel = roleLevels[userRole] || 0
- const requiredLevel = roleLevels[requiredRole] || 0
-
- if (userLevel < requiredLevel) {
- console.warn(`[Auth] User role ${userRole} (level ${userLevel}) insufficient for required ${requiredRole} (level ${requiredLevel})`)
+ if (!hasMinimumRole(userRole, requiredRole)) {
+ console.warn(`[Auth] User role ${userRole} insufficient for required ${requiredRole}`)
return navigateTo('/')
}
}
diff --git a/app/pages/admin/users.vue b/app/pages/admin/users.vue
index 774a608..67ccd02 100644
--- a/app/pages/admin/users.vue
+++ b/app/pages/admin/users.vue
@@ -17,7 +17,7 @@ const banError = ref('')
const roleError = ref('')
const pendingBanUser = ref(null)
const isUpdatingBan = ref(false)
-const parseRoles = (roles?: string) => roles ? roles.split(',').map((r) => r.trim().toUpperCase()).filter(Boolean) : []
+const parseRoles = (roles?: string) => roles ? roles.split(',').map(normalizeRole).filter(Boolean) : []
const banConfirmMessage = computed(() =>
pendingBanUser.value
? `Vuoi ${pendingBanUser.value.banned ? 'sbloccare' : 'bannare'} utente ${pendingBanUser.value.first_name} ${pendingBanUser.value.last_name}?`
diff --git a/app/pages/dashboard/index.vue b/app/pages/dashboard/index.vue
index f16106c..d6802a8 100644
--- a/app/pages/dashboard/index.vue
+++ b/app/pages/dashboard/index.vue
@@ -10,24 +10,21 @@ import OperatorDashboard from '~/components/dashboard/OperatorDashboard.vue'
import AdminDashboard from '~/components/dashboard/AdminDashboard.vue'
const currentDashboard = computed(() => {
- const role = user.value?.role?.toUpperCase()
- if (role === 'CITIZEN') return CitizenDashboard
- if (role === 'OPERATOR') return OperatorDashboard
- if (role === 'ADMIN') return AdminDashboard
+ if (isCitizen(user.value?.role)) return CitizenDashboard
+ if (isOperator(user.value?.role)) return OperatorDashboard
+ if (isAdmin(user.value?.role)) return AdminDashboard
return null
})
const heroTitle = computed(() => {
- const role = user.value?.role?.toUpperCase()
- if (role === 'ADMIN') return 'Pannello Admin'
- if (role === 'OPERATOR') return 'Gestione Operativa'
+ if (isAdmin(user.value?.role)) return 'Pannello Admin'
+ if (isOperator(user.value?.role)) return 'Gestione Operativa'
return 'Dashboard'
})
const heroSubtitle = computed(() => {
- const role = user.value?.role?.toUpperCase()
- if (role === 'ADMIN') return 'Accesso amministrativo per configurazioni, utenti e moderazione'
- if (role === 'OPERATOR') return 'Prendi in carico e gestisci le segnalazioni in corso'
+ if (isAdmin(user.value?.role)) return 'Accesso amministrativo per configurazioni, utenti e moderazione'
+ if (isOperator(user.value?.role)) return 'Prendi in carico e gestisci le segnalazioni in corso'
return 'Riepilogo rapido delle tue segnalazioni e dei punti cittadino'
})
diff --git a/app/pages/operator/reports/[id].vue b/app/pages/operator/reports/[id].vue
index f45d676..9c958dc 100644
--- a/app/pages/operator/reports/[id].vue
+++ b/app/pages/operator/reports/[id].vue
@@ -129,7 +129,9 @@ const handleStatusUpdate = async () => {
}
const handleAssignTeam = async () => {
+ if (isAdmin(user.value?.role)) return
if (!selectedTeamId.value) return
+
isAssigning.value = true
try {
const res = await assignTeam(reportId, {
@@ -229,6 +231,8 @@ const handleSendMessage = async (msgText: string, files: File[]) => {
const canFlagMessage = (message: Message) => {
if (!user.value) return false
+ if (isAdmin(user.value?.role)) return false
+
const isCitizenMessage = message.user_id !== user.value.id && message.user_id === report.value?.user_id
const isAlreadyFlagged = flaggedMessageIds.value.includes(message.id)
return isCitizenMessage && !isAlreadyFlagged
@@ -408,7 +412,7 @@ const formattedInterventionLinkExpiry = computed(() =>
-
+
diff --git a/app/pages/profile.vue b/app/pages/profile.vue
index f65d6c8..246a7cb 100644
--- a/app/pages/profile.vue
+++ b/app/pages/profile.vue
@@ -71,7 +71,7 @@ const formatDate = (dateString: string) =>
Ruolo
- {{ user?.role }}
+ {{ normalizeRole(user?.role) }}
diff --git a/app/utils/roles.ts b/app/utils/roles.ts
new file mode 100644
index 0000000..ef77b5c
--- /dev/null
+++ b/app/utils/roles.ts
@@ -0,0 +1,43 @@
+export const ROLES = {
+ CITIZEN: 'CITIZEN',
+ OPERATOR: 'OPERATOR',
+ ADMIN: 'ADMIN'
+} as const
+
+export type Role = keyof typeof ROLES
+
+export const ROLE_LEVELS: Record = {
+ [ROLES.CITIZEN]: 1,
+ [ROLES.OPERATOR]: 2,
+ [ROLES.ADMIN]: 3
+}
+
+export function normalizeRole(role?: string | null): string {
+ return role?.trim().toUpperCase() ?? ''
+}
+
+export function isRole(userRole: string | undefined | null, targetRole: string): boolean {
+ return normalizeRole(userRole) === targetRole.toUpperCase()
+}
+
+export function hasMinimumRole(userRole: string | undefined | null, requiredRole: string): boolean {
+ const userLvl = ROLE_LEVELS[normalizeRole(userRole)] || 0
+ const reqLvl = ROLE_LEVELS[requiredRole.toUpperCase()] || 0
+ return userLvl >= reqLvl
+}
+
+export function isAdmin(userRole?: string | null): boolean {
+ return isRole(userRole, ROLES.ADMIN)
+}
+
+export function isOperator(userRole?: string | null): boolean {
+ return isRole(userRole, ROLES.OPERATOR)
+}
+
+export function isCitizen(userRole?: string | null): boolean {
+ return isRole(userRole, ROLES.CITIZEN)
+}
+
+export function canAccess(userRole: string | undefined | null, requiredRole: string): boolean {
+ return hasMinimumRole(userRole, requiredRole)
+}
From f32fa8620b2e4bbd2603a424e946572d0b7d1509 Mon Sep 17 00:00:00 2001
From: ReLoia
Date: Tue, 16 Jun 2026 18:30:40 +0200
Subject: [PATCH 2/6] style: remove unnecessary inline comments
Removed Italian and redundant inline comments across multiple files.
---
app/components/ReportForm.vue | 4 ----
app/middleware/auth.ts | 3 ---
app/pages/admin/users.vue | 1 -
app/pages/auth/login.vue | 1 -
app/pages/info.vue | 2 --
app/pages/open-data.vue | 3 ---
app/pages/reports/[id].vue | 1 -
app/types/api.d.ts | 2 +-
8 files changed, 1 insertion(+), 16 deletions(-)
diff --git a/app/components/ReportForm.vue b/app/components/ReportForm.vue
index 650e689..fa72ecd 100644
--- a/app/components/ReportForm.vue
+++ b/app/components/ReportForm.vue
@@ -45,7 +45,6 @@ const isResolvingAddress = ref(false)
const addressSearchError = ref('')
const showAddressSuggestions = ref(false)
-// Map variables
const mapContainer = ref(null)
let leaflet: typeof import('leaflet') | null = null
let map: LeafletMap | null = null
@@ -62,7 +61,6 @@ const loadLeaflet = async () => {
const leafletModule = await import('leaflet')
- // Fix per le icone di Leaflet in Nuxt/Vite
const defaultIcon = leafletModule.icon({
iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png',
iconRetinaUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png',
@@ -276,7 +274,6 @@ const initMap = async () => {
void updateLocation(e.latlng.lat, e.latlng.lng)
})
- // Se c'è già una posizione, imposta il marker
if (form.value.latitude && form.value.longitude) {
void updateLocation(form.value.latitude, form.value.longitude, { zoomTo: true })
}
@@ -435,7 +432,6 @@ watch(() => form.value.address, (newAddress) => {
}, 350)
})
-// Sincronizza input manuali con la mappa
watch([() => form.value.latitude, () => form.value.longitude], ([newLat, newLng]) => {
if (newLat && newLng && map && leaflet) {
if (!marker || marker.getLatLng().lat !== newLat || marker.getLatLng().lng !== newLng) {
diff --git a/app/middleware/auth.ts b/app/middleware/auth.ts
index 9292130..46b3079 100644
--- a/app/middleware/auth.ts
+++ b/app/middleware/auth.ts
@@ -1,7 +1,6 @@
export default defineNuxtRouteMiddleware(async (to) => {
const auth = useAuth()
- // Se c'è un token ma l'utente non è ancora caricato e non stiamo già sincronizzando, sincronizza
if ((auth.token.value || auth.tokenState.value) && !auth.user.value && !auth.isSyncingUser.value) {
try {
await auth.syncUser({ currentPath: to.path })
@@ -11,12 +10,10 @@ export default defineNuxtRouteMiddleware(async (to) => {
}
}
- // Se non autenticato, redirige al login
if (!auth.isAuthenticated.value) {
return navigateTo('/auth/login')
}
- // Verifica ruoli se richiesto
const requiredRole = to.meta.auth?.role
if (requiredRole && auth.user.value) {
diff --git a/app/pages/admin/users.vue b/app/pages/admin/users.vue
index 67ccd02..1e1af4c 100644
--- a/app/pages/admin/users.vue
+++ b/app/pages/admin/users.vue
@@ -68,7 +68,6 @@ const handleToggleBan = async () => {
}
}
-// Modal per i ruoli
const editingUserId = ref(null)
const selectedRoles = ref([])
const availableRoles = ['CITIZEN', 'OPERATOR', 'ADMIN']
diff --git a/app/pages/auth/login.vue b/app/pages/auth/login.vue
index 38ccab9..50ceafe 100644
--- a/app/pages/auth/login.vue
+++ b/app/pages/auth/login.vue
@@ -9,7 +9,6 @@ useHead({ title: 'Accedi - Visione' })
const { requestOtp, verifyOtp, handleLoginSuccess } = useAuth()
-// UI State
const otpStep = ref<1 | 2>(1)
const isLoading = ref(false)
const errorMessage = ref('')
diff --git a/app/pages/info.vue b/app/pages/info.vue
index e5f9c76..9b2b089 100644
--- a/app/pages/info.vue
+++ b/app/pages/info.vue
@@ -1,5 +1,4 @@