base-api-scramble-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +140 -0
- package/docs/AI-GUIDE-COMPLETE.md +372 -0
- package/docs/EXCEL-EXPORT-SERVICE.md +349 -0
- package/docs/EXPORT-DATA-PROVIDERS.md +340 -0
- package/docs/EXPORT-TEMPLATES.md +459 -0
- package/docs/LARAVEL-API-TEST.md +355 -0
- package/docs/MAKE-EXPORT-EXCEL-COMMAND.md +215 -0
- package/docs/README.md +678 -0
- package/docs/STRUCTURE.md +540 -0
- package/docs/starting_point.md +423 -0
- package/index.js +968 -0
- package/package.json +17 -0
package/index.js
ADDED
|
@@ -0,0 +1,968 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP Server — Base API Scramble
|
|
5
|
+
* Laravel API Knowledge Base untuk AI (Claude, Cursor, Copilot, dll)
|
|
6
|
+
*
|
|
7
|
+
* Tools:
|
|
8
|
+
* - get_starting_point → Kickoff guide, quick start, stack, konvensi wajib
|
|
9
|
+
* - get_architecture → Arsitektur lengkap, flow request, pattern CRUD
|
|
10
|
+
* - get_structure → Struktur direktori project
|
|
11
|
+
* - search_docs → Full-text search di semua docs
|
|
12
|
+
* - get_doc → Baca 1 doc file lengkap
|
|
13
|
+
* - list_docs → Daftar semua docs yang tersedia
|
|
14
|
+
* - get_conventions → Konvensi & aturan wajib (model, migration, controller, dll)
|
|
15
|
+
* - get_commands → Referensi artisan gc commands
|
|
16
|
+
* - get_api_response_format → Format response API standar
|
|
17
|
+
* - get_query_params → Query parameter yang tersedia
|
|
18
|
+
* - get_file_service → Cara pakai FileService (upload/delete file)
|
|
19
|
+
* - get_export_guide → Excel export system
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
23
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
24
|
+
import { z } from "zod";
|
|
25
|
+
import { readFileSync, existsSync, readdirSync } from "fs";
|
|
26
|
+
import { join, dirname } from "path";
|
|
27
|
+
import { fileURLToPath } from "url";
|
|
28
|
+
|
|
29
|
+
// ─────────────────────────────────────────────
|
|
30
|
+
// Resolve docs path
|
|
31
|
+
// ─────────────────────────────────────────────
|
|
32
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
33
|
+
const DOCS_PATH = join(__dirname, "docs");
|
|
34
|
+
|
|
35
|
+
// ─────────────────────────────────────────────
|
|
36
|
+
// Helper: baca file doc
|
|
37
|
+
// ─────────────────────────────────────────────
|
|
38
|
+
function readDoc(filename) {
|
|
39
|
+
const filePath = join(DOCS_PATH, filename);
|
|
40
|
+
if (!existsSync(filePath)) {
|
|
41
|
+
return `❌ File tidak ditemukan: ${filename}`;
|
|
42
|
+
}
|
|
43
|
+
return readFileSync(filePath, "utf-8");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ─────────────────────────────────────────────
|
|
47
|
+
// Helper: list semua doc files
|
|
48
|
+
// ─────────────────────────────────────────────
|
|
49
|
+
function listDocFiles() {
|
|
50
|
+
if (!existsSync(DOCS_PATH)) return [];
|
|
51
|
+
return readdirSync(DOCS_PATH).filter((f) => f.endsWith(".md"));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ─────────────────────────────────────────────
|
|
55
|
+
// Helper: search di semua docs
|
|
56
|
+
// ─────────────────────────────────────────────
|
|
57
|
+
function searchDocs(query) {
|
|
58
|
+
const files = listDocFiles();
|
|
59
|
+
const queryLower = query.toLowerCase();
|
|
60
|
+
const results = [];
|
|
61
|
+
|
|
62
|
+
for (const file of files) {
|
|
63
|
+
const content = readDoc(file);
|
|
64
|
+
const lines = content.split("\n");
|
|
65
|
+
const matches = [];
|
|
66
|
+
|
|
67
|
+
lines.forEach((line, idx) => {
|
|
68
|
+
if (line.toLowerCase().includes(queryLower)) {
|
|
69
|
+
// ambil context 1 baris sebelum & sesudahnya
|
|
70
|
+
const from = Math.max(0, idx - 1);
|
|
71
|
+
const to = Math.min(lines.length - 1, idx + 1);
|
|
72
|
+
matches.push({
|
|
73
|
+
line: idx + 1,
|
|
74
|
+
context: lines.slice(from, to + 1).join("\n"),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (matches.length > 0) {
|
|
80
|
+
results.push({
|
|
81
|
+
file,
|
|
82
|
+
match_count: matches.length,
|
|
83
|
+
matches: matches.slice(0, 5), // max 5 matches per file
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return results;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ─────────────────────────────────────────────
|
|
92
|
+
// Buat MCP Server
|
|
93
|
+
// ─────────────────────────────────────────────
|
|
94
|
+
const server = new McpServer({
|
|
95
|
+
name: "base-api-scramble",
|
|
96
|
+
version: "1.0.0",
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// ══════════════════════════════════════════════
|
|
100
|
+
// TOOL: get_starting_point
|
|
101
|
+
// ══════════════════════════════════════════════
|
|
102
|
+
server.tool(
|
|
103
|
+
"get_starting_point",
|
|
104
|
+
"Dapatkan kickoff guide lengkap: quick start, tech stack, arsitektur, konvensi wajib, dan langkah memulai project baru dari base-api-scramble. GUNAKAN INI PERTAMA KALI saat mulai kerja di project ini.",
|
|
105
|
+
{},
|
|
106
|
+
async () => {
|
|
107
|
+
const content = readDoc("starting_point.md");
|
|
108
|
+
return {
|
|
109
|
+
content: [
|
|
110
|
+
{
|
|
111
|
+
type: "text",
|
|
112
|
+
text: content,
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// ══════════════════════════════════════════════
|
|
120
|
+
// TOOL: get_architecture
|
|
121
|
+
// ══════════════════════════════════════════════
|
|
122
|
+
server.tool(
|
|
123
|
+
"get_architecture",
|
|
124
|
+
"Dapatkan panduan arsitektur lengkap: flow request, 5 core components, dua workflow (Migration-First & Fields-First), pattern Model/Query/Controller/FormRequest, dan aturan AI. Ideal untuk memahami cara membuat fitur baru.",
|
|
125
|
+
{},
|
|
126
|
+
async () => {
|
|
127
|
+
const content = readDoc("AI-GUIDE-COMPLETE.md");
|
|
128
|
+
return {
|
|
129
|
+
content: [
|
|
130
|
+
{
|
|
131
|
+
type: "text",
|
|
132
|
+
text: content,
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// ══════════════════════════════════════════════
|
|
140
|
+
// TOOL: get_structure
|
|
141
|
+
// ══════════════════════════════════════════════
|
|
142
|
+
server.tool(
|
|
143
|
+
"get_structure",
|
|
144
|
+
"Dapatkan struktur direktori lengkap project base-api-scramble: semua folder, file penting, dan penjelasannya.",
|
|
145
|
+
{},
|
|
146
|
+
async () => {
|
|
147
|
+
const content = readDoc("STRUCTURE.md");
|
|
148
|
+
return {
|
|
149
|
+
content: [
|
|
150
|
+
{
|
|
151
|
+
type: "text",
|
|
152
|
+
text: content,
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// ══════════════════════════════════════════════
|
|
160
|
+
// TOOL: list_docs
|
|
161
|
+
// ══════════════════════════════════════════════
|
|
162
|
+
server.tool(
|
|
163
|
+
"list_docs",
|
|
164
|
+
"Tampilkan daftar semua file dokumentasi yang tersedia di knowledge base ini.",
|
|
165
|
+
{},
|
|
166
|
+
async () => {
|
|
167
|
+
const files = listDocFiles();
|
|
168
|
+
|
|
169
|
+
const descriptions = {
|
|
170
|
+
"starting_point.md":
|
|
171
|
+
"Kickoff guide: quick start, tech stack, konvensi, langkah project baru",
|
|
172
|
+
"AI-GUIDE-COMPLETE.md":
|
|
173
|
+
"Panduan AI lengkap: arsitektur, pattern code, commands, aturan AI",
|
|
174
|
+
"STRUCTURE.md": "Struktur direktori lengkap seluruh project",
|
|
175
|
+
"README.md": "Overview project dengan contoh workflow dan fitur",
|
|
176
|
+
"EXCEL-EXPORT-SERVICE.md": "Dokumentasi Excel export service (PhpSpreadsheet)",
|
|
177
|
+
"EXPORT-DATA-PROVIDERS.md": "Pattern Data Provider untuk export Excel",
|
|
178
|
+
"EXPORT-TEMPLATES.md": "Template Excel: multi-sheet, multi-table, styling",
|
|
179
|
+
"LARAVEL-API-TEST.md": "Panduan testing Laravel API",
|
|
180
|
+
"MAKE-EXPORT-EXCEL-COMMAND.md": "Command generator untuk Excel export",
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const list = files
|
|
184
|
+
.map((f) => {
|
|
185
|
+
const desc = descriptions[f] || "Dokumentasi tambahan";
|
|
186
|
+
return `• **${f}** — ${desc}`;
|
|
187
|
+
})
|
|
188
|
+
.join("\n");
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
content: [
|
|
192
|
+
{
|
|
193
|
+
type: "text",
|
|
194
|
+
text: `# Daftar Dokumentasi Base API Scramble\n\nTotal: ${files.length} files\n\n${list}\n\n---\nGunakan tool \`get_doc\` dengan nama file untuk membaca isi lengkapnya.`,
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// ══════════════════════════════════════════════
|
|
202
|
+
// TOOL: get_doc
|
|
203
|
+
// ══════════════════════════════════════════════
|
|
204
|
+
server.tool(
|
|
205
|
+
"get_doc",
|
|
206
|
+
"Baca isi lengkap dari satu file dokumentasi. Gunakan list_docs terlebih dahulu untuk melihat file yang tersedia.",
|
|
207
|
+
{
|
|
208
|
+
filename: z
|
|
209
|
+
.string()
|
|
210
|
+
.describe(
|
|
211
|
+
'Nama file doc yang ingin dibaca. Contoh: "EXCEL-EXPORT-SERVICE.md"'
|
|
212
|
+
),
|
|
213
|
+
},
|
|
214
|
+
async ({ filename }) => {
|
|
215
|
+
const content = readDoc(filename);
|
|
216
|
+
return {
|
|
217
|
+
content: [
|
|
218
|
+
{
|
|
219
|
+
type: "text",
|
|
220
|
+
text: content,
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
// ══════════════════════════════════════════════
|
|
228
|
+
// TOOL: search_docs
|
|
229
|
+
// ══════════════════════════════════════════════
|
|
230
|
+
server.tool(
|
|
231
|
+
"search_docs",
|
|
232
|
+
"Cari keyword/topik di semua file dokumentasi. Berguna untuk menemukan informasi spesifik tentang fitur, class, atau konvensi tertentu.",
|
|
233
|
+
{
|
|
234
|
+
query: z
|
|
235
|
+
.string()
|
|
236
|
+
.describe(
|
|
237
|
+
'Keyword yang dicari. Contoh: "FileService", "baseQuery", "uuid", "gc:from-migration"'
|
|
238
|
+
),
|
|
239
|
+
},
|
|
240
|
+
async ({ query }) => {
|
|
241
|
+
const results = searchDocs(query);
|
|
242
|
+
|
|
243
|
+
if (results.length === 0) {
|
|
244
|
+
return {
|
|
245
|
+
content: [
|
|
246
|
+
{
|
|
247
|
+
type: "text",
|
|
248
|
+
text: `❌ Tidak ditemukan hasil untuk: "${query}"\n\nCoba keyword lain atau gunakan \`list_docs\` untuk melihat topik yang tersedia.`,
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const totalMatches = results.reduce((acc, r) => acc + r.match_count, 0);
|
|
255
|
+
let output = `# Hasil Pencarian: "${query}"\n\nDitemukan **${totalMatches} baris** di **${results.length} file**\n\n---\n\n`;
|
|
256
|
+
|
|
257
|
+
for (const result of results) {
|
|
258
|
+
output += `## 📄 ${result.file} (${result.match_count} match)\n\n`;
|
|
259
|
+
for (const match of result.matches) {
|
|
260
|
+
output += `**Baris ${match.line}:**\n\`\`\`\n${match.context}\n\`\`\`\n\n`;
|
|
261
|
+
}
|
|
262
|
+
output += "---\n\n";
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
content: [
|
|
267
|
+
{
|
|
268
|
+
type: "text",
|
|
269
|
+
text: output,
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
// ══════════════════════════════════════════════
|
|
277
|
+
// TOOL: get_conventions
|
|
278
|
+
// ══════════════════════════════════════════════
|
|
279
|
+
server.tool(
|
|
280
|
+
"get_conventions",
|
|
281
|
+
"Dapatkan konvensi & aturan wajib project: cara buat Model (UUID + Queryable), Migration, Query Class, Controller, FormRequest, dan Route. Gunakan ini sebelum membuat kode baru.",
|
|
282
|
+
{},
|
|
283
|
+
async () => {
|
|
284
|
+
const text = `# Konvensi & Pattern Wajib — Base API Scramble
|
|
285
|
+
|
|
286
|
+
## ⚡ Flow Request
|
|
287
|
+
\`\`\`
|
|
288
|
+
Request → Route → FormRequest (validasi) → Controller → Query Class → Model → ApiResponse
|
|
289
|
+
\`\`\`
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## 1. Model
|
|
294
|
+
- **WAJIB** pakai \`HasUuids\` (UUID sebagai primary key, BUKAN auto-increment)
|
|
295
|
+
- **WAJIB** pakai \`Queryable\` trait
|
|
296
|
+
|
|
297
|
+
\`\`\`php
|
|
298
|
+
use App\\Traits\\Queryable;
|
|
299
|
+
use Illuminate\\Database\\Eloquent\\Concerns\\HasUuids;
|
|
300
|
+
|
|
301
|
+
class NamaModel extends Model
|
|
302
|
+
{
|
|
303
|
+
use HasFactory, HasUuids, Queryable;
|
|
304
|
+
|
|
305
|
+
protected $fillable = ['field1', 'field2'];
|
|
306
|
+
protected $searchable = ['field1']; // kolom untuk ?search=
|
|
307
|
+
protected $relationIncludes = ['relation1']; // relasi untuk ?include=
|
|
308
|
+
}
|
|
309
|
+
\`\`\`
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## 2. Migration
|
|
314
|
+
- **WAJIB** pakai UUID primary key
|
|
315
|
+
|
|
316
|
+
\`\`\`php
|
|
317
|
+
Schema::create('table_name', function (Blueprint $table) {
|
|
318
|
+
$table->uuid('id')->primary();
|
|
319
|
+
$table->string('name');
|
|
320
|
+
$table->foreignUuid('parent_id')->constrained('parents')->cascadeOnDelete();
|
|
321
|
+
$table->timestamps();
|
|
322
|
+
});
|
|
323
|
+
\`\`\`
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## 3. Query Class
|
|
328
|
+
- Lokasi: \`app/Queries/NamaModelQuery.php\`
|
|
329
|
+
- Extends \`QueryBuilder\` dari Spatie
|
|
330
|
+
|
|
331
|
+
\`\`\`php
|
|
332
|
+
class NamaModelQuery extends QueryBuilder
|
|
333
|
+
{
|
|
334
|
+
public function __construct()
|
|
335
|
+
{
|
|
336
|
+
parent::__construct(NamaModel::query());
|
|
337
|
+
$this
|
|
338
|
+
->allowedIncludes([AllowedInclude::relationship('relation1')])
|
|
339
|
+
->allowedFilters([
|
|
340
|
+
AllowedFilter::exact('id'),
|
|
341
|
+
AllowedFilter::partial('name'),
|
|
342
|
+
]);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
\`\`\`
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## 4. Controller
|
|
350
|
+
- Extends \`Controller\` (sudah include \`ApiResponse\` + \`baseQuery()\`)
|
|
351
|
+
- \`baseQuery()\` menerima **1 argumen** saja: instance Query class
|
|
352
|
+
|
|
353
|
+
\`\`\`php
|
|
354
|
+
class NamaModelController extends Controller
|
|
355
|
+
{
|
|
356
|
+
public function index(IndexRequest $request)
|
|
357
|
+
{
|
|
358
|
+
return $this->responseData(
|
|
359
|
+
$this->baseQuery(new NamaModelQuery()),
|
|
360
|
+
'NamaModel retrieved successfully'
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
public function show(NamaModel $namaModel)
|
|
365
|
+
{
|
|
366
|
+
return $this->responseSuccess($namaModel, 'NamaModel retrieved successfully');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
public function store(NamaModelStoreRequest $request)
|
|
370
|
+
{
|
|
371
|
+
$model = NamaModel::create($request->validated());
|
|
372
|
+
return $this->responseCreated($model, 'NamaModel created successfully');
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
public function update(NamaModelUpdateRequest $request, NamaModel $namaModel)
|
|
376
|
+
{
|
|
377
|
+
$namaModel->update($request->validated());
|
|
378
|
+
return $this->responseSuccess($namaModel, 'NamaModel updated successfully');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
public function destroy(NamaModel $namaModel)
|
|
382
|
+
{
|
|
383
|
+
$namaModel->delete();
|
|
384
|
+
return $this->responseDeleted('NamaModel deleted successfully');
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
\`\`\`
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## 5. FormRequest
|
|
392
|
+
- Gunakan \`@example\` PHPDoc agar Scramble auto-generate contoh di API docs
|
|
393
|
+
|
|
394
|
+
\`\`\`php
|
|
395
|
+
public function rules(): array
|
|
396
|
+
{
|
|
397
|
+
return [
|
|
398
|
+
/** @example John Doe */
|
|
399
|
+
'name' => 'required|string|max:255',
|
|
400
|
+
/** @example john@example.com */
|
|
401
|
+
'email' => 'required|email|unique:users,email',
|
|
402
|
+
];
|
|
403
|
+
}
|
|
404
|
+
\`\`\`
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## 6. Route
|
|
409
|
+
- Tambahkan di \`routes/api_v1.php\` di dalam group \`auth:sanctum\`
|
|
410
|
+
|
|
411
|
+
\`\`\`php
|
|
412
|
+
Route::apiResource('nama-model', Controllers\\NamaModelController::class);
|
|
413
|
+
\`\`\`
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## 7. Naming Convention
|
|
418
|
+
| Item | Convention | Contoh |
|
|
419
|
+
|---|---|---|
|
|
420
|
+
| Model | Singular PascalCase | \`ProductCategory\` |
|
|
421
|
+
| Table | Plural snake_case (auto) | \`product_categories\` |
|
|
422
|
+
| Controller | \`{Model}Controller\` | \`ProductCategoryController\` |
|
|
423
|
+
| Query | \`{Model}Query\` | \`ProductCategoryQuery\` |
|
|
424
|
+
| Request | \`{Model}StoreRequest\` | \`ProductCategoryStoreRequest\` |
|
|
425
|
+
| Route | Kebab plural | \`product-categories\` |
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
## ❌ JANGAN PERNAH
|
|
430
|
+
- Generate CRUD manual — **selalu gunakan \`gc\` command**
|
|
431
|
+
- Pakai \`id()->primary()\` — harus \`uuid('id')->primary()\`
|
|
432
|
+
- Pass lebih dari 1 argumen ke \`baseQuery()\`
|
|
433
|
+
- Buat migration tanpa UUID primary key
|
|
434
|
+
`;
|
|
435
|
+
|
|
436
|
+
return {
|
|
437
|
+
content: [{ type: "text", text }],
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
// ══════════════════════════════════════════════
|
|
443
|
+
// TOOL: get_commands
|
|
444
|
+
// ══════════════════════════════════════════════
|
|
445
|
+
server.tool(
|
|
446
|
+
"get_commands",
|
|
447
|
+
"Referensi lengkap artisan GC commands: gc, gc:from-migration, gc:wizard. Beserta contoh penggunaan dan field type mapping.",
|
|
448
|
+
{},
|
|
449
|
+
async () => {
|
|
450
|
+
const text = `# Artisan GC Commands — Referensi Lengkap
|
|
451
|
+
|
|
452
|
+
## Perintah Utama
|
|
453
|
+
|
|
454
|
+
\`\`\`bash
|
|
455
|
+
# ─── Fields-First ─────────────────────────────────────────────
|
|
456
|
+
php artisan gc Product --fields="name,price:decimal,stock:integer"
|
|
457
|
+
php artisan gc Product --fields="name,price:decimal,image:file" --belongsTo="Category,Brand"
|
|
458
|
+
php artisan gc Product --fields="name,price:decimal" --hasMany="Review"
|
|
459
|
+
|
|
460
|
+
# ─── Migration-First (Recommended untuk schema kompleks) ───────
|
|
461
|
+
php artisan gc:from-migration products # ← nama tabel (plural)
|
|
462
|
+
|
|
463
|
+
# ─── Interactive Wizard ────────────────────────────────────────
|
|
464
|
+
php artisan gc:wizard Product
|
|
465
|
+
|
|
466
|
+
# ─── Extras ───────────────────────────────────────────────────
|
|
467
|
+
php artisan gc Product --fields="name,price" --preview # preview tanpa generate
|
|
468
|
+
php artisan gc Product --fields="name,price" --migration # generate migration only
|
|
469
|
+
php artisan gc:help
|
|
470
|
+
|
|
471
|
+
# ─── Setelah generate ─────────────────────────────────────────
|
|
472
|
+
php artisan migrate
|
|
473
|
+
\`\`\`
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## File yang di-Generate (6 files)
|
|
478
|
+
\`\`\`
|
|
479
|
+
app/Models/Product.php
|
|
480
|
+
app/Queries/ProductQuery.php
|
|
481
|
+
app/Http/Controllers/Api/V1/ProductController.php
|
|
482
|
+
app/Http/Requests/Api/V1/ProductStoreRequest.php
|
|
483
|
+
app/Http/Requests/Api/V1/ProductUpdateRequest.php
|
|
484
|
+
database/migrations/xxxx_create_products_table.php
|
|
485
|
+
routes/api_v1.php ← route ditambah otomatis
|
|
486
|
+
\`\`\`
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
## Field Type Mapping (auto-detect dari nama field)
|
|
491
|
+
|
|
492
|
+
| Nama Field | Type |
|
|
493
|
+
|---|---|
|
|
494
|
+
| nama, name, title, judul | \`string\` |
|
|
495
|
+
| deskripsi, description, content, konten | \`text\` |
|
|
496
|
+
| harga, price, cost, biaya | \`decimal\` |
|
|
497
|
+
| stok, stock, quantity, jumlah | \`integer\` |
|
|
498
|
+
| gambar, image, photo, foto, file, dokumen | \`file\` |
|
|
499
|
+
| is_*, aktif, active, published | \`boolean\` |
|
|
500
|
+
| *_at, tanggal, date | \`datetime\` |
|
|
501
|
+
| slug | \`string\` |
|
|
502
|
+
|
|
503
|
+
**Tersedia juga:** \`string\`, \`text\`, \`integer\`/\`int\`, \`decimal\`/\`float\`, \`boolean\`/\`bool\`, \`date\`, \`datetime\`, \`file\`/\`image\`
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## Decision Logic
|
|
508
|
+
|
|
509
|
+
- User sebut "migration" ATAU schema > 5 field → **Workflow A** (\`gc:from-migration\`)
|
|
510
|
+
- Fields < 5 dan jelas → **Workflow B** (\`gc\`)
|
|
511
|
+
- Tidak jelas / butuh guidance → \`gc:wizard\`
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
## Contoh Cepat
|
|
516
|
+
|
|
517
|
+
\`\`\`bash
|
|
518
|
+
# Simple product
|
|
519
|
+
php artisan gc Product --fields="name,price:decimal,stock:integer" && php artisan migrate
|
|
520
|
+
|
|
521
|
+
# Product dengan file upload & relasi
|
|
522
|
+
php artisan gc Product --fields="name,price:decimal,image:file" --belongsTo="Category,Brand" && php artisan migrate
|
|
523
|
+
|
|
524
|
+
# Dari migration yang sudah ada
|
|
525
|
+
php artisan gc:from-migration products && php artisan migrate
|
|
526
|
+
|
|
527
|
+
# Indonesian fields (auto-translate)
|
|
528
|
+
php artisan gc Produk --fields="nama,harga:decimal,stok:integer,gambar:file"
|
|
529
|
+
# → name, price, stock, image
|
|
530
|
+
\`\`\`
|
|
531
|
+
`;
|
|
532
|
+
|
|
533
|
+
return {
|
|
534
|
+
content: [{ type: "text", text }],
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
// ══════════════════════════════════════════════
|
|
540
|
+
// TOOL: get_api_response_format
|
|
541
|
+
// ══════════════════════════════════════════════
|
|
542
|
+
server.tool(
|
|
543
|
+
"get_api_response_format",
|
|
544
|
+
"Dapatkan format response API standar (ApiResponse trait) beserta semua method yang tersedia dan contoh output JSON-nya.",
|
|
545
|
+
{},
|
|
546
|
+
async () => {
|
|
547
|
+
const text = `# API Response Format — Base API Scramble
|
|
548
|
+
|
|
549
|
+
## Methods Tersedia
|
|
550
|
+
|
|
551
|
+
\`\`\`php
|
|
552
|
+
// Di dalam Controller:
|
|
553
|
+
$this->responseSuccess($data, $message, $statusCode = 200)
|
|
554
|
+
$this->responseData($data, $message) // auto-detect paginator → tambah key pagination
|
|
555
|
+
$this->responseCreated($data, $message) // status 201
|
|
556
|
+
$this->responseDeleted($message) // status 200, data: null
|
|
557
|
+
$this->responseError($message, $code = 400)
|
|
558
|
+
\`\`\`
|
|
559
|
+
|
|
560
|
+
---
|
|
561
|
+
|
|
562
|
+
## Format JSON
|
|
563
|
+
|
|
564
|
+
### ✅ Success (responseSuccess)
|
|
565
|
+
\`\`\`json
|
|
566
|
+
{
|
|
567
|
+
"success": true,
|
|
568
|
+
"message": "Product retrieved successfully",
|
|
569
|
+
"code": 200,
|
|
570
|
+
"data": { "id": "uuid", "name": "iPhone 14" }
|
|
571
|
+
}
|
|
572
|
+
\`\`\`
|
|
573
|
+
|
|
574
|
+
### ✅ Created (responseCreated)
|
|
575
|
+
\`\`\`json
|
|
576
|
+
{
|
|
577
|
+
"success": true,
|
|
578
|
+
"message": "Product created successfully",
|
|
579
|
+
"code": 201,
|
|
580
|
+
"data": { "id": "uuid", "name": "iPhone 14" }
|
|
581
|
+
}
|
|
582
|
+
\`\`\`
|
|
583
|
+
|
|
584
|
+
### ✅ Paginated (responseData dengan paginator)
|
|
585
|
+
\`\`\`json
|
|
586
|
+
{
|
|
587
|
+
"success": true,
|
|
588
|
+
"message": "Products retrieved successfully",
|
|
589
|
+
"code": 200,
|
|
590
|
+
"data": [ { "id": "uuid", "name": "iPhone 14" } ],
|
|
591
|
+
"pagination": {
|
|
592
|
+
"current_page": 1,
|
|
593
|
+
"from": 1,
|
|
594
|
+
"to": 10,
|
|
595
|
+
"total": 50,
|
|
596
|
+
"per_page": 10,
|
|
597
|
+
"last_page": 5,
|
|
598
|
+
"next_page": 2,
|
|
599
|
+
"prev_page": null,
|
|
600
|
+
"path": "http://localhost:8000/api/v1/products"
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
\`\`\`
|
|
604
|
+
|
|
605
|
+
### ✅ Deleted (responseDeleted)
|
|
606
|
+
\`\`\`json
|
|
607
|
+
{
|
|
608
|
+
"success": true,
|
|
609
|
+
"message": "Product deleted successfully",
|
|
610
|
+
"code": 200,
|
|
611
|
+
"data": null
|
|
612
|
+
}
|
|
613
|
+
\`\`\`
|
|
614
|
+
|
|
615
|
+
### ❌ Error (responseError)
|
|
616
|
+
\`\`\`json
|
|
617
|
+
{
|
|
618
|
+
"success": false,
|
|
619
|
+
"message": "Validation failed",
|
|
620
|
+
"code": 422
|
|
621
|
+
}
|
|
622
|
+
\`\`\`
|
|
623
|
+
|
|
624
|
+
---
|
|
625
|
+
|
|
626
|
+
## Lokasi File
|
|
627
|
+
\`app/Helpers/ApiResponse.php\`
|
|
628
|
+
`;
|
|
629
|
+
|
|
630
|
+
return {
|
|
631
|
+
content: [{ type: "text", text }],
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
);
|
|
635
|
+
|
|
636
|
+
// ══════════════════════════════════════════════
|
|
637
|
+
// TOOL: get_query_params
|
|
638
|
+
// ══════════════════════════════════════════════
|
|
639
|
+
server.tool(
|
|
640
|
+
"get_query_params",
|
|
641
|
+
"Dapatkan daftar lengkap query parameter yang tersedia di semua endpoint index: search, filter, sort, include, paginate, dan cara penggunaannya.",
|
|
642
|
+
{},
|
|
643
|
+
async () => {
|
|
644
|
+
const text = `# Query Parameters — Base API Scramble
|
|
645
|
+
|
|
646
|
+
Semua endpoint \`index\` otomatis support parameter berikut:
|
|
647
|
+
|
|
648
|
+
## Parameter Lengkap
|
|
649
|
+
|
|
650
|
+
| Parameter | Contoh | Keterangan |
|
|
651
|
+
|---|---|---|
|
|
652
|
+
| \`search\` | \`?search=keyword\` | Full-text search di kolom \`$searchable\` model |
|
|
653
|
+
| \`filter[kolom]\` | \`?filter[name]=john\` | Filter per kolom (exact/partial sesuai Query class) |
|
|
654
|
+
| \`filter[status]\` | \`?filter[status]=active\` | Filter exact |
|
|
655
|
+
| \`sort_by\` | \`?sort_by=name\` | Kolom untuk sorting |
|
|
656
|
+
| \`sort_order\` | \`?sort_order=asc\` | \`asc\` atau \`desc\` (default: \`desc\`) |
|
|
657
|
+
| \`include\` | \`?include=category,brand\` | Eager load relasi (dari \`$relationIncludes\`) |
|
|
658
|
+
| \`paginate\` | \`?paginate=20\` | Items per page (aktifkan pagination) |
|
|
659
|
+
| \`paginate=false\` | \`?paginate=false\` | Ambil semua data tanpa pagination |
|
|
660
|
+
| \`page\` | \`?page=2\` | Nomor halaman |
|
|
661
|
+
| \`first\` | \`?first=true\` | Ambil hanya 1 data pertama |
|
|
662
|
+
| \`fields[model]\` | \`?fields[products]=id,name\` | Pilih kolom yang dikembalikan |
|
|
663
|
+
|
|
664
|
+
---
|
|
665
|
+
|
|
666
|
+
## Contoh Kombinasi
|
|
667
|
+
|
|
668
|
+
\`\`\`
|
|
669
|
+
GET /api/v1/products?search=iphone&filter[category_id]=uuid&sort_by=price&sort_order=asc&paginate=15&include=category
|
|
670
|
+
|
|
671
|
+
GET /api/v1/users?search=john&filter[status]=active&include=roles,permissions&paginate=25
|
|
672
|
+
|
|
673
|
+
GET /api/v1/products?paginate=false # semua data, tanpa pagination
|
|
674
|
+
|
|
675
|
+
GET /api/v1/products?first=true # hanya 1 data pertama
|
|
676
|
+
\`\`\`
|
|
677
|
+
|
|
678
|
+
---
|
|
679
|
+
|
|
680
|
+
## Sumber Parameter
|
|
681
|
+
|
|
682
|
+
| Parameter | Dihandle oleh |
|
|
683
|
+
|---|---|
|
|
684
|
+
| \`search\`, \`sort_by\`, \`sort_order\`, \`paginate\`, \`first\` | \`baseQuery()\` di Controller (via \`Queryable\` trait) |
|
|
685
|
+
| \`filter[]\`, \`include\`, \`sort\`, \`fields[]\` | Spatie QueryBuilder di Query class |
|
|
686
|
+
|
|
687
|
+
---
|
|
688
|
+
|
|
689
|
+
## Default Behaviour
|
|
690
|
+
- Pagination default: **10 per page**
|
|
691
|
+
- Sort default: **created_at DESC**
|
|
692
|
+
- Tanpa \`?paginate\`: data dikembalikan tanpa pagination (array biasa)
|
|
693
|
+
`;
|
|
694
|
+
|
|
695
|
+
return {
|
|
696
|
+
content: [{ type: "text", text }],
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
// ══════════════════════════════════════════════
|
|
702
|
+
// TOOL: get_file_service
|
|
703
|
+
// ══════════════════════════════════════════════
|
|
704
|
+
server.tool(
|
|
705
|
+
"get_file_service",
|
|
706
|
+
"Panduan cara menggunakan FileService untuk upload file dengan kompresi otomatis, delete file, dan integrasi ke Controller.",
|
|
707
|
+
{},
|
|
708
|
+
async () => {
|
|
709
|
+
const text = `# FileService — Panduan Upload & Delete File
|
|
710
|
+
|
|
711
|
+
## Lokasi
|
|
712
|
+
\`app/Services/FileService.php\`
|
|
713
|
+
|
|
714
|
+
---
|
|
715
|
+
|
|
716
|
+
## Upload File (saveFileComplete)
|
|
717
|
+
|
|
718
|
+
\`\`\`php
|
|
719
|
+
use App\\Services\\FileService;
|
|
720
|
+
|
|
721
|
+
class ProductController extends Controller
|
|
722
|
+
{
|
|
723
|
+
private FileService $fileService;
|
|
724
|
+
|
|
725
|
+
public function __construct()
|
|
726
|
+
{
|
|
727
|
+
$this->fileService = new FileService();
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
public function store(ProductStoreRequest $request)
|
|
731
|
+
{
|
|
732
|
+
$data = $request->validated();
|
|
733
|
+
|
|
734
|
+
if ($request->hasFile('image')) {
|
|
735
|
+
$file = $this->fileService->saveFileComplete(
|
|
736
|
+
file: $request->file('image'),
|
|
737
|
+
folder: 'products', // disimpan di storage/app/products/
|
|
738
|
+
fileName: 'product_' . time(), // nama file (tanpa ekstensi)
|
|
739
|
+
is_compressed: true, // kompresi otomatis (jpg/jpeg/png/webp)
|
|
740
|
+
);
|
|
741
|
+
$data['image'] = $file['name']; // simpan nama file ke DB
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
return $this->responseCreated(Product::create($data), 'Product created');
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
\`\`\`
|
|
748
|
+
|
|
749
|
+
**Return value \`saveFileComplete()\`:**
|
|
750
|
+
\`\`\`php
|
|
751
|
+
[
|
|
752
|
+
'name' => 'product_1234567890.jpg', // nama file yang disimpan
|
|
753
|
+
'path' => '/storage/products/...', // path lengkap
|
|
754
|
+
'size' => 204800, // ukuran dalam bytes
|
|
755
|
+
]
|
|
756
|
+
\`\`\`
|
|
757
|
+
|
|
758
|
+
---
|
|
759
|
+
|
|
760
|
+
## Delete File
|
|
761
|
+
|
|
762
|
+
\`\`\`php
|
|
763
|
+
// Hapus 1 file
|
|
764
|
+
$this->fileService->deleteFile(
|
|
765
|
+
folder: 'products',
|
|
766
|
+
fileName: $product->image // nama file yang tersimpan di DB
|
|
767
|
+
);
|
|
768
|
+
\`\`\`
|
|
769
|
+
|
|
770
|
+
---
|
|
771
|
+
|
|
772
|
+
## Update File (pattern lengkap)
|
|
773
|
+
|
|
774
|
+
\`\`\`php
|
|
775
|
+
public function update(ProductUpdateRequest $request, Product $product)
|
|
776
|
+
{
|
|
777
|
+
$data = $request->validated();
|
|
778
|
+
|
|
779
|
+
// Case 1: Field image di-set null → hapus file lama
|
|
780
|
+
if ($request->input('image') == null && $product->image) {
|
|
781
|
+
$this->fileService->deleteFile(folder: 'products', fileName: $product->image);
|
|
782
|
+
$data['image'] = '';
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// Case 2: Ada file baru → hapus lama, simpan baru
|
|
786
|
+
if ($request->hasFile('image')) {
|
|
787
|
+
if ($product->image) {
|
|
788
|
+
$this->fileService->deleteFile(folder: 'products', fileName: $product->image);
|
|
789
|
+
}
|
|
790
|
+
$file = $this->fileService->saveFileComplete(
|
|
791
|
+
file: $request->file('image'),
|
|
792
|
+
folder: 'products',
|
|
793
|
+
fileName: 'product_' . time(),
|
|
794
|
+
is_compressed: true,
|
|
795
|
+
);
|
|
796
|
+
$data['image'] = $file['name'];
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
$product->update($data);
|
|
800
|
+
return $this->responseSuccess($product, 'Product updated');
|
|
801
|
+
}
|
|
802
|
+
\`\`\`
|
|
803
|
+
|
|
804
|
+
---
|
|
805
|
+
|
|
806
|
+
## Accessor untuk URL (di Model)
|
|
807
|
+
|
|
808
|
+
\`\`\`php
|
|
809
|
+
protected $appends = ['image_url'];
|
|
810
|
+
|
|
811
|
+
public function getImageUrlAttribute(): ?string
|
|
812
|
+
{
|
|
813
|
+
return $this->image
|
|
814
|
+
? asset('uploads/products/' . $this->image)
|
|
815
|
+
: null;
|
|
816
|
+
}
|
|
817
|
+
\`\`\`
|
|
818
|
+
|
|
819
|
+
---
|
|
820
|
+
|
|
821
|
+
## Storage Support
|
|
822
|
+
- **Local:** \`storage/app/{folder}/\`
|
|
823
|
+
- **S3:** \`filemanager/{folder}/\` (konfigurasi di \`.env\`)
|
|
824
|
+
|
|
825
|
+
---
|
|
826
|
+
|
|
827
|
+
## Kompresi Otomatis
|
|
828
|
+
- Menggunakan **Imagick**
|
|
829
|
+
- Support: \`jpg\`, \`jpeg\`, \`png\`, \`webp\`
|
|
830
|
+
- Aktifkan: \`is_compressed: true\`
|
|
831
|
+
`;
|
|
832
|
+
|
|
833
|
+
return {
|
|
834
|
+
content: [{ type: "text", text }],
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
);
|
|
838
|
+
|
|
839
|
+
// ══════════════════════════════════════════════
|
|
840
|
+
// TOOL: get_export_guide
|
|
841
|
+
// ══════════════════════════════════════════════
|
|
842
|
+
server.tool(
|
|
843
|
+
"get_export_guide",
|
|
844
|
+
"Panduan sistem Excel export: BaseExport, Data Provider pattern, template multi-sheet/multi-table, dan konfigurasi di config/export_info.php.",
|
|
845
|
+
{},
|
|
846
|
+
async () => {
|
|
847
|
+
// Coba baca dari docs dulu, fallback ke summary
|
|
848
|
+
const files = ["EXCEL-EXPORT-SERVICE.md", "EXPORT-DATA-PROVIDERS.md"];
|
|
849
|
+
let content = "";
|
|
850
|
+
|
|
851
|
+
for (const f of files) {
|
|
852
|
+
const doc = readDoc(f);
|
|
853
|
+
if (!doc.startsWith("❌")) {
|
|
854
|
+
content += `\n\n---\n# ${f}\n\n${doc}`;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
if (!content) {
|
|
859
|
+
content = `# Excel Export System — Base API Scramble
|
|
860
|
+
|
|
861
|
+
## Overview
|
|
862
|
+
System export Excel menggunakan **PhpSpreadsheet** dengan pattern **Data Provider**.
|
|
863
|
+
|
|
864
|
+
## Komponen Utama
|
|
865
|
+
- \`app/Exports/BaseExport.php\` — Abstract base class
|
|
866
|
+
- \`app/Services/ExcelExportService.php\` — Service layer
|
|
867
|
+
- \`config/export_info.php\` — Konfigurasi styling & metadata
|
|
868
|
+
- Data Provider pattern (interface-based)
|
|
869
|
+
|
|
870
|
+
## Generate Export Module
|
|
871
|
+
\`\`\`bash
|
|
872
|
+
php artisan make:export-excel NamaModule
|
|
873
|
+
\`\`\`
|
|
874
|
+
|
|
875
|
+
Gunakan \`get_doc\` dengan \`EXCEL-EXPORT-SERVICE.md\` untuk dokumentasi lengkap.
|
|
876
|
+
`;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
return {
|
|
880
|
+
content: [{ type: "text", text: content }],
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
);
|
|
884
|
+
|
|
885
|
+
// ══════════════════════════════════════════════
|
|
886
|
+
// TOOL: get_auth_endpoints
|
|
887
|
+
// ══════════════════════════════════════════════
|
|
888
|
+
server.tool(
|
|
889
|
+
"get_auth_endpoints",
|
|
890
|
+
"Daftar endpoint autentikasi dan RBAC yang sudah tersedia: login, logout, profile, role, permission management.",
|
|
891
|
+
{},
|
|
892
|
+
async () => {
|
|
893
|
+
const text = `# Endpoint Bawaan — Base API Scramble
|
|
894
|
+
|
|
895
|
+
## Authentication (Sanctum)
|
|
896
|
+
| Method | Endpoint | Keterangan |
|
|
897
|
+
|---|---|---|
|
|
898
|
+
| POST | \`/api/v1/login\` | Login, dapat Bearer token |
|
|
899
|
+
| POST | \`/api/v1/logout\` | Logout (auth required) |
|
|
900
|
+
| GET | \`/api/v1/me\` | Profile user yang sedang login |
|
|
901
|
+
| PUT | \`/api/v1/me\` | Update profile |
|
|
902
|
+
| PUT | \`/api/v1/change-password\` | Ganti password |
|
|
903
|
+
|
|
904
|
+
**Default credentials:**
|
|
905
|
+
| Email | Password |
|
|
906
|
+
|---|---|
|
|
907
|
+
| \`admin@gmail.com\` | \`password\` |
|
|
908
|
+
| \`dev@gotrasoft.com\` | \`gotradev\` |
|
|
909
|
+
|
|
910
|
+
---
|
|
911
|
+
|
|
912
|
+
## Role & Permission (Spatie RBAC)
|
|
913
|
+
| Method | Endpoint | Keterangan |
|
|
914
|
+
|---|---|---|
|
|
915
|
+
| GET/POST/PUT/DELETE | \`/api/v1/role\` | CRUD Role |
|
|
916
|
+
| GET/POST/PUT/DELETE | \`/api/v1/permission\` | CRUD Permission |
|
|
917
|
+
| POST | \`/api/v1/user-role/attach\` | Assign role ke user |
|
|
918
|
+
| POST | \`/api/v1/user-role/detach\` | Lepas role dari user |
|
|
919
|
+
| POST | \`/api/v1/user-role/sync-roles\` | Sync roles user |
|
|
920
|
+
| POST | \`/api/v1/user-role/sync-users\` | Sync users role |
|
|
921
|
+
| POST | \`/api/v1/role-permission/attach\` | Assign permission ke role |
|
|
922
|
+
| POST | \`/api/v1/role-permission/detach\` | Lepas permission dari role |
|
|
923
|
+
| POST | \`/api/v1/role-permission/sync-permissions\` | Sync permissions role |
|
|
924
|
+
| POST | \`/api/v1/role-permission/sync-roles\` | Sync roles permission |
|
|
925
|
+
|
|
926
|
+
---
|
|
927
|
+
|
|
928
|
+
## File Management
|
|
929
|
+
| Method | Endpoint | Keterangan |
|
|
930
|
+
|---|---|---|
|
|
931
|
+
| GET/POST/PUT/DELETE | \`/api/v1/file\` | CRUD File (soft-delete) |
|
|
932
|
+
| GET/POST/PUT/DELETE | \`/api/v1/folder\` | CRUD Folder |
|
|
933
|
+
| POST | \`/api/v1/file/restore/{id}\` | Restore file yang di-soft-delete |
|
|
934
|
+
|
|
935
|
+
---
|
|
936
|
+
|
|
937
|
+
## Activity Log
|
|
938
|
+
| Method | Endpoint | Keterangan |
|
|
939
|
+
|---|---|---|
|
|
940
|
+
| GET | \`/api/v1/log-activity\` | Lihat semua audit log |
|
|
941
|
+
|
|
942
|
+
---
|
|
943
|
+
|
|
944
|
+
## API Docs
|
|
945
|
+
- **Scramble:** \`http://localhost:8000/docs/api\`
|
|
946
|
+
- **Base URL:** \`http://localhost:8000/api/v1\`
|
|
947
|
+
|
|
948
|
+
---
|
|
949
|
+
|
|
950
|
+
## Headers Wajib
|
|
951
|
+
\`\`\`
|
|
952
|
+
Authorization: Bearer {token}
|
|
953
|
+
Content-Type: application/json
|
|
954
|
+
Accept: application/json
|
|
955
|
+
\`\`\`
|
|
956
|
+
`;
|
|
957
|
+
|
|
958
|
+
return {
|
|
959
|
+
content: [{ type: "text", text }],
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
);
|
|
963
|
+
|
|
964
|
+
// ─────────────────────────────────────────────
|
|
965
|
+
// Start server
|
|
966
|
+
// ─────────────────────────────────────────────
|
|
967
|
+
const transport = new StdioServerTransport();
|
|
968
|
+
await server.connect(transport);
|