standardization of policies/permissions
Some checks failed
linter / quality (push) Failing after 1m3s
tests / ci (8.3) (push) Failing after 48s
tests / ci (8.4) (push) Failing after 1m5s
tests / ci (8.5) (push) Failing after 1m4s

This commit is contained in:
Daan Meijer 2026-06-22 16:13:32 +02:00
parent c60033b365
commit 188c4435cb
11 changed files with 166 additions and 162 deletions

View File

@ -13,6 +13,13 @@ class MutationResource extends BaseResource
*/
public function toArray(Request $request): array
{
return parent::toArray($request);
$data = parent::toArray($request);
$data['can'] = [
'update' => $request->user()?->can('update', $this->resource) ?? false,
'void' => $request->user()?->can('void', $this->resource) ?? false,
];
return $data;
}
}

View File

@ -24,8 +24,9 @@ class MutationPolicy
public function update(User $user, Mutation $mutation): bool
{
$dynamic = $mutation->ledger->dynamic;
$isOwner = $dynamic->participants()->where('user_id', $user->id)->where('role', 'owner')->exists();
return $dynamic->participants()->where('user_id', $user->id)->where('role', 'owner')->exists();
return $isOwner && $mutation->status === 'pending';
}
/**
@ -34,7 +35,8 @@ class MutationPolicy
public function void(User $user, Mutation $mutation): bool
{
$dynamic = $mutation->ledger->dynamic;
$isOwner = $dynamic->participants()->where('user_id', $user->id)->where('role', 'owner')->exists();
return $dynamic->participants()->where('user_id', $user->id)->where('role', 'owner')->exists();
return $isOwner && $mutation->status !== 'voided';
}
}

View File

@ -17,13 +17,16 @@ const props = defineProps<{
created_at: string;
chat: any;
media?: Array<{ id: number; url: string; mime_type: string }>;
can: {
update: boolean;
void: boolean;
};
}>;
participants?: Array<{
id: number;
name: string;
pivot?: { role: string };
}>;
isOwner: boolean;
}>();
const emit = defineEmits<{
@ -167,25 +170,25 @@ function getAmountClass(amount: number): string {
<!-- Owner Approve/Reject Actions -->
<div
v-if="isOwner && (mutation.status === 'pending' || mutation.status === 'approved')"
v-if="mutation.can.update || mutation.can.void"
class="c-mutation-list__actions"
>
<button
v-if="mutation.status === 'pending'"
v-if="mutation.can.update"
@click="updateStatus(mutation.id, 'approved')"
class="c-mutation-list__approve-btn"
>
Approve
</button>
<button
v-if="mutation.status === 'pending'"
v-if="mutation.can.update"
@click="updateStatus(mutation.id, 'rejected')"
class="c-mutation-list__reject-btn"
>
Reject
</button>
<button
v-if="mutation.status !== 'voided'"
v-if="mutation.can.void"
@click="voidMutation(mutation.id)"
class="c-mutation-list__void-btn"
>

View File

@ -2,22 +2,26 @@
import { Head, useForm } from '@inertiajs/vue3';
import { route } from 'ziggy-js';
defineOptions({
layout: {
breadcrumbs: [
{
title: 'Dynamics',
href: route('dynamics.index'),
},
{
title: 'Create',
href: route('dynamics.create'),
},
],
},
});
const form = useForm({
name: '',
rules: '',
});
const breadcrumbs = [
{
name: 'Dynamics',
href: route('dynamics.index'),
},
{
name: 'Create',
href: route('dynamics.create'),
},
];
function submit() {
form.post(route('dynamics.store'));
}

View File

@ -2,6 +2,17 @@
import { Head, Link } from '@inertiajs/vue3';
import { route } from 'ziggy-js';
defineOptions({
layout: {
breadcrumbs: [
{
title: 'Dynamics',
href: route('dynamics.index'),
},
],
},
});
defineProps<{
dynamics: Array<{
id: string;
@ -9,13 +20,6 @@ defineProps<{
rules: string;
}>;
}>();
const breadcrumbs = [
{
name: 'Dynamics',
href: route('dynamics.index'),
},
];
</script>
<template>

View File

@ -1,7 +1,25 @@
<script setup lang="ts">
import { Head, useForm } from '@inertiajs/vue3';
import { route } from 'ziggy-js';
import AppLayout from '@/layouts/AppLayout.vue';
defineOptions({
layout: (props: any) => ({
breadcrumbs: [
{
title: 'Dynamics',
href: route('dynamics.index'),
},
{
title: props.dynamic.name,
href: route('dynamics.show', props.dynamic.id),
},
{
title: 'Invite User',
href: route('dynamics.invitations.create', props.dynamic.id),
},
],
}),
});
const props = defineProps<{
dynamic: {
@ -15,21 +33,6 @@ const form = useForm({
role: 'participant',
});
const breadcrumbs = [
{
name: 'Dynamics',
href: route('dynamics.index'),
},
{
name: props.dynamic.name,
href: route('dynamics.show', props.dynamic.id),
},
{
name: 'Invite User',
href: route('dynamics.invitations.create', props.dynamic.id),
},
];
function submit() {
form.post(route('dynamics.invitations.store', props.dynamic.id), {
onSuccess: () => form.reset(),

View File

@ -1,7 +1,25 @@
<script setup lang="ts">
import { Head, useForm, Link as InertiaLink } from '@inertiajs/vue3';
import { route } from 'ziggy-js';
import AppLayout from '@/layouts/AppLayout.vue';
defineOptions({
layout: (props: any) => ({
breadcrumbs: [
{
title: 'Dynamics',
href: route('dynamics.index'),
},
{
title: props.dynamic.name,
href: route('dynamics.show', props.dynamic.id),
},
{
title: 'Settings',
href: route('dynamics.edit', props.dynamic.id),
},
],
}),
});
const props = defineProps<{
dynamic: {
@ -16,21 +34,6 @@ const form = useForm({
rules: props.dynamic.rules,
});
const breadcrumbs = [
{
name: 'Dynamics',
href: route('dynamics.index'),
},
{
name: props.dynamic.name,
href: route('dynamics.show', props.dynamic.id),
},
{
name: 'Settings',
href: route('dynamics.edit', props.dynamic.id),
},
];
function submit() {
form.patch(route('dynamics.update', props.dynamic.id));
}

View File

@ -43,18 +43,6 @@ const props = defineProps<{
update: boolean;
}
}>();
const breadcrumbs = [
{
name: 'Dynamics',
href: route('dynamics.index'),
},
{
name: props.dynamic.name,
href: route('dynamics.show', props.dynamic.id),
},
];
</script>
<template>

View File

@ -1,30 +1,33 @@
<script setup lang="ts">
import { Head, useForm } from '@inertiajs/vue3';
import { route } from 'ziggy-js';
import AppLayout from '@/layouts/AppLayout.vue';
import CreateLedgerForm from '@/components/CreateLedgerForm.vue';
defineOptions({
layout: (props: any) => ({
breadcrumbs: [
{
title: 'Dynamics',
href: route('dynamics.index'),
},
{
title: props.dynamic.name,
href: route('dynamics.show', props.dynamic.id),
},
{
title: 'Create Ledger',
href: route('dynamics.ledgers.create', props.dynamic.id),
},
],
}),
});
const props = defineProps<{
dynamic: {
id: number;
name: string;
};
}>();
const breadcrumbs = [
{
name: 'Dynamics',
href: route('dynamics.index'),
},
{
name: props.dynamic.name,
href: route('dynamics.show', props.dynamic.id),
},
{
name: 'Create Ledger',
href: route('dynamics.ledgers.create', props.dynamic.id),
},
];
</script>
<template>

View File

@ -1,7 +1,29 @@
<script setup lang="ts">
import { Head, useForm } from '@inertiajs/vue3';
import { Head, useForm, Link } from '@inertiajs/vue3';
import { route } from 'ziggy-js';
import AppLayout from '@/layouts/AppLayout.vue';
defineOptions({
layout: (props: any) => ({
breadcrumbs: [
{
title: 'Dynamics',
href: route('dynamics.index'),
},
{
title: props.dynamic.name,
href: route('dynamics.show', props.dynamic.id),
},
{
title: props.ledger.name,
href: route('dynamics.ledgers.show', [props.dynamic.id, props.ledger.id]),
},
{
title: 'Edit',
href: route('dynamics.ledgers.edit', [props.dynamic.id, props.ledger.id]),
},
],
}),
});
const props = defineProps<{
dynamic: {
@ -20,25 +42,6 @@ const form = useForm({
rules: props.ledger.rules,
});
const breadcrumbs = [
{
name: 'Dynamics',
href: route('dynamics.index'),
},
{
name: props.dynamic.name,
href: route('dynamics.show', props.dynamic.id),
},
{
name: props.ledger.name,
href: route('dynamics.ledgers.show', [props.dynamic.id, props.ledger.id]),
},
{
name: 'Edit',
href: route('dynamics.ledgers.edit', [props.dynamic.id, props.ledger.id]),
},
];
function submit() {
form.put(route('dynamics.ledgers.update', [props.dynamic.id, props.ledger.id]));
}
@ -47,7 +50,6 @@ function submit() {
<template>
<Head title="Edit Ledger" />
<AppLayout :breadcrumbs="breadcrumbs">
<div class="c-ledger-edit">
<div class="c-ledger-edit__container">
<div class="c-ledger-edit__card">
@ -85,7 +87,6 @@ function submit() {
</div>
</div>
</div>
</AppLayout>
</template>
<style scoped>

View File

@ -43,6 +43,7 @@ const props = defineProps<{
score: number;
rules: string;
alignment: string;
status: string;
media?: Array<{ id: number; url: string; mime_type: string }>;
mutations: Array<{
id: number;
@ -56,30 +57,15 @@ const props = defineProps<{
media?: Array<{ id: number; url: string; mime_type: string }>;
}>;
};
isOwner: boolean;
can: {
update: boolean;
close: boolean;
};
messages: {
data: Array<any>;
next_page_url: string | null;
};
}>();
const breadcrumbs = [
{
name: 'Dynamics',
href: route('dynamics.index'),
},
{
name: props.dynamic.name,
href: route('dynamics.show', props.dynamic.id),
},
{
name: props.ledger.name,
href: route('dynamics.ledgers.show', {
dynamic: props.dynamic.id,
ledger: props.ledger.id,
}),
},
];
}>();
// Lightbox Modal state
const activeLightboxUrl = ref<string | null>(null);
@ -202,7 +188,7 @@ function isOwnerUser(userId: number): boolean {
{{ ledger.rules }}
</p>
</div>
<div v-if="isOwner" class="flex flex-col gap-2">
<div v-if="can.update" class="flex flex-col gap-2">
<InertiaLink
:href="route('dynamics.ledgers.predefined-mutations.index', [dynamic.id, ledger.id])"
class="c-ledger-show__manage-btn"
@ -284,7 +270,7 @@ function isOwnerUser(userId: number): boolean {
:ledger-id="ledger.id"
:mutations="ledger.mutations"
:participants="dynamic.participants"
:is-owner="isOwner"
:can-update="can.update"
:ledger-alignment="ledger.alignment"
@open-lightbox="openLightbox"
/>