feat: add API routes for authentication and task management
- Created api.php for handling authentication routes (register, login, logout) with token middleware. - Implemented task management routes using API resource controller. feat: add console routes for artisan commands - Added console.php to define an inspiring quote command. feat: add web routes for homepage - Created web.php to serve the welcome view at the root URL. chore: add .gitignore files for storage directories - Added .gitignore files in storage/app, storage/framework, and storage/logs to exclude unnecessary files from version control. test: add feature and unit tests - Created ExampleTest in Feature and Unit directories to verify basic application responses and assertions. build: add Vite configuration for Laravel and Tailwind CSS - Added vite.config.js to configure Vite with Laravel and Tailwind CSS for asset management.
This commit is contained in:
56
app/Http/Controllers/AuthController.php
Normal file
56
app/Http/Controllers/AuthController.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\AuthRequest;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
public function login(Request $request)
|
||||
{
|
||||
$user = User::where('email', $request->email)->first();
|
||||
|
||||
if (!$user || !Hash::check($request->password, $user->password)) {
|
||||
return response()->json([
|
||||
'message' => 'Email atau password salah!'
|
||||
], 401);
|
||||
}
|
||||
|
||||
$token = $user->createToken('token')->plainTextToken;
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Login berhasil!',
|
||||
'user' => $user,
|
||||
'token' => $token
|
||||
]);
|
||||
}
|
||||
|
||||
public function register(AuthRequest $request)
|
||||
{
|
||||
$user = User::create([
|
||||
'name' => $request->name,
|
||||
'email' => $request->email,
|
||||
'password' => bcrypt($request->password)
|
||||
]);
|
||||
|
||||
$token = $user->createToken('token')->plainTextToken;
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Register berhasil! Anda sudah otomatis masuk.',
|
||||
'user' => $user,
|
||||
'token' => $token
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function logout(Request $request)
|
||||
{
|
||||
$request->user()->currentAccessToken()->delete();
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Logout berhasil!'
|
||||
]);
|
||||
}
|
||||
}
|
||||
8
app/Http/Controllers/Controller.php
Normal file
8
app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
98
app/Http/Controllers/TaskController.php
Normal file
98
app/Http/Controllers/TaskController.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\TaskRequest;
|
||||
use App\Http\Repositories\TaskRepository;
|
||||
use App\Http\Resources\TaskResource;
|
||||
use App\Models\Task;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class TaskController extends Controller
|
||||
{
|
||||
protected $taskRepository;
|
||||
|
||||
public function __construct(TaskRepository $taskRepository)
|
||||
{
|
||||
$this->taskRepository = $taskRepository;
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$filters = [
|
||||
'is_done' => $request->is_done,
|
||||
'per_page' => $request->per_page ?? 10,
|
||||
];
|
||||
|
||||
// admin bisa lihat semua task
|
||||
if ($user->role !== 'admin') {
|
||||
$filters['user_id'] = $user->id;
|
||||
}
|
||||
|
||||
return TaskResource::collection(
|
||||
$this->taskRepository->getTasks($filters)
|
||||
);
|
||||
}
|
||||
|
||||
public function store(TaskRequest $request)
|
||||
{
|
||||
$task = $this->taskRepository->store([
|
||||
'user_id' => Auth::id(),
|
||||
'title' => $request->title,
|
||||
'description' => $request->description
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Task berhasil dibuat!',
|
||||
'task' => new TaskResource($task)
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function update(TaskRequest $request, $id)
|
||||
{
|
||||
$task = Task::find($id);
|
||||
|
||||
if (!$task) {
|
||||
return response()->json(['message' => 'Task tidak ditemukan!'], 404);
|
||||
}
|
||||
|
||||
if ($task->user_id !== Auth::id()) {
|
||||
return response()->json(['message' => 'Forbidden!'], 403);
|
||||
}
|
||||
|
||||
$task = $this->taskRepository->update($task, [
|
||||
'title' => $request->title ?? $task->title,
|
||||
'description' => $request->description ?? $task->description,
|
||||
'is_done' => $request->is_done ?? $task->is_done,
|
||||
'status' => $request->status ?? $request->status,
|
||||
'due_date' => $request->due_date ?? $request->due_date
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Task berhasil diupdate!',
|
||||
'task' => new TaskResource($task)
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$task = Task::find($id);
|
||||
|
||||
if (!$task) {
|
||||
return response()->json(['message' => 'Task tidak ditemukan!'], 404);
|
||||
}
|
||||
|
||||
if ($task->user_id !== Auth::id()) {
|
||||
return response()->json(['message' => 'Forbidden!'], 403);
|
||||
}
|
||||
|
||||
$task->delete();
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Task berhasil dihapus!'
|
||||
]);
|
||||
}
|
||||
}
|
||||
50
app/Http/Middleware/CheckToken.php
Normal file
50
app/Http/Middleware/CheckToken.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Laravel\Sanctum\PersonalAccessToken;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class CheckToken
|
||||
{
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$token = $request->bearerToken();
|
||||
|
||||
// tidak ada token, lanjut
|
||||
if (!$token) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
// sanctum menyimpan token dengan format "id|token"
|
||||
$parts = explode('|', $token);
|
||||
|
||||
// pastikan format token benar
|
||||
if (count($parts) !== 2) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
// cari token berdasarkan id
|
||||
$accessToken = PersonalAccessToken::find($parts[0]);
|
||||
|
||||
// token tidak ditemukan
|
||||
if (!$accessToken) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
// verifikasi hash token
|
||||
$hashedToken = hash('sha256', $parts[1]);
|
||||
|
||||
if (!hash_equals($accessToken->token, $hashedToken)) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
// token valid, tolak request
|
||||
Log::info('CheckToken - Token valid, tolak request');
|
||||
return response()->json([
|
||||
'message' => 'Anda sudah login, silahkan logout terlebih dahulu!'
|
||||
], 403);
|
||||
}
|
||||
}
|
||||
21
app/Http/Middleware/ForceJsonResponse.php
Normal file
21
app/Http/Middleware/ForceJsonResponse.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ForceJsonResponse
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Closure(Request): (Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$request->headers->set('Accept', 'application/json');
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
45
app/Http/Repositories/TaskRepository.php
Normal file
45
app/Http/Repositories/TaskRepository.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Repositories;
|
||||
|
||||
use App\Interfaces\TaskRepositoryInterface;
|
||||
use App\Models\Task;
|
||||
|
||||
class TaskRepository implements TaskRepositoryInterface
|
||||
{
|
||||
public function getTasks(array $filters)
|
||||
{
|
||||
$query = Task::query();
|
||||
|
||||
// filter berdasarkan user
|
||||
if (isset($filters['user_id'])) {
|
||||
$query->where('user_id', $filters['user_id']);
|
||||
}
|
||||
|
||||
// filter berdasarkan status is_done
|
||||
if (isset($filters['is_done'])) {
|
||||
$query->where('is_done', $filters['is_done']);
|
||||
}
|
||||
|
||||
// pagination
|
||||
$perPage = $filters['per_page'] ?? 10;
|
||||
|
||||
return $query->paginate($perPage);
|
||||
}
|
||||
|
||||
public function store(array $data)
|
||||
{
|
||||
return Task::create($data);
|
||||
}
|
||||
|
||||
public function update($task, array $data)
|
||||
{
|
||||
$task->update($data);
|
||||
return $task;
|
||||
}
|
||||
|
||||
public function delete($task)
|
||||
{
|
||||
return $task->delete();
|
||||
}
|
||||
}
|
||||
42
app/Http/Requests/AuthRequest.php
Normal file
42
app/Http/Requests/AuthRequest.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class AuthRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required',
|
||||
'email' => 'required|email|unique:users',
|
||||
'password' => 'required|min:8'
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'name.required' => 'Username wajib diisi!',
|
||||
'email.required' => 'Email wajib diisi!',
|
||||
'email.email' => 'Format Email tidak valid!',
|
||||
'email.unique' => 'Email sudah terdaftar!',
|
||||
'password.required' => 'Password wajib diisi!',
|
||||
'password.min' => 'Password minimal 8 karakter!'
|
||||
];
|
||||
}
|
||||
}
|
||||
39
app/Http/Requests/TaskRequest.php
Normal file
39
app/Http/Requests/TaskRequest.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class TaskRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = [
|
||||
'title' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
];
|
||||
|
||||
// jika update, title tidak wajib diisi
|
||||
if ($this->isMethod('put') || $this->isMethod('patch')) {
|
||||
$rules['title'] = 'sometimes|string|max:255';
|
||||
$rules['is_done'] = 'sometimes|boolean';
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'title.required' => 'Judul task wajib diisi!',
|
||||
'title.string' => 'Judul task harus berupa teks!',
|
||||
'title.max' => 'Judul task maksimal 255 karakter!',
|
||||
'is_done.boolean' => 'Status task harus bernilai 0 atau 1!',
|
||||
];
|
||||
}
|
||||
}
|
||||
28
app/Http/Resources/TaskResource.php
Normal file
28
app/Http/Resources/TaskResource.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class TaskResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'title' => $this->title,
|
||||
'description' => $this->description,
|
||||
'is_done' => $this->is_done,
|
||||
'status' => $this->status,
|
||||
'due_date' => $this->due_date,
|
||||
'created_at' => $this->created_at,
|
||||
'updated_at' => $this->updated_at
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user