Logs, Categories created

This commit is contained in:
Stepan 2025-11-10 22:47:08 +01:00
parent 823d498de7
commit 8ca7fa0fd0
14 changed files with 889 additions and 53 deletions

View File

@ -4,6 +4,7 @@ namespace App\Http\Controllers;
use App\Http\Resources\UserResource; use App\Http\Resources\UserResource;
use App\Mail\EmailActivation; use App\Mail\EmailActivation;
use App\Models\Log;
use App\Models\Token; use App\Models\Token;
use App\Models\User; use App\Models\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -59,6 +60,8 @@ class AuthController extends Controller
$user = User::create($fields); $user = User::create($fields);
Log::writeLog("User '" . $user->username . "' is registered");
$token = Token::existsToken($user, 'email_verification'); $token = Token::existsToken($user, 'email_verification');
if(!$token) { if(!$token) {
$token = Token::createToken($user, 'email_verification'); $token = Token::createToken($user, 'email_verification');
@ -126,6 +129,8 @@ class AuthController extends Controller
} }
$token = $user->createToken('auth_token')->plainTextToken; $token = $user->createToken('auth_token')->plainTextToken;
Log::writeLog("User '" . $user->username . "' is logged into his account");
return response()->json([ return response()->json([
'access_token' => $token, 'access_token' => $token,
'token_type' => 'Bearer', 'token_type' => 'Bearer',
@ -297,6 +302,8 @@ class AuthController extends Controller
$user->password = $new_password; $user->password = $new_password;
$user->save(); $user->save();
Log::writeLog("User '" . $user->username . "' is resetted his password");
return response()->json([ return response()->json([
'message' => 'Your password was successfully changed!' 'message' => 'Your password was successfully changed!'

View File

@ -2,62 +2,185 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Http\Requests\StoreCategoryRequest; use App\Http\Resources\CategoryResource;
use App\Http\Requests\UpdateCategoryRequest;
use App\Models\Category; use App\Models\Category;
use App\Models\Log;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class CategoryController extends Controller class CategoryController extends Controller
{ {
/** /**
* Display a listing of the resource. * @OA\Get(
* path="/api/categories",
* summary="Get all categories",
* tags={"Categories"},
* security={{"bearerAuth": {}}},
* @OA\Response(
* response=200,
* description="List of categories",
* @OA\JsonContent(
* type="array",
* @OA\Items(ref="#/components/schemas/CategoryResource")
* )
* )
* )
*/ */
public function index() public function index()
{ {
return Category::all(); return CategoryResource::collection(Category::all());
} }
/** /**
* Store a newly created resource in storage. * @OA\Post(
* path="/api/categories",
* summary="Create a new category (only admin)",
* tags={"Categories"},
* security={{"bearerAuth": {}}},
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(
* required={"name"},
* @OA\Property(property="name", type="string", example="Physics")
* )
* ),
* @OA\Response(
* response=200,
* description="Category created successfully",
* @OA\JsonContent(ref="#/components/schemas/CategoryResource")
* ),
* @OA\Response(
* response=403,
* description="Forbidden"
* )
* )
*/ */
public function store(Request $request) public function store(Request $request)
{ {
$this->authorize('create', Category::class);
$fields = $request->validate([ $fields = $request->validate([
'name' => 'required|max:150' 'name' => 'required|max:150'
]); ]);
$category = Category::create($fields); $category = Category::create($fields);
return $category; Log::writeLog("Category '" . $category->name . "' is created");
return new CategoryResource($category);
} }
/** /**
* Display the specified resource. * @OA\Get(
* path="/api/categories/{id}",
* summary="Get a specific category",
* tags={"Categories"},
* @OA\Parameter(
* name="id",
* in="path",
* required=true,
* description="ID of the category",
* @OA\Schema(type="integer", example=1)
* ),
* @OA\Response(
* response=200,
* description="Category retrieved successfully",
* @OA\JsonContent(ref="#/components/schemas/CategoryResource")
* ),
* @OA\Response(
* response=404,
* description="Category not found"
* )
* )
*/ */
public function show(Category $category) public function show(Category $category)
{ {
return $category; return new CategoryResource($category);
} }
/** /**
* Update the specified resource in storage. * @OA\Put(
* path="/api/categories/{id}",
* summary="Update a category (only admin)",
* tags={"Categories"},
* security={{"bearerAuth": {}}},
* @OA\Parameter(
* name="id",
* in="path",
* required=true,
* description="ID of the category",
* @OA\Schema(type="integer", example=1)
* ),
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(
* required={"name"},
* @OA\Property(property="name", type="string", example="Physics Updated")
* )
* ),
* @OA\Response(
* response=200,
* description="Category updated successfully",
* @OA\JsonContent(ref="#/components/schemas/CategoryResource")
* ),
* @OA\Response(
* response=403,
* description="Forbidden"
* ),
* @OA\Response(
* response=404,
* description="Category not found"
* )
* )
*/ */
public function update(Request $request, Category $category) public function update(Request $request, Category $category)
{ {
$this->authorize('update', $category);
$fields = $request->validate([ $fields = $request->validate([
'name' => 'required|max:150' 'name' => 'required|max:150'
]); ]);
$old_category_name = $category->name;
$category->update($fields); $category->update($fields);
return $category; Log::writeLog("Category '$old_category_name' is renamed to '" . $category->name ."'");
return new CategoryResource($category);
} }
/** /**
* Remove the specified resource from storage. * @OA\Delete(
* path="/api/categories/{id}",
* summary="Delete a category (only admin)",
* tags={"Categories"},
* security={{"bearerAuth": {}}},
* @OA\Parameter(
* name="id",
* in="path",
* required=true,
* description="ID of the category",
* @OA\Schema(type="integer", example=1)
* ),
* @OA\Response(
* response=200,
* description="Category deleted successfully",
* @OA\JsonContent(
* @OA\Property(property="message", type="string", example="The category was deleted")
* )
* ),
* @OA\Response(
* response=403,
* description="Forbidden"
* ),
* @OA\Response(
* response=404,
* description="Category not found"
* )
* )
*/ */
public function destroy(Category $category) public function destroy(Category $category)
{ {
$this->authorize('delete', $category);
$category->delete(); $category->delete();
Log::writeLog("Category '" . $category->name . "' is deleted");
return ['message' => 'The category was deleted']; return ['message' => 'The category was deleted'];
} }
} }

View File

@ -2,6 +2,10 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
/** /**
* @OA\Info( * @OA\Info(
* title="HoshiAI API", * title="HoshiAI API",
@ -11,5 +15,5 @@ namespace App\Http\Controllers;
*/ */
abstract class Controller abstract class Controller
{ {
// use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
} }

View File

@ -0,0 +1,114 @@
<?php
namespace App\Http\Controllers;
use App\Http\Resources\LogResource;
use App\Models\Log;
use Illuminate\Http\Request;
class LogController extends Controller
{
private const PAGINATED_COUNT = 20;
/**
* @OA\Get(
* path="/api/logs",
* summary="Get all logs (paginated, only admin)",
* tags={"Logs"},
* security={{"bearerAuth": {}}},
* @OA\Response(
* response=200,
* description="List of logs",
* @OA\JsonContent(
* type="array",
* @OA\Items(ref="#/components/schemas/LogResource")
* )
* ),
* @OA\Response(
* response=403,
* description="Forbidden"
* )
* )
*/
public function index()
{
$this->authorize('viewAny', Log::class);
return LogResource::collection(Log::paginate());
}
/**
* @OA\Get(
* path="/api/logs/{id}",
* summary="Get a specific log (only admin)",
* tags={"Logs"},
* security={{"bearerAuth": {}}},
* @OA\Parameter(
* name="id",
* in="path",
* required=true,
* description="ID of the log",
* @OA\Schema(type="integer", example=1)
* ),
* @OA\Response(
* response=200,
* description="Log retrieved successfully",
* @OA\JsonContent(ref="#/components/schemas/LogResource")
* ),
* @OA\Response(
* response=403,
* description="Forbidden"
* ),
* @OA\Response(
* response=404,
* description="Log not found"
* )
* )
*/
public function show(Log $log)
{
$this->authorize('view', $log);
return $log;
}
/**
* @OA\Delete(
* path="/api/logs/{id}",
* summary="Delete a specific log (only admin)",
* tags={"Logs"},
* security={{"bearerAuth": {}}},
* @OA\Parameter(
* name="id",
* in="path",
* required=true,
* description="ID of the log",
* @OA\Schema(type="integer", example=1)
* ),
* @OA\Response(
* response=200,
* description="Log deleted successfully",
* @OA\JsonContent(
* @OA\Property(property="message", type="string", example="This log is successfully deleted")
* )
* ),
* @OA\Response(
* response=403,
* description="Forbidden"
* ),
* @OA\Response(
* response=404,
* description="Log not found"
* )
* )
*/
public function destroy(Log $log)
{
$this->authorize('delete', $log);
$log->delete();
return response()->json([
'message' => 'This log is successfully deleted'
]);
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @OA\Schema(
* schema="CategoryResource",
* type="object",
* title="Category",
* description="Category resource with dynamic count",
* @OA\Property(
* property="id",
* type="integer",
* example=1
* ),
* @OA\Property(
* property="name",
* type="string",
* example="Books"
* ),
* @OA\Property(
* property="created_at",
* type="string",
* format="date-time",
* example="2025-11-10T18:00:00Z"
* ),
* @OA\Property(
* property="count",
* type="integer",
* example=5,
* description="Dynamic count of related items"
* )
* )
*/
class CategoryResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'created_at' => $this->created_at,
'count' => $this->count ?? 0
];
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @OA\Schema(
* schema="LogResource",
* type="object",
* title="Log",
* description="Log resource",
* @OA\Property(
* property="id",
* type="integer",
* example=1
* ),
* @OA\Property(
* property="description",
* type="string",
* example="User logged in"
* ),
* @OA\Property(
* property="created_at",
* type="string",
* format="date-time",
* example="2025-11-10T17:45:00.000000Z"
* )
* )
*/
class LogResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'description' => $this->description,
'created_at' => $this->created_at
];
}
}

40
app/Models/Log.php Normal file
View File

@ -0,0 +1,40 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Log extends Model
{
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'description',
'type'
];
/**
* Creating new log
* @param string $descpr
* @return void
*/
public static function writeLog(string $descpr, string $type = 'access')
{
self::create(['description' => $descpr, 'type' => $type]);
}
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'created_at' => 'datetime'
];
}
}

View File

@ -8,22 +8,6 @@ use Illuminate\Auth\Access\Response;
class CategoryPolicy class CategoryPolicy
{ {
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return false;
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Category $category): bool
{
return false;
}
/** /**
* Determine whether the user can create models. * Determine whether the user can create models.
*/ */
@ -47,20 +31,4 @@ class CategoryPolicy
{ {
return $user->type === 'admin'; return $user->type === 'admin';
} }
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Category $category): bool
{
return false;
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Category $category): bool
{
return false;
}
} }

View File

@ -0,0 +1,34 @@
<?php
namespace App\Policies;
use App\Models\Log;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class LogPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->type === 'admin';
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Log $log): bool
{
return $user->type === 'admin';
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Log $log): bool
{
return $user->type === 'admin';
}
}

