feat: userProfile

This commit is contained in:
Michael 2025-07-18 14:10:37 +02:00
parent 8f24453eff
commit 13a3de9709
16 changed files with 492 additions and 284 deletions

View file

@ -26,7 +26,7 @@ class UpdateUserPassword implements UpdatesUserPasswords
])->validateWithBag('updatePassword');
$user->forceFill([
'password' => Hash::make($input['password']),
'pass' => Hash::make($input['password']),
])->save();
}
}

View file

@ -13,43 +13,48 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
/**
* Validate and update the given user's profile information.
*
* @param array<string, string> $input
* @param array<string, mixed> $input
*/
public function update(User $user, array $input): void
{
// Use PHP's built-in list of timezones for robust validation
$timezones = timezone_identifiers_list();
$rules = [
'login' => ['required', 'string', 'max:255', Rule::unique('cc_subjs')->ignore($user->id)],
'email' => [
'required',
'string',
'email',
'max:255',
Rule::unique('cc_subjs')->ignore($user->id),
],
'email' => ['nullable', 'string', 'email', 'max:255', Rule::unique('cc_subjs')->ignore($user->id)],
'first_name' => ['nullable', 'string', 'max:255'],
'last_name' => ['nullable', 'string', 'max:255'],
'cell_phone' => ['nullable', 'string', 'max:25'],
'timezone' => ['nullable', 'string', Rule::in($timezones)],
];
// Only add the 'type' validation rule if the user has the permission to change roles
if (auth()->user()->hasPermissionTo('users.changeRole')) {
if (isset($rules['type']) && auth()->user()->hasPermissionTo('users.changeRole')) {
$rules['type'] = ['required', 'string', 'max:6', Rule::in(['admin', 'editor', 'dj'])];
}
Validator::make($input, $rules)->validateWithBag('updateProfileInformation');
if ($input['email'] !== $user->email && $user instanceof MustVerifyEmail) {
$this->updateVerifiedUser($user, $input);
} else {
$data = [
'login' => $input['login'],
'email' => $input['email'],
'first_name' => $input['first_name'],
'last_name' => $input['last_name'],
'cell_phone' => $input['cell_phone'],
];
// Only update 'type' if the user has the permission
if (auth()->user()->hasPermissionTo('users.changeRole')) {
if ($input['email'] !== $user->email && $user instanceof MustVerifyEmail) {
$this->updateVerifiedUser($user, $input);
} else {
if (isset($rules['type']) && auth()->user()->hasPermissionTo('users.changeRole')) {
$data['type'] = $input['type'];
}
$user->forceFill($data)->save();
}
// The timezone is handled by the mutator in the User model
$user->timezone = $input['timezone'];
}
/**
@ -62,16 +67,22 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
$data = [
'login' => $input['login'],
'email' => $input['email'],
'first_name' => $input['first_name'],
'last_name' => $input['last_name'],
'cell_phone' => $input['cell_phone'],
'email_verified_at' => null,
];
// Only update 'type' if the user has the permission
if (auth()->user()->hasPermissionTo('user.changeRole')) {
// Corrected permission name to be consistent
if (auth()->user()->hasPermissionTo('users.changeRole')) {
$data['type'] = $input['type'];
}
$user->forceFill($data)->save();
// The timezone is handled by the mutator in the User model
$user->timezone = $input['timezone'];
$user->sendEmailVerificationNotification();
}
}

View file

@ -56,34 +56,38 @@ class UserController extends Controller
public function userProfile()
{
return response()->json(auth()->user());
$user =auth()->user();
$user->role = $user->roles()->value('name');
return response()->json($user);
}
public function update(Request $request, User $user, UpdateUserProfileInformation $updater)
{
$authenticatedUser = auth()->user();
if ($authenticatedUser->id !== $user->id && ! $authenticatedUser->hasPermissionTo('user.manageAll')) {
if ($authenticatedUser->id !== $user->id && !$authenticatedUser->hasPermissionTo('user.manageAll')) {
return response()->json(['message' => 'You do not have permission to edit other users.'], 403);
}
if ($authenticatedUser->id === $user->id && ! $authenticatedUser->hasPermissionTo('users.manageOwn')) {
if ($authenticatedUser->id === $user->id && !$authenticatedUser->hasPermissionTo('users.manageOwn')) {
return response()->json(['message' => 'You do not have permission to edit your own profile.'], 403);
}
try {
(new UpdateUserProfileInformation())->update($user, $request->all());
$updater->update($user, $request->all());
$user->load('preferences');
return response()->json($user);
} catch (\Throwable $e) {
Log::error($e->getMessage());
if ($e instanceof \Illuminate\Validation\ValidationException) {
return response()->json(['message' => $e->getMessage(), 'errors' => $e->errors()], 422);
}
return response()->json(['message' => 'Failed to update user'], 500);
}
}
public function destroy(Request $request)
{
try {

View file

@ -8,6 +8,8 @@ import { createPinia } from "pinia";
import { createI18n } from "vue-i18n";
import App from "@/layouts/App.vue";
import { useAuthStore } from '@/stores/auth.store';
const pinia = createPinia();
const i18n = createI18n(vueI18n);
const app = createApp(App);
@ -16,4 +18,8 @@ app.use(pinia)
.use(i18n)
.use(router)
.use(vuetify)
.mount("#app");
const auth = useAuthStore();
auth.fetchUser().finally(() => {
app.mount("#app");
});

View file

@ -6,7 +6,7 @@
import axios from 'axios';
window.axios = axios;
window.axios.defaults.withCredentials = true
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
/**

View file

@ -91,6 +91,7 @@ onDeactivated(() => {
</script>
<template>
<div>
<template v-if="showCreateEditMode || showInstanceCreateEditMode">
<ShowForm v-if="showCreateEditMode" :showId="showSelected" @go-back="goBack"/>
<ShowInstanceForm v-if="showInstanceCreateEditMode" :showInstance="selectedShowInstance"
@ -114,6 +115,7 @@ onDeactivated(() => {
@contextMenuDeleteShow="contextMenuDeleteShow"
/>
</template>
</div>
</template>
<style scoped>

View file

@ -1,91 +1,233 @@
<script setup lang="ts">
import { useAuthStore } from '@/stores/auth.store.ts'; // Adjust the import path to your store
import { storeToRefs } from 'pinia';
import { ref } from 'vue';
import {useAuthStore} from '@/stores/auth.store.ts';
import {storeToRefs} from 'pinia';
import {onBeforeMount, ref, reactive} from 'vue';
import {useRouter} from "vue-router";
import axios from "axios";
const router = useRouter();
const authStore = useAuthStore();
const { userData } = storeToRefs(authStore);
const {userData} = storeToRefs(authStore);
const localUserData = reactive({...userData.value});
const emit = defineEmits([
'userProfilePage'
])
]);
// Sample data for selection components
const roles = ['Admin', 'User', 'Editor'];
const timezones = [
'UTC',
'America/New_York',
'America/Chicago',
'America/Denver',
'America/Los_Angeles',
'Europe/London',
'Europe/Berlin',
'Asia/Tokyo',
];
let timezones = ref<string[]>([]);
let roleList = ref<string[]>([]);
const form = ref(null);
const form = ref<HTMLFormElement | null>(null);
const passwordForm = ref<HTMLFormElement | null>(null);
const dialog = ref(false);
const passwordData = reactive({
oldPassword: '',
newPassword: '',
confirmPassword: '',
});
const formRules = {
'emailRules': [(v: string) => !v || /.+@.+\..+/.test(v) || 'E-mail must be a valid format'],
'cellphoneRules': [(v: string) => !v || /^[0-9-()]*$/.test(v) || 'Cellphone must be a valid number'],
'passwordConfirmationRule': [
(v: string) => !!v || 'Password confirmation is required',
(v: string) => v === passwordData.newPassword || 'Passwords do not match'
],
'requiredRule': [(v: string) => !!v || 'This field is required'],
}
const saveUser = async () => {
if (!form.value) return
const {valid} = await form.value.validate();
if (!valid) return
authStore.userData = {...localUserData};
await authStore.updateUser();
};
const goBack = () => {
router.go(-1);
}
const openPasswordDialog = () => {
dialog.value = true;
};
const closePasswordDialog = () => {
dialog.value = false;
passwordForm.value?.reset();
passwordForm.value?.resetValidation();
};
const resetPassword = async () => {
console.log('aaaa')
try {
await axios.put('/api/user/password', passwordData)
console.log('Password changed');
closePasswordDialog()
return
} catch (e) {
const errorMessage = e.response?.data?.error || 'An unexpected error occurred.';
console.error('Error changing password:' + errorMessage);
return
}
};
onBeforeMount(async () => {
await axios.get('/timezoneList').then(response => {
timezones.value = response?.data
})
if (userData.value.role === 'admin') {
await axios.get('/roleList').then(response => {
roleList.value = response?.data
})
}
})
</script>
<template>
<div>
<v-form ref="form">
<v-container>
<v-row>
<v-col cols="12" md="6">
<v-text-field
v-model="userData.login"
v-model="localUserData.login"
label="Login"
required
hint="Your public username."
persistent-hint
></v-text-field>
</v-col>
<v-col cols="12" md="6">
<v-text-field
v-model="localUserData.email"
:rules="formRules.emailRules"
label="Email Address"
hint="Used for notifications and account recovery."
persistent-hint
></v-text-field>
</v-col>
<v-col cols="12" md="6">
<v-text-field
v-model="localUserData.firstName"
label="First Name"
required
></v-text-field>
</v-col>
<v-col cols="12" md="6">
<v-text-field
v-model="userData.firstName"
label="First Name"
v-model="localUserData.lastName"
label="Last Name"
required
></v-text-field>
</v-col>
<v-col cols="12" md="6">
<v-text-field
v-model="userData.lastName"
label="Last Name"
v-model="localUserData.cellPhone"
:rules="formRules.cellphoneRules"
label="Cell Phone"
></v-text-field>
</v-col>
<v-col cols="12" md="6">
<v-autocomplete
v-model="userData.timezone"
v-model="localUserData.timezone"
:items="timezones"
label="Timezone"
hint="Sets the time for all events and schedules."
persistent-hint
></v-autocomplete>
</v-col>
<v-col cols="12" md="6">
<v-text-field
v-model="userData.email"
label="email"
></v-text-field>
<v-select
v-model="localUserData.role"
:items="roleList"
label="Ruolo"
:disabled="userData.role !== 'admin'"
></v-select>
</v-col>
<v-col cols="12" md="6">
<v-text-field
v-model="userData.cellPhone"
label="cellPhone"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col>
<v-col class="d-flex justify-end">
<v-btn color="secondary" @click="goBack" class="mr-4">
Back
</v-btn>
<v-btn color="primary" @click="saveUser">
Salva
Save Changes
</v-btn>
<v-btn color="error" @click="openPasswordDialog">
Reset Password
</v-btn>
</v-col>
</v-row>
</v-container>
</v-form>
<v-dialog v-model="dialog" persistent max-width="600px">
<v-card>
<v-card-title>
<span class="text-h5">Reset Your Password</span>
</v-card-title>
<v-card-text>
<p class="text-subtitle-1 mb-4">
Please be sure about the new password you are choosing. Password recovery via email is not implemented yet,
so
a forgotten password cannot be recovered.
</p>
</v-card-text>
<v-form ref="passwordForm" @submit.prevent="resetPassword">
<v-container>
<v-row>
<v-col cols="12">
<v-text-field
v-model="passwordData.oldPassword"
label="Old Password"
type="password"
:rules="formRules.requiredRule"
required
></v-text-field>
</v-col>
<v-col cols="12">
<v-text-field
v-model="passwordData.newPassword"
label="New Password"
type="password"
:rules="formRules.requiredRule"
required
></v-text-field>
</v-col>
<v-col cols="12">
<v-text-field
v-model="passwordData.confirmPassword"
label="Confirm New Password"
type="password"
:rules="formRules.passwordConfirmationRule"
required
></v-text-field>
</v-col>
</v-row>
</v-container>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue-darken-1" variant="text" @click="closePasswordDialog">
Cancel
</v-btn>
<v-btn type="submit" color="blue-darken-1" variant="tonal">
Confirm
</v-btn>
</v-card-actions>
</v-form>
</v-card>
</v-dialog>
</div>
</template>

View file

@ -7,21 +7,9 @@ const router = useRouter();
const auth = useAuthStore();
const userName = auth.userData.login;
const emit = defineEmits([
'userProfilePage'
])
const userProfilePage = () => {
emit('userProfilePage')
}
const logout = async () => {
try {
await axios.get('/logout');
auth.resetUser();
router.push('/login');
} catch (error) {
console.error('Error logging out:', error);
}
await auth.logout()
router.push({ path: 'login' });
};
</script>
@ -30,7 +18,7 @@ const logout = async () => {
<v-sheet
:width="150"
>
<v-btn color="info" @click="userProfilePage">{{ userName }} {{ $t('header.userinfo.info') }}</v-btn>
<v-btn color="info" :to="{ path: 'user-profile'}">{{ userName }} {{ $t('header.userinfo.info') }}</v-btn>
<v-btn color="" @click="logout">{{ $t('header.userinfo.logout') }}</v-btn>
</v-sheet>
</template>

View file

@ -1,11 +1,74 @@
import { createRouter, createWebHistory } from "vue-router";
import { useAuthStore } from "@/stores/auth.store.ts";
import {createRouter, createWebHashHistory, createWebHistory, type RouteRecordRaw} from "vue-router";
import {useAuthStore} from "@/stores/auth.store.ts";
import {useShowTypeStore} from '@/stores/showType.store';
const routes = [
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'Dashboard',
name: 'Backoffice',
component: () => import('../pages/Backoffice.vue'),
children: [
{
path: '',
name: 'Dashboard',
component: () => import('@/components/content/Dashboard.vue')
},
{
path: 'show',
name: 'Trasmissioni',
component: () => import('@/components/content/Show.vue'),
meta: {showType: 'shows'}
},
{
path: 'archive',
name: 'Archivio',
component: () => import('@/components/content/Archive.vue')},
{
path: 'playlist',
name: 'Playlist',
component: () => import('@/components/content/Playlist.vue'),
meta: {showType: 'shows'}
},
{
path: 'blocks',
name: 'Blocchi dinamici',
component: () => import('@/components/content/SmartBlock.vue'),
meta: {showType: 'shows'}
},
{
path: 'podcast',
name: 'Podcast',
component: () => import('@/components/content/Podcast.vue')
},
{
path: 'webstream',
name: 'Webstream',
component: () => import('@/components/content/Webstream.vue')
},
{
path: 'spot',
name: 'Spot',
component: () => import('@/components/content/Show.vue'),
meta: {showType: 'spots'}
},
{
path: 'spot-playlist',
name: 'Spot playlist',
component: () => import('@/components/content/Playlist.vue'),
meta: {showType: 'spots'}
},
{
path: 'spot-blocks',
name: 'Spot Blocchi dinamici',
component: () => import('@/components/content/SmartBlock.vue'),
meta: {showType: 'spots'}
},
{
path: 'user-profile',
name: 'UserProfile',
component: () => import('@/components/content/UserProfile.vue')
},
]
},
{
path: '/login',
@ -15,22 +78,35 @@ const routes = [
];
const router = createRouter({
history: createWebHistory(),
history: createWebHashHistory(),
routes
});
export default router;
/**
* Redirect to login page if unauthenticated
* Navigation Guards
*/
router.beforeEach(async (to) => {
router.beforeEach(async (to, from, next) => {
const publicPages = ['/login'];
const authRequired = !publicPages.includes(to.path);
const auth = useAuthStore();
if (authRequired && !auth.userData.login) {
return '/login';
return next('/login');
}
const showTypeStore = useShowTypeStore();
switch (to.meta.showType) {
case 'shows':
showTypeStore.setAsShows();
break;
case 'spots':
showTypeStore.setAsSpots();
break;
default:
showTypeStore.clearType();
}
next();
});
export default router;

View file

@ -46,6 +46,7 @@ watch(currentPage, (newVal) => {
<template>
<v-col>
<router-view></router-view>
<!-- <keep-alive>-->
<Component :is="tabs[currentPage]" />
<!-- </keep-alive>-->

View file

@ -3,14 +3,7 @@ import OnAir from "@/components/header/OnAir.vue";
import Clock from "@/components/header/Clock.vue";
import Timer from "@/components/header/Timer.vue";
import UserInfo from "@/components/header/UserInfo.vue";
const emit = defineEmits([
'userProfilePage'
])
const userProfilePage = () => {
emit('userProfilePage')
}
import {useRouter} from "vue-router";
</script>
<template>
@ -18,7 +11,7 @@ const userProfilePage = () => {
<OnAir />
<Clock />
<Timer />
<UserInfo @user-profile-page="userProfilePage" />
<UserInfo />
</header>
</template>

View file

@ -1,66 +1,32 @@
<script setup lang="ts">
const pages = [
{
id: 'dashboard',
name: 'Dashboard',
component: '@components/content/Dashboard.vue',
},
{
id: 'show',
name: 'Trasmissioni',
component: '@components/content/Show.vue',
},
{
id: 'archive',
name: 'Archivio',
component: '@components/content/Archive.vue',
},
{
id: 'playlist',
name: 'Playlist',
component: '@components/content/Playlist.vue',
},
{
id: 'blocks',
name: 'Blocchi dinamici',
component: '@components/content/SmartBlock.vue',
},
{
id: 'podcast',
name: 'Podcast',
component: '@components/content/Podcast.vue',
},
{
id: 'webstream',
name: 'Webstream',
component: '@components/content/Webstream.vue',
},
{
id: 'spot',
name: 'Spot',
component: '@components/content/Show.vue',
},
{
id: 'spot-playlist',
name: 'Spot playlist',
component: '@components/content/Playlist.vue',
},
{
id: 'spot-blocks',
name: 'Spot Blocchi dinamici',
component: '@components/content/SmartBlock.vue',
},
];
import { useRouter } from 'vue-router';
import {useAuthStore} from "@stores/auth.store.ts";
const auth = useAuthStore();
const router = useRouter();
const backofficeRoutes = router.options.routes.find(route => route.name === 'Backoffice')?.children || [];
const pages = backofficeRoutes.filter(route => {
if (route.name === 'UserProfile') {
return false;
}
if (auth.currentUser.role !== 'admin' && route.name === 'adminDashboard') {
return false;
}
if (!['admin', 'editor'].includes(auth.currentUser.role) && route?.meta?.showType === 'spots') {
return false;
}
return true;
});
</script>
<template>
<v-col>
<v-btn
v-for="page in pages"
:key="page.id"
:key="page.path"
:to="{ name: page.name }"
size="large"
@click="$emit('showPage', page)"
exact
>{{ page.name }}</v-btn>
</v-col>
</template>

View file

@ -1,31 +1,12 @@
<script setup lang="ts">
import {useAuthStore} from "@/stores/auth.store.ts";
import Header from "@/layouts/partials/Header.vue";
import Sidebar from "@/layouts/partials/Sidebar.vue";
import Content from "@/layouts/partials/Content.vue";
import {reactive} from "vue";
import { useRouter } from "vue-router";
const page = reactive({
id: 'dashboard',
name: 'Dashboard',
})
let returnToPage = {id: '', name: ''}
const router = useRouter();
const userProfilePage = () => {
if(page.id == 'userProfile') {
page.id = returnToPage.id
page.name = returnToPage.name
return
}
returnToPage = {...page}
page.id = 'userProfile'
page.name = 'userProfile'
}
const changePage = (currentPage) => {
page.id = currentPage.id;
page.name = currentPage.name;
router.push({ name: 'UserProfile' });
}
</script>
@ -33,17 +14,17 @@ const changePage = (currentPage) => {
<div>
<Header @user-profile-page="userProfilePage" />
<v-row :fluid="true">
<Sidebar
@show-page="changePage"
/>
<Content
:page="page"
/>
<Sidebar />
<router-view class="routed-component"/>
</v-row>
</div>
</template>
<style scoped>
div {
flex-wrap: nowrap;
}
.v-row {
margin: 0;
}

View file

@ -1,12 +1,9 @@
<script setup lang="ts">
import {onBeforeMount, reactive} from 'vue';
import axios from "axios";
import {useRouter} from "vue-router";
import {Settings} from "luxon";
import {onBeforeMount, reactive} from 'vue';
import {useAuthStore} from "@/stores/auth.store.ts";
axios.defaults.withCredentials = true
const data = reactive({
'username': null,
'password': null,
@ -17,56 +14,29 @@ const auth = useAuthStore();
const router = useRouter();
const onSubmit = async () => {
let loginSuccessful = false
if (!data.username || !data.password) return;
data.loading = true;
await axios.get('/sanctum/csrf-cookie').then(() => {
axios.post('/login', {
username: data.username,
password: data.password,
}).then(async (response) => {
let timezone = await getTimezone();
if(response.data.timezone) {
timezone = response.data.timezone
} else {
response.data.timezone = timezone
}
auth.loadUser(response.data);
Settings.defaultZone = timezone;
try {
await router.push('/');
} catch (e) {
console.error(e)
}
}).catch((error) => {
data.errors = error.response.data.errors
const response = await auth.login(data.username, data.password);
if (response?.error) {
data.errors = response?.error
data.loading = false;
});
})
}
return
}
const getTimezone = (): Promise<string> => {
return axios.get("timezone").then((res) => {
return res.data;
}).catch(error => {
return null;
})
data.loading = false;
await router.push('/');
}
const required = (v) => {
return !!v || $t('login.errors.required');
}
onBeforeMount(() => {
// TODO Create a route taht checks if the user is already logged in (laravel session cookie), if it's logged in
// route.push /
// In the BE make something like route.get('userIsAuthenticated')->return auth()->user()
onBeforeMount(()=>{
if(auth.isAuthenticated){
router.push('/')
}
})
</script>
<template>

View file

@ -1,7 +1,8 @@
import {defineStore} from 'pinia'
import type {User} from "@models/User.ts";
import axios, {type AxiosResponse} from "axios";
import {camelToSnake} from "@/helpers/AxiosHelper.ts";
import {camelToSnake, snakeToCamel} from "@/helpers/AxiosHelper.ts";
import {Settings} from "luxon";
export const baseUser = (): User => ({
@ -15,7 +16,12 @@ export const baseUser = (): User => ({
export const useAuthStore = defineStore('user', {
state: () => ({
userData: {} as User,
isAuthenticated: false
}),
getters: {
isLoggedIn: (state): boolean => state.isAuthenticated,
currentUser: (state): User | null => state.userData,
},
actions: {
loadUser(userData: User) {
this.userData = {...userData};
@ -37,7 +43,54 @@ export const useAuthStore = defineStore('user', {
resetUser() {
this.userData = {...baseUser()};
},
async fetchUser() {
try {
const { data } = await axios.get('/user/profile');
this.userData = snakeToCamel(data);
this.isAuthenticated = true;
} catch (error) {
this.userData = null;
this.isAuthenticated = false;
console.error("Not authenticated or failed to fetch user.", error);
}
},
async login(username, password){
await axios.get('/sanctum/csrf-cookie');
await axios.post('/login', {
username: username,
password: password,
}).then(async (response) => {
let timezone = await axios.get("timezone").then((res) => {
return res.data;
}).catch(error => {
return null;
})
if(response.data.timezone) {
timezone = response.data.timezone
} else {
response.data.timezone = timezone
}
const userData = snakeToCamel(response.data)
this.loadUser(userData);
Settings.defaultZone = timezone;
}).catch((error) => {
return {
"errors": error.response.data.errors,
"loading": false,
}
});
},
async logout(){
try {
await axios.get('/logout');
} catch (error) {
console.error("Error during logout, but clearing state anyway.", error)
} finally {
this.resetUser = null;
this.isAuthenticated = false;
}
},
/**
* Updates the current user on the server.
* @returns {Promise<any>} A promise that resolves with the response data from the server.
@ -55,8 +108,6 @@ export const useAuthStore = defineStore('user', {
return response.data;
}).catch((error: Error) => {
console.error("Error: " + error.message);
});
},
}

View file

@ -15,6 +15,7 @@ use App\Http\Controllers\MusicBrainzController;
use App\Http\Controllers\UserController;
use App\Http\Controllers\WebstreamController;
use Illuminate\Support\Facades\Route;
use Spatie\Permission\Models\Role;
/*
|--------------------------------------------------------------------------
@ -30,6 +31,20 @@ use Illuminate\Support\Facades\Route;
Route::middleware('auth')->get('/user/profile', [UserController::class, 'userProfile']);
/**
* Misc
*/
Route::middleware('auth')->get('/timezoneList',function() {
return response()->json(DateTimeZone::listIdentifiers(DateTimeZone::ALL));
});
Route::middleware('auth')->get('/roleList',function() {
if(!auth()->user()->hasRole('admin')){
return response()->json(['message' => 'Unauthorized.'], 403);
}
$roles = Role::all()->pluck('name');
return response()->json($roles);
});
/**
* Create routes without create method
*/
@ -79,6 +94,8 @@ Route::get('/rss_podcast_episodes', [PodcastController::class, 'getEpisodes']);
Route::post('/musicbrainz/get_track_metadata', [MusicBrainzController::class, 'get_track_metadata'])->name('musicbrainz.get_track');
Route::get('musicbrainz', [MusicBrainzController::class, 'test']);
Route::get('testCelery', [PodcastEpisodeController::class, 'testCelery']);
require __DIR__.'/auth.php';
/**