Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
61 / 61
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
AdminRolePlayTemplateController
100.00% covered (success)
100.00%
61 / 61
100.00% covered (success)
100.00%
7 / 7
14
100.00% covered (success)
100.00%
1 / 1
 index
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 store
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
 show
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 update
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
2
 duplicate
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
2
 destroy
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 findOrFail
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3namespace App\Http\Controllers\v2\Admin;
4
5use App\Http\Controllers\Controller;
6use App\Http\Models\RolePlayPersonaTemplate;
7use App\Http\Resources\v2\RolePlayPersonaTemplateResource;
8use Illuminate\Http\JsonResponse;
9use Illuminate\Http\Request;
10
11/**
12 * Admin-scoped CRUD for company-visibility RolePlay persona templates.
13 *
14 * This controller is intentionally narrower than the user-facing one at
15 * {@see \App\Http\Controllers\v2\RolePlay\RolePlayPersonaTemplatesController}:
16 * it only manages `visibility = company` templates, forces that flag on
17 * store, and rejects any attempt to touch a personal template (IDOR guard
18 * across companies).
19 */
20class AdminRolePlayTemplateController extends Controller
21{
22    /**
23     * List company-visibility templates for the admin's company.
24     */
25    public function index(Request $request)
26    {
27        $templates = RolePlayPersonaTemplate::query()
28            ->where('visibility', RolePlayPersonaTemplate::VISIBILITY_COMPANY)
29            ->where('company_id', $request->user()->company_id)
30            ->orderBy('created_at', 'desc')
31            ->get();
32
33        return RolePlayPersonaTemplateResource::collection($templates);
34    }
35
36    /**
37     * Store a new company-visibility template. Visibility is forced to
38     * "company" regardless of the payload to prevent accidental creation
39     * of a personal template through this admin route.
40     */
41    public function store(Request $request): JsonResponse
42    {
43        $rules = RolePlayPersonaTemplate::getRules();
44        // Admin endpoint only stores company templates — relax the visibility
45        // requirement on the payload so the client can omit it.
46        $rules['visibility'] = 'sometimes|string';
47        $validated = $request->validate($rules);
48
49        $validated['visibility'] = RolePlayPersonaTemplate::VISIBILITY_COMPANY;
50        $validated['company_id'] = $request->user()->company_id;
51        $validated['user_id'] = $request->user()->id;
52
53        $template = RolePlayPersonaTemplate::create($validated);
54
55        return response()->json([
56            'status' => 'success',
57            'data' => new RolePlayPersonaTemplateResource($template),
58        ], 201);
59    }
60
61    /**
62     * Show a single template in the admin's company.
63     */
64    public function show(Request $request, string $id): JsonResponse
65    {
66        $template = $this->findOrFail($request, $id);
67        if ($template instanceof JsonResponse) {
68            return $template;
69        }
70
71        return response()->json([
72            'status' => 'success',
73            'data' => new RolePlayPersonaTemplateResource($template),
74        ]);
75    }
76
77    /**
78     * Update an existing company-visibility template.
79     */
80    public function update(Request $request, string $id): JsonResponse
81    {
82        $template = $this->findOrFail($request, $id);
83        if ($template instanceof JsonResponse) {
84            return $template;
85        }
86
87        $rules = RolePlayPersonaTemplate::getRules();
88        $rules['visibility'] = 'sometimes|string';
89        $validated = $request->validate($rules);
90        // Never downgrade to personal.
91        $validated['visibility'] = RolePlayPersonaTemplate::VISIBILITY_COMPANY;
92
93        $template->update($validated);
94
95        return response()->json([
96            'status' => 'success',
97            'data' => new RolePlayPersonaTemplateResource($template->fresh()),
98        ]);
99    }
100
101    /**
102     * Duplicate an existing company-visibility template.
103     *
104     * Copies every field of the source template EXCEPT identity/timestamps,
105     * forces `visibility = company`, stamps `user_id` and `company_id` from
106     * the caller, and appends " (Copy)" to the name. Only company-visibility
107     * templates in the caller's company can be duplicated — anything else
108     * returns 404 to avoid leaking existence of other templates.
109     *
110     * @response 201 {"result": {"status": "success", "data": {"id": "...", "name": "... (Copy)"}}}
111     * @response 404 {"result": {"status": "error", "message": "Template not found"}}
112     */
113    public function duplicate(Request $request, string $id): JsonResponse
114    {
115        $source = $this->findOrFail($request, $id);
116        if ($source instanceof JsonResponse) {
117            return $source;
118        }
119
120        $excluded = ['_id', 'id', 'created_at', 'updated_at', 'name'];
121        $data = array_diff_key($source->getAttributes(), array_flip($excluded));
122
123        $data['name'] = trim((string) $source->name).' (Copy)';
124        $data['visibility'] = RolePlayPersonaTemplate::VISIBILITY_COMPANY;
125        $data['user_id'] = $request->user()->id;
126        $data['company_id'] = $request->user()->company_id;
127
128        $clone = RolePlayPersonaTemplate::create($data);
129
130        return response()->json([
131            'status' => 'success',
132            'data' => new RolePlayPersonaTemplateResource($clone),
133        ], 201);
134    }
135
136    /**
137     * Delete a company-visibility template.
138     */
139    public function destroy(Request $request, string $id): JsonResponse
140    {
141        $template = $this->findOrFail($request, $id);
142        if ($template instanceof JsonResponse) {
143            return $template;
144        }
145
146        $template->delete();
147
148        return response()->json(['status' => 'success']);
149    }
150
151    /**
152     * @return RolePlayPersonaTemplate|JsonResponse
153     */
154    private function findOrFail(Request $request, string $id)
155    {
156        $template = RolePlayPersonaTemplate::find($id);
157
158        if (! $template
159            || $template->visibility !== RolePlayPersonaTemplate::VISIBILITY_COMPANY
160            || (string) $template->company_id !== (string) $request->user()->company_id
161        ) {
162            return response()->json(['status' => 'error', 'message' => 'Template not found'], 404);
163        }
164
165        return $template;
166    }
167}