View File

@ -40,6 +40,11 @@ return [
'driver' => 'session', 'driver' => 'session',
'provider' => 'users', 'provider' => 'users',
], ],
'sanctum' => [
'driver' => 'sanctum',
'provider' => 'users',
],
], ],
/* /*

View File

@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('logs', function (Blueprint $table) {
$table->id();
$table->string("description", 255);
$table->enum('type', ['error', 'access'])->default('access');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('logs');
}
};

View File

@ -2,14 +2,24 @@
use App\Http\Controllers\AuthController; use App\Http\Controllers\AuthController;
use App\Http\Controllers\CategoryController; use App\Http\Controllers\CategoryController;
use App\Http\Controllers\LogController;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
// Route::get('/user', function (Request $request) {
// return $request->user();
// })->middleware('auth:sanctum');
Route::apiResource('categories', CategoryController::class);
// CategoryController
Route::get('categories', [CategoryController::class, 'index']);
Route::get('categories/{category}', [CategoryController::class, 'show']);
Route::middleware('auth:sanctum')->group(function () {
Route::post('categories', [CategoryController::class, 'store']);
Route::put('categories/{category}', [CategoryController::class, 'update']);
Route::delete('categories/{category}', [CategoryController::class, 'destroy']);
});
Route::apiResource('logs', LogController::class)
->only(['index', 'show', 'destroy'])->middleware('auth:sanctum');
// AuthController // AuthController
Route::post('/auth/register', [ AuthController::class, 'register' ]); Route::post('/auth/register', [ AuthController::class, 'register' ]);
@ -18,4 +28,8 @@ Route::post('/auth/logout', [ AuthController::class, 'logout' ])->middleware('au
Route::post('/auth/forgot-password', [ AuthController::class, 'forgotPassword' ]); Route::post('/auth/forgot-password', [ AuthController::class, 'forgotPassword' ]);
Route::post('/auth/reset-password', [ AuthController::class, 'resetPassword' ]); Route::post('/auth/reset-password', [ AuthController::class, 'resetPassword' ]);
Route::post('/auth/activate-account', [ AuthController::class, 'confirmationAccount' ]); Route::post('/auth/activate-account', [ AuthController::class, 'confirmationAccount' ]);
Route::get('/auth/me', [ AuthController::class, 'me' ])->middleware('auth:sanctum');
Route::middleware('auth:sanctum')->group(function () {
Route::post('/auth/logout', [ AuthController::class, 'logout' ]);
Route::get('/auth/me', [ AuthController::class, 'me' ])->middleware('auth:sanctum');
});

View File

@ -546,10 +546,399 @@
} }
] ]
} }
},
"/api/categories": {
"get": {
"tags": [
"Categories"
],
"summary": "Get all categories",
"operationId": "3f5817a34833d0a1f4af4548dd3aeaba",
"responses": {
"200": {
"description": "List of categories",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CategoryResource"
}
}
}
}
}
},
"security": [
{
"bearerAuth": []
}
]
},
"post": {
"tags": [
"Categories"
],
"summary": "Create a new category (only admin)",
"operationId": "71fcad552bb0eaba9fb191fd8d8dcab0",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"example": "Physics"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Category created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CategoryResource"
}
}
}
},
"403": {
"description": "Forbidden"
}
},
"security": [
{
"bearerAuth": []
}
]
}
},
"/api/categories/{id}": {
"get": {
"tags": [
"Categories"
],
"summary": "Get a specific category",
"operationId": "c68e76d323c008827a9e47398b1583de",
"parameters": [
{
"name": "id",
"in": "path",
"description": "ID of the category",
"required": true,
"schema": {
"type": "integer",
"example": 1
}
}
],
"responses": {
"200": {
"description": "Category retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CategoryResource"
}
}
}
},
"404": {
"description": "Category not found"
}
}
},
"put": {
"tags": [
"Categories"
],
"summary": "Update a category (only admin)",
"operationId": "0e686b2748211cc688333ed705dc111f",
"parameters": [
{
"name": "id",
"in": "path",
"description": "ID of the category",
"required": true,
"schema": {
"type": "integer",
"example": 1
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"example": "Physics Updated"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Category updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CategoryResource"
}
}
}
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Category not found"
}
},
"security": [
{
"bearerAuth": []
}
]
},
"delete": {
"tags": [
"Categories"
],
"summary": "Delete a category (only admin)",
"operationId": "4c12e22a7f8c617bd83bfa1fdc05428d",
"parameters": [
{
"name": "id",
"in": "path",
"description": "ID of the category",
"required": true,
"schema": {
"type": "integer",
"example": 1
}
}
],
"responses": {
"200": {
"description": "Category deleted successfully",
"content": {
"application/json": {
"schema": {
"properties": {
"message": {
"type": "string",
"example": "The category was deleted"
}
},
"type": "object"
}
}
}
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Category not found"
}
},
"security": [
{
"bearerAuth": []
}
]
}
},
"/api/logs": {
"get": {
"tags": [
"Logs"
],
"summary": "Get all logs (paginated, only admin)",
"operationId": "07258c00ce1b2cbc7c7151a7cc8ca986",
"responses": {
"200": {
"description": "List of logs",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LogResource"
}
}
}
}
},
"403": {
"description": "Forbidden"
}
},
"security": [
{
"bearerAuth": []
}
]
}
},
"/api/logs/{id}": {
"get": {
"tags": [
"Logs"
],
"summary": "Get a specific log (only admin)",
"operationId": "caa09131dde473dca25ea025d181146a",
"parameters": [
{
"name": "id",
"in": "path",
"description": "ID of the log",
"required": true,
"schema": {
"type": "integer",
"example": 1
}
}
],
"responses": {
"200": {
"description": "Log retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LogResource"
}
}
}
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Log not found"
}
},
"security": [
{
"bearerAuth": []
}
]
},
"delete": {
"tags": [
"Logs"
],
"summary": "Delete a specific log (only admin)",
"operationId": "2a0e57b9168eaca7e207f5b35f469666",
"parameters": [
{
"name": "id",
"in": "path",
"description": "ID of the log",
"required": true,
"schema": {
"type": "integer",
"example": 1
}
}
],
"responses": {
"200": {
"description": "Log deleted successfully",
"content": {
"application/json": {
"schema": {
"properties": {
"message": {
"type": "string",
"example": "This log is successfully deleted"
}
},
"type": "object"
}
}
}
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Log not found"
}
},
"security": [
{
"bearerAuth": []
}
]
}
} }
}, },
"components": { "components": {
"schemas": { "schemas": {
"CategoryResource": {
"title": "Category",
"description": "Category resource with dynamic count",
"properties": {
"id": {
"type": "integer",
"example": 1
},
"name": {
"type": "string",
"example": "Books"
},
"created_at": {
"type": "string",
"format": "date-time",
"example": "2025-11-10T18:00:00Z"
},
"count": {
"description": "Dynamic count of related items",
"type": "integer",
"example": 5
}
},
"type": "object"
},
"LogResource": {
"title": "Log",
"description": "Log resource",
"properties": {
"id": {
"type": "integer",
"example": 1
},
"description": {
"type": "string",
"example": "User logged in"
},
"created_at": {
"type": "string",
"format": "date-time",
"example": "2025-11-10T17:45:00.000000Z"
}
},
"type": "object"
},
"UserResource": { "UserResource": {
"properties": { "properties": {
"id": { "id": {
@ -596,6 +985,14 @@
{ {
"name": "Auth", "name": "Auth",
"description": "Auth" "description": "Auth"
},
{
"name": "Categories",
"description": "Categories"
},
{
"name": "Logs",
"description": "Logs"
} }
] ]
} }