# Introduction

{% embed url="<https://www.youtube.com/watch?v=6_wfq76CG6g>" fullWidth="false" %}

Welcome to the Blog API documentation! This API provides functionalities for managing blog posts, users, comments, and likes.

## Base URL

All API endpoints are relative to the following base URL: /api/v1

For example, the user registration endpoint is `/api/v1/auth/register`.

## Overview

* **API Version:** 1.0.0
* **Authentication:** Uses JWT Bearer tokens for access and JWT refresh tokens (via HTTP-only cookies) for session renewal.
* **Authorization:** Role-based access control ('admin', 'user'). Specific roles are required for certain endpoints.
* **Rate Limiting:** Applied globally (60 requests per minute per IP). Exceeding the limit returns a `429 Too Many Requests` error.
* **Input Validation:** Uses `express-validator`. Invalid requests return detailed `400 Bad Request` errors.
* **Content Format:** Primarily JSON (`application/json`). File uploads use `multipart/form-data`.


# Authentication

This API uses JSON Web Tokens (JWT) for securing endpoints. It employs a two-token strategy: an Access Token and a Refresh Token.

## Access Token

* **Usage:** Required for accessing protected API endpoints.
* **Format:** Standard JWT.
* **Transmission:** Must be included in the `Authorization` header of your request using the `Bearer` scheme.

```http
  Authorization: Bearer <your_access_token>
```

* **Lifetime:** Access tokens have a predefined expiration time (configured on the server, e.g., 15 minutes). Once expired, you'll receive a `401 Unauthorized` error.
* **Obtaining:** Received upon successful registration (`/auth/register`) or login (`/auth/login`). Can be renewed using a valid Refresh Token (`/auth/refresh-token`).

## Refresh Token

* **Usage:** Used to obtain a new Access Token when the current one expires, without requiring the user to log in again.
* **Format:** Standard JWT.
* **Transmission:** Automatically handled via an `HttpOnly`, `Secure` (in production), `SameSite=Strict` cookie named `refreshToken`. You generally don't need to manage this token directly in your client-side code.
* **Lifetime:** Refresh tokens have a longer expiration time (configured on the server, e.g., 7 days).
* **Obtaining:** Set automatically as a cookie upon successful registration or login.
* **Invalidation:** Deleted from the server and the cookie is cleared upon logout (`/auth/logout`).

## Authentication Flow

1. **Register or Login:** Call `/auth/register` or `/auth/login` with valid credentials.
   * Receive an `accessToken` in the response body.
   * Receive a `refreshToken` set as an HttpOnly cookie.
2. **Access Protected Resources:** Make requests to other API endpoints, including the `accessToken` in the `Authorization: Bearer <token>` header.
3. **Handle Expired Access Token:** If a request returns a `401 Unauthorized` error indicating an expired access token:
   * Call `POST /auth/refresh-token`. This endpoint uses the `refreshToken` cookie automatically sent by the browser.
   * Receive a new `accessToken` in the response.
   * Retry the original request with the new `accessToken`.
4. **Handle Expired Refresh Token:** If the `/auth/refresh-token` endpoint returns a `401 Unauthorized` error, the refresh token has expired or is invalid. The user must log in again via `/auth/login`.
5. **Logout:** Call `POST /auth/logout`. This requires both the `accessToken` (in the header) and the `refreshToken` (in the cookie) to be valid. It invalidates the refresh token on the server and clears the cookie.


# Rate Limiting

To ensure fair usage and protect the API from abuse, rate limiting is applied.

* **Limit:** 60 requests per minute per IP address.
* **Headers:** Standard rate limit headers (`RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`, `Retry-After`) are included in responses according to the [IETF Draft standard](https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-ratelimit-headers-08).
* **Exceeding Limit:** If you exceed the rate limit, you will receive an HTTP `429 Too Many Requests` response. Check the `Retry-After` header to see when you can make requests again.

**Example 429 Response:**

```http
HTTP/1.1 429 Too Many Requests
RateLimit-Limit: 60
RateLimit-Remaining: 0
RateLimit-Reset: 45  // Seconds until the limit resets
Retry-After: 45      // Seconds to wait before retrying
Content-Type: application/json

{
  "error": "You can only make 60 requests every minute."
}
```


# Error Handling

The API uses standard HTTP status codes to indicate the success or failure of a request. Errors generally return a JSON body with `code` and `message` fields. Validation errors include an additional `errors` object.

## Common Status Codes & Error Codes

| Status Code                 | `code` Value(s)       | Meaning                                                                                                  |
| --------------------------- | --------------------- | -------------------------------------------------------------------------------------------------------- |
| `200 OK`                    | N/A                   | Request successful.                                                                                      |
| `201 Created`               | N/A                   | Resource created successfully.                                                                           |
| `204 No Content`            | N/A                   | Request successful, no response body needed (e.g., successful deletion).                                 |
| `400 Bad Request`           | `ValidationError`     | Input validation failed (e.g., missing required field, invalid format). See `errors` object for details. |
| `400 Bad Request`           | `BadRequest`          | General bad request (e.g., trying to like an already liked blog).                                        |
| `401 Unauthorized`          | `AuthenticationError` | Missing, invalid, or expired `accessToken` or `refreshToken`. Check `message` for specifics.             |
| `403 Forbidden`             | `AuthorizationError`  | User lacks permission (role) for the action, or attempting unauthorized admin registration.              |
| `404 Not Found`             | `NotFound`            | The requested resource (user, blog, comment) could not be found.                                         |
| `413 Payload Too Large`     | `ValidationError`     | Uploaded file exceeds the size limit (2MB for blog banners).                                             |
| `429 Too Many Requests`     | N/A                   | Rate limit exceeded (see Rate Limiting guide).                                                           |
| `500 Internal Server Error` | `ServerError`         | An unexpected error occurred on the server. Contact support if this persists.                            |

## Error Response Format

**General Error:**

```json
{
  "code": "NotFound",
  "message": "Blog not found"
}
```

**Validation Error:**

```json
{
  "code": "ValidationError",
  "errors": {
    "email": {
      "type": "field",
      "value": "invalid-email",
      "msg": "Invalid email address",
      "path": "email",
      "location": "body"
    },
    "password": {
      "type": "field",
      "value": "short",
      "msg": "Password must be at least 8 characters long",
      "path": "password",
      "location": "body"
    }
  }
}
```


# API Reference

This section provides an interactive reference for the Blog API, generated from the OpenAPI specification.


# Root

API Status

## Get API Status

> Provides basic status and information about the API.

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"tags":[{"name":"Root","description":"API Status"}],"servers":[{"url":"https://blog-api.codewithsadee.com/api/v1","description":"API v1 Base Path"}],"security":[],"paths":{"/":{"get":{"tags":["Root"],"summary":"Get API Status","description":"Provides basic status and information about the API.","operationId":"getApiStatus","responses":{"200":{"description":"API Status Information","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"status":{"type":"string"},"version":{"type":"string"},"docs":{"type":"string","format":"url"},"timestamp":{"type":"string","format":"date-time"}}}}}}}}}}}
```


# Authentication

User authentication operations

## Register New User

> Creates a new user account. Admin registration requires whitelisted email.

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"tags":[{"name":"Authentication","description":"User authentication operations"}],"servers":[{"url":"https://blog-api.codewithsadee.com/api/v1","description":"API v1 Base Path"}],"security":[],"paths":{"/auth/register":{"post":{"tags":["Authentication"],"summary":"Register New User","description":"Creates a new user account. Admin registration requires whitelisted email.","operationId":"registerUser","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserInputRequired"}}}},"responses":{"201":{"description":"User registered successfully. Sets refreshToken cookie.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"400":{"$ref":"#/components/responses/BadRequestValidation"},"403":{"description":"Admin registration denied for non-whitelisted email.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"$ref":"#/components/responses/ServerError"}}}}},"components":{"schemas":{"UserInputRequired":{"type":"object","properties":{"email":{"type":"string","format":"email","description":"User's email address","maxLength":50},"password":{"type":"string","description":"User's password","minLength":8,"writeOnly":true},"role":{"type":"string","enum":["admin","user"],"description":"User role (optional for registration)"}},"required":["email","password"]},"LoginResponse":{"allOf":[{"$ref":"#/components/schemas/AccessTokenResponse"},{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"}}}]},"AccessTokenResponse":{"type":"object","properties":{"accessToken":{"type":"string","description":"JWT Access Token"}}},"User":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","description":"Unique identifier for the user","readOnly":true},"username":{"type":"string","description":"User's unique username","maxLength":20},"email":{"type":"string","format":"email","description":"User's unique email address","maxLength":50},"role":{"type":"string","enum":["admin","user"],"description":"User role","readOnly":true,"default":"user"},"firstName":{"type":"string","description":"User's first name","maxLength":20},"lastName":{"type":"string","description":"User's last name","maxLength":20},"socialLinks":{"type":"object","properties":{"website":{"type":"string","format":"url","maxLength":100},"facebook":{"type":"string","format":"url","maxLength":100},"instagram":{"type":"string","format":"url","maxLength":100},"linkedin":{"type":"string","format":"url","maxLength":100},"x":{"type":"string","format":"url","maxLength":100},"youtube":{"type":"string","format":"url","maxLength":100}}},"createdAt":{"type":"string","format":"date-time","description":"Timestamp of user creation","readOnly":true},"updatedAt":{"type":"string","format":"date-time","description":"Timestamp of last user update","readOnly":true}},"required":["username","email","role"]},"ValidationErrorResponse":{"type":"object","properties":{"code":{"type":"string","enum":["ValidationError"]},"errors":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ValidationErrorDetail"}}},"required":["code","errors"]},"ValidationErrorDetail":{"type":"object","properties":{"type":{"type":"string"},"value":{"type":"string"},"msg":{"type":"string"},"path":{"type":"string"},"location":{"type":"string"}}},"ErrorResponse":{"type":"object","properties":{"code":{"type":"string","description":"Application-specific error code"},"message":{"type":"string","description":"Human-readable error message"}},"required":["code","message"]}},"responses":{"BadRequestValidation":{"description":"Invalid input data provided. See errors object for details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}},"ServerError":{"description":"An unexpected error occurred on the server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}
```

## Login User

> Authenticates a user and returns tokens.

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"tags":[{"name":"Authentication","description":"User authentication operations"}],"servers":[{"url":"https://blog-api.codewithsadee.com/api/v1","description":"API v1 Base Path"}],"security":[],"paths":{"/auth/login":{"post":{"tags":["Authentication"],"summary":"Login User","description":"Authenticates a user and returns tokens.","operationId":"loginUser","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserInputRequired"}}}},"responses":{"200":{"description":"Login successful. Sets refreshToken cookie.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"400":{"$ref":"#/components/responses/BadRequestValidation"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/ServerError"}}}}},"components":{"schemas":{"UserInputRequired":{"type":"object","properties":{"email":{"type":"string","format":"email","description":"User's email address","maxLength":50},"password":{"type":"string","description":"User's password","minLength":8,"writeOnly":true},"role":{"type":"string","enum":["admin","user"],"description":"User role (optional for registration)"}},"required":["email","password"]},"LoginResponse":{"allOf":[{"$ref":"#/components/schemas/AccessTokenResponse"},{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"}}}]},"AccessTokenResponse":{"type":"object","properties":{"accessToken":{"type":"string","description":"JWT Access Token"}}},"User":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","description":"Unique identifier for the user","readOnly":true},"username":{"type":"string","description":"User's unique username","maxLength":20},"email":{"type":"string","format":"email","description":"User's unique email address","maxLength":50},"role":{"type":"string","enum":["admin","user"],"description":"User role","readOnly":true,"default":"user"},"firstName":{"type":"string","description":"User's first name","maxLength":20},"lastName":{"type":"string","description":"User's last name","maxLength":20},"socialLinks":{"type":"object","properties":{"website":{"type":"string","format":"url","maxLength":100},"facebook":{"type":"string","format":"url","maxLength":100},"instagram":{"type":"string","format":"url","maxLength":100},"linkedin":{"type":"string","format":"url","maxLength":100},"x":{"type":"string","format":"url","maxLength":100},"youtube":{"type":"string","format":"url","maxLength":100}}},"createdAt":{"type":"string","format":"date-time","description":"Timestamp of user creation","readOnly":true},"updatedAt":{"type":"string","format":"date-time","description":"Timestamp of last user update","readOnly":true}},"required":["username","email","role"]},"ValidationErrorResponse":{"type":"object","properties":{"code":{"type":"string","enum":["ValidationError"]},"errors":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ValidationErrorDetail"}}},"required":["code","errors"]},"ValidationErrorDetail":{"type":"object","properties":{"type":{"type":"string"},"value":{"type":"string"},"msg":{"type":"string"},"path":{"type":"string"},"location":{"type":"string"}}},"ErrorResponse":{"type":"object","properties":{"code":{"type":"string","description":"Application-specific error code"},"message":{"type":"string","description":"Human-readable error message"}},"required":["code","message"]}},"responses":{"BadRequestValidation":{"description":"Invalid input data provided. See errors object for details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}},"NotFound":{"description":"The specified resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ServerError":{"description":"An unexpected error occurred on the server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}
```

## Refresh Access Token

> Generates a new access token using the refresh token cookie.

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"tags":[{"name":"Authentication","description":"User authentication operations"}],"servers":[{"url":"https://blog-api.codewithsadee.com/api/v1","description":"API v1 Base Path"}],"security":[],"paths":{"/auth/refresh-token":{"post":{"tags":["Authentication"],"summary":"Refresh Access Token","description":"Generates a new access token using the refresh token cookie.","operationId":"refreshToken","parameters":[{"$ref":"#/components/parameters/RefreshTokenCookie"}],"responses":{"200":{"description":"Access token refreshed successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessTokenResponse"}}}},"400":{"$ref":"#/components/responses/BadRequestValidation"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/ServerError"}}}}},"components":{"parameters":{"RefreshTokenCookie":{"in":"cookie","name":"refreshToken","schema":{"type":"string","format":"jwt"},"required":true,"description":"HTTP-only refresh token cookie."}},"schemas":{"AccessTokenResponse":{"type":"object","properties":{"accessToken":{"type":"string","description":"JWT Access Token"}}},"ValidationErrorResponse":{"type":"object","properties":{"code":{"type":"string","enum":["ValidationError"]},"errors":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ValidationErrorDetail"}}},"required":["code","errors"]},"ValidationErrorDetail":{"type":"object","properties":{"type":{"type":"string"},"value":{"type":"string"},"msg":{"type":"string"},"path":{"type":"string"},"location":{"type":"string"}}},"ErrorResponse":{"type":"object","properties":{"code":{"type":"string","description":"Application-specific error code"},"message":{"type":"string","description":"Human-readable error message"}},"required":["code","message"]}},"responses":{"BadRequestValidation":{"description":"Invalid input data provided. See errors object for details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}},"Unauthorized":{"description":"Authentication information is missing or invalid (e.g., missing/expired token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ServerError":{"description":"An unexpected error occurred on the server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}
```

## Logout User

> Invalidates the refresh token and clears the cookie. Requires both access and refresh tokens.

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"tags":[{"name":"Authentication","description":"User authentication operations"}],"servers":[{"url":"https://blog-api.codewithsadee.com/api/v1","description":"API v1 Base Path"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT Access Token obtained via login/register/refresh"}},"parameters":{"RefreshTokenCookie":{"in":"cookie","name":"refreshToken","schema":{"type":"string","format":"jwt"},"required":true,"description":"HTTP-only refresh token cookie."}},"responses":{"BadRequestValidation":{"description":"Invalid input data provided. See errors object for details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}},"Unauthorized":{"description":"Authentication information is missing or invalid (e.g., missing/expired token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ServerError":{"description":"An unexpected error occurred on the server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"schemas":{"ValidationErrorResponse":{"type":"object","properties":{"code":{"type":"string","enum":["ValidationError"]},"errors":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ValidationErrorDetail"}}},"required":["code","errors"]},"ValidationErrorDetail":{"type":"object","properties":{"type":{"type":"string"},"value":{"type":"string"},"msg":{"type":"string"},"path":{"type":"string"},"location":{"type":"string"}}},"ErrorResponse":{"type":"object","properties":{"code":{"type":"string","description":"Application-specific error code"},"message":{"type":"string","description":"Human-readable error message"}},"required":["code","message"]}}},"paths":{"/auth/logout":{"post":{"tags":["Authentication"],"summary":"Logout User","description":"Invalidates the refresh token and clears the cookie. Requires both access and refresh tokens.","operationId":"logoutUser","parameters":[{"$ref":"#/components/parameters/RefreshTokenCookie"}],"responses":{"200":{"description":"Logout successful. Clears refreshToken cookie.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequestValidation"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/ServerError"}}}}}}
```


# Users

User management operations

## Get Current User Profile

> Retrieves the profile of the currently authenticated user.

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"tags":[{"name":"Users","description":"User management operations"}],"servers":[{"url":"https://blog-api.codewithsadee.com/api/v1","description":"API v1 Base Path"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT Access Token obtained via login/register/refresh"}},"schemas":{"User":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","description":"Unique identifier for the user","readOnly":true},"username":{"type":"string","description":"User's unique username","maxLength":20},"email":{"type":"string","format":"email","description":"User's unique email address","maxLength":50},"role":{"type":"string","enum":["admin","user"],"description":"User role","readOnly":true,"default":"user"},"firstName":{"type":"string","description":"User's first name","maxLength":20},"lastName":{"type":"string","description":"User's last name","maxLength":20},"socialLinks":{"type":"object","properties":{"website":{"type":"string","format":"url","maxLength":100},"facebook":{"type":"string","format":"url","maxLength":100},"instagram":{"type":"string","format":"url","maxLength":100},"linkedin":{"type":"string","format":"url","maxLength":100},"x":{"type":"string","format":"url","maxLength":100},"youtube":{"type":"string","format":"url","maxLength":100}}},"createdAt":{"type":"string","format":"date-time","description":"Timestamp of user creation","readOnly":true},"updatedAt":{"type":"string","format":"date-time","description":"Timestamp of last user update","readOnly":true}},"required":["username","email","role"]},"ErrorResponse":{"type":"object","properties":{"code":{"type":"string","description":"Application-specific error code"},"message":{"type":"string","description":"Human-readable error message"}},"required":["code","message"]}},"responses":{"Unauthorized":{"description":"Authentication information is missing or invalid (e.g., missing/expired token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ServerError":{"description":"An unexpected error occurred on the server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"paths":{"/users/current":{"get":{"tags":["Users"],"summary":"Get Current User Profile","description":"Retrieves the profile of the currently authenticated user.","operationId":"getCurrentUser","responses":{"200":{"description":"Current user profile data.","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/ServerError"}}}}}}
```

## Update Current User Profile

> Updates the profile of the currently authenticated user.

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"tags":[{"name":"Users","description":"User management operations"}],"servers":[{"url":"https://blog-api.codewithsadee.com/api/v1","description":"API v1 Base Path"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT Access Token obtained via login/register/refresh"}},"schemas":{"UserUpdateInput":{"type":"object","properties":{"username":{"type":"string","description":"User's unique username","maxLength":20},"email":{"type":"string","format":"email","description":"User's unique email address","maxLength":50},"password":{"type":"string","description":"New password (min 8 chars)","minLength":8,"writeOnly":true},"first_name":{"type":"string","description":"User's first name","maxLength":20},"last_name":{"type":"string","description":"User's last name","maxLength":20},"website":{"type":"string","format":"url","maxLength":100},"facebook":{"type":"string","format":"url","maxLength":100},"instagram":{"type":"string","format":"url","maxLength":100},"linkedin":{"type":"string","format":"url","maxLength":100},"x":{"type":"string","format":"url","maxLength":100},"youtube":{"type":"string","format":"url","maxLength":100}}},"User":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","description":"Unique identifier for the user","readOnly":true},"username":{"type":"string","description":"User's unique username","maxLength":20},"email":{"type":"string","format":"email","description":"User's unique email address","maxLength":50},"role":{"type":"string","enum":["admin","user"],"description":"User role","readOnly":true,"default":"user"},"firstName":{"type":"string","description":"User's first name","maxLength":20},"lastName":{"type":"string","description":"User's last name","maxLength":20},"socialLinks":{"type":"object","properties":{"website":{"type":"string","format":"url","maxLength":100},"facebook":{"type":"string","format":"url","maxLength":100},"instagram":{"type":"string","format":"url","maxLength":100},"linkedin":{"type":"string","format":"url","maxLength":100},"x":{"type":"string","format":"url","maxLength":100},"youtube":{"type":"string","format":"url","maxLength":100}}},"createdAt":{"type":"string","format":"date-time","description":"Timestamp of user creation","readOnly":true},"updatedAt":{"type":"string","format":"date-time","description":"Timestamp of last user update","readOnly":true}},"required":["username","email","role"]},"ValidationErrorResponse":{"type":"object","properties":{"code":{"type":"string","enum":["ValidationError"]},"errors":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ValidationErrorDetail"}}},"required":["code","errors"]},"ValidationErrorDetail":{"type":"object","properties":{"type":{"type":"string"},"value":{"type":"string"},"msg":{"type":"string"},"path":{"type":"string"},"location":{"type":"string"}}},"ErrorResponse":{"type":"object","properties":{"code":{"type":"string","description":"Application-specific error code"},"message":{"type":"string","description":"Human-readable error message"}},"required":["code","message"]}},"responses":{"BadRequestValidation":{"description":"Invalid input data provided. See errors object for details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}},"Unauthorized":{"description":"Authentication information is missing or invalid (e.g., missing/expired token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"NotFound":{"description":"The specified resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ServerError":{"description":"An unexpected error occurred on the server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"paths":{"/users/current":{"put":{"tags":["Users"],"summary":"Update Current User Profile","description":"Updates the profile of the currently authenticated user.","operationId":"updateCurrentUser","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserUpdateInput"}}}},"responses":{"200":{"description":"User profile updated successfully.","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"}}}}}},"400":{"$ref":"#/components/responses/BadRequestValidation"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/ServerError"}}}}}}
```

## Delete Current User Account

> Deletes the account of the currently authenticated user and their associated data.

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"tags":[{"name":"Users","description":"User management operations"}],"servers":[{"url":"https://blog-api.codewithsadee.com/api/v1","description":"API v1 Base Path"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT Access Token obtained via login/register/refresh"}},"responses":{"NoContent":{"description":"Request successful, no response body."},"Unauthorized":{"description":"Authentication information is missing or invalid (e.g., missing/expired token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ServerError":{"description":"An unexpected error occurred on the server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"schemas":{"ErrorResponse":{"type":"object","properties":{"code":{"type":"string","description":"Application-specific error code"},"message":{"type":"string","description":"Human-readable error message"}},"required":["code","message"]}}},"paths":{"/users/current":{"delete":{"tags":["Users"],"summary":"Delete Current User Account","description":"Deletes the account of the currently authenticated user and their associated data.","operationId":"deleteCurrentUser","responses":{"204":{"$ref":"#/components/responses/NoContent"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/ServerError"}}}}}}
```

## Get All Users (Admin)

> Retrieves a paginated list of all users. Admin role required.

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"tags":[{"name":"Users","description":"User management operations"}],"servers":[{"url":"https://blog-api.codewithsadee.com/api/v1","description":"API v1 Base Path"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT Access Token obtained via login/register/refresh"}},"parameters":{"LimitParam":{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":50,"default":20},"description":"Maximum number of items to return.","required":false},"OffsetParam":{"in":"query","name":"offset","schema":{"type":"integer","minimum":0,"default":0},"description":"Number of items to skip for pagination.","required":false}},"schemas":{"PaginatedUsers":{"type":"object","properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"},"users":{"type":"array","items":{"$ref":"#/components/schemas/User"}}}},"User":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","description":"Unique identifier for the user","readOnly":true},"username":{"type":"string","description":"User's unique username","maxLength":20},"email":{"type":"string","format":"email","description":"User's unique email address","maxLength":50},"role":{"type":"string","enum":["admin","user"],"description":"User role","readOnly":true,"default":"user"},"firstName":{"type":"string","description":"User's first name","maxLength":20},"lastName":{"type":"string","description":"User's last name","maxLength":20},"socialLinks":{"type":"object","properties":{"website":{"type":"string","format":"url","maxLength":100},"facebook":{"type":"string","format":"url","maxLength":100},"instagram":{"type":"string","format":"url","maxLength":100},"linkedin":{"type":"string","format":"url","maxLength":100},"x":{"type":"string","format":"url","maxLength":100},"youtube":{"type":"string","format":"url","maxLength":100}}},"createdAt":{"type":"string","format":"date-time","description":"Timestamp of user creation","readOnly":true},"updatedAt":{"type":"string","format":"date-time","description":"Timestamp of last user update","readOnly":true}},"required":["username","email","role"]},"ValidationErrorResponse":{"type":"object","properties":{"code":{"type":"string","enum":["ValidationError"]},"errors":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ValidationErrorDetail"}}},"required":["code","errors"]},"ValidationErrorDetail":{"type":"object","properties":{"type":{"type":"string"},"value":{"type":"string"},"msg":{"type":"string"},"path":{"type":"string"},"location":{"type":"string"}}},"ErrorResponse":{"type":"object","properties":{"code":{"type":"string","description":"Application-specific error code"},"message":{"type":"string","description":"Human-readable error message"}},"required":["code","message"]}},"responses":{"BadRequestValidation":{"description":"Invalid input data provided. See errors object for details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}},"Unauthorized":{"description":"Authentication information is missing or invalid (e.g., missing/expired token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"Forbidden":{"description":"Access denied due to insufficient permissions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ServerError":{"description":"An unexpected error occurred on the server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"paths":{"/users/":{"get":{"tags":["Users"],"summary":"Get All Users (Admin)","description":"Retrieves a paginated list of all users. Admin role required.","operationId":"getAllUsers","parameters":[{"$ref":"#/components/parameters/LimitParam"},{"$ref":"#/components/parameters/OffsetParam"}],"responses":{"200":{"description":"A list of users.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedUsers"}}}},"400":{"$ref":"#/components/responses/BadRequestValidation"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"500":{"$ref":"#/components/responses/ServerError"}}}}}}
```

## Get User by ID (Admin)

> Retrieves profile information for a specific user. Admin role required.

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"tags":[{"name":"Users","description":"User management operations"}],"servers":[{"url":"https://blog-api.codewithsadee.com/api/v1","description":"API v1 Base Path"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT Access Token obtained via login/register/refresh"}},"parameters":{"UserIdParam":{"in":"path","name":"userId","schema":{"type":"string","format":"objectid"},"required":true,"description":"ID of the user."}},"schemas":{"User":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","description":"Unique identifier for the user","readOnly":true},"username":{"type":"string","description":"User's unique username","maxLength":20},"email":{"type":"string","format":"email","description":"User's unique email address","maxLength":50},"role":{"type":"string","enum":["admin","user"],"description":"User role","readOnly":true,"default":"user"},"firstName":{"type":"string","description":"User's first name","maxLength":20},"lastName":{"type":"string","description":"User's last name","maxLength":20},"socialLinks":{"type":"object","properties":{"website":{"type":"string","format":"url","maxLength":100},"facebook":{"type":"string","format":"url","maxLength":100},"instagram":{"type":"string","format":"url","maxLength":100},"linkedin":{"type":"string","format":"url","maxLength":100},"x":{"type":"string","format":"url","maxLength":100},"youtube":{"type":"string","format":"url","maxLength":100}}},"createdAt":{"type":"string","format":"date-time","description":"Timestamp of user creation","readOnly":true},"updatedAt":{"type":"string","format":"date-time","description":"Timestamp of last user update","readOnly":true}},"required":["username","email","role"]},"ValidationErrorResponse":{"type":"object","properties":{"code":{"type":"string","enum":["ValidationError"]},"errors":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ValidationErrorDetail"}}},"required":["code","errors"]},"ValidationErrorDetail":{"type":"object","properties":{"type":{"type":"string"},"value":{"type":"string"},"msg":{"type":"string"},"path":{"type":"string"},"location":{"type":"string"}}},"ErrorResponse":{"type":"object","properties":{"code":{"type":"string","description":"Application-specific error code"},"message":{"type":"string","description":"Human-readable error message"}},"required":["code","message"]}},"responses":{"BadRequestValidation":{"description":"Invalid input data provided. See errors object for details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}},"Unauthorized":{"description":"Authentication information is missing or invalid (e.g., missing/expired token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"Forbidden":{"description":"Access denied due to insufficient permissions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"NotFound":{"description":"The specified resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ServerError":{"description":"An unexpected error occurred on the server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"paths":{"/users/{userId}":{"get":{"tags":["Users"],"summary":"Get User by ID (Admin)","description":"Retrieves profile information for a specific user. Admin role required.","operationId":"getUserById","parameters":[{"$ref":"#/components/parameters/UserIdParam"}],"responses":{"200":{"description":"Specific user profile data.","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"}}}}}},"400":{"$ref":"#/components/responses/BadRequestValidation"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/ServerError"}}}}}}
```

## Delete User by ID (Admin)

> Deletes a specific user account and their associated data. Admin role required.

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"tags":[{"name":"Users","description":"User management operations"}],"servers":[{"url":"https://blog-api.codewithsadee.com/api/v1","description":"API v1 Base Path"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT Access Token obtained via login/register/refresh"}},"parameters":{"UserIdParam":{"in":"path","name":"userId","schema":{"type":"string","format":"objectid"},"required":true,"description":"ID of the user."}},"responses":{"NoContent":{"description":"Request successful, no response body."},"BadRequestValidation":{"description":"Invalid input data provided. See errors object for details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}},"Unauthorized":{"description":"Authentication information is missing or invalid (e.g., missing/expired token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"Forbidden":{"description":"Access denied due to insufficient permissions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"NotFound":{"description":"The specified resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ServerError":{"description":"An unexpected error occurred on the server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"schemas":{"ValidationErrorResponse":{"type":"object","properties":{"code":{"type":"string","enum":["ValidationError"]},"errors":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ValidationErrorDetail"}}},"required":["code","errors"]},"ValidationErrorDetail":{"type":"object","properties":{"type":{"type":"string"},"value":{"type":"string"},"msg":{"type":"string"},"path":{"type":"string"},"location":{"type":"string"}}},"ErrorResponse":{"type":"object","properties":{"code":{"type":"string","description":"Application-specific error code"},"message":{"type":"string","description":"Human-readable error message"}},"required":["code","message"]}}},"paths":{"/users/{userId}":{"delete":{"tags":["Users"],"summary":"Delete User by ID (Admin)","description":"Deletes a specific user account and their associated data. Admin role required.","operationId":"deleteUserById","parameters":[{"$ref":"#/components/parameters/UserIdParam"}],"responses":{"204":{"$ref":"#/components/responses/NoContent"},"400":{"$ref":"#/components/responses/BadRequestValidation"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/ServerError"}}}}}}
```


# Blogs

Blog post management

## Get All Blogs

> Retrieves a paginated list of blogs. Admins see all; users see only 'published'. Sorted by creation date descending.

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"tags":[{"name":"Blogs","description":"Blog post management"}],"servers":[{"url":"https://blog-api.codewithsadee.com/api/v1","description":"API v1 Base Path"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT Access Token obtained via login/register/refresh"}},"parameters":{"LimitParam":{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":50,"default":20},"description":"Maximum number of items to return.","required":false},"OffsetParam":{"in":"query","name":"offset","schema":{"type":"integer","minimum":0,"default":0},"description":"Number of items to skip for pagination.","required":false}},"schemas":{"PaginatedBlogs":{"type":"object","properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"},"blogs":{"type":"array","items":{"$ref":"#/components/schemas/Blog"}}}},"Blog":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","readOnly":true},"title":{"type":"string","maxLength":180},"slug":{"type":"string","readOnly":true,"description":"URL-friendly identifier, automatically generated"},"content":{"type":"string","description":"HTML content of the blog post"},"banner":{"type":"object","properties":{"url":{"type":"string","format":"url","description":"URL of the banner image","readOnly":true},"width":{"type":"integer","description":"Width of the banner image","readOnly":true},"height":{"type":"integer","description":"Height of the banner image","readOnly":true}}},"author":{"$ref":"#/components/schemas/User","readOnly":true},"viewsCount":{"type":"integer","default":0,"readOnly":true},"likesCount":{"type":"integer","default":0,"readOnly":true},"commentsCount":{"type":"integer","default":0,"readOnly":true},"status":{"type":"string","enum":["draft","published"],"default":"draft"},"publishedAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["title","content","banner","author","status"]},"User":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","description":"Unique identifier for the user","readOnly":true},"username":{"type":"string","description":"User's unique username","maxLength":20},"email":{"type":"string","format":"email","description":"User's unique email address","maxLength":50},"role":{"type":"string","enum":["admin","user"],"description":"User role","readOnly":true,"default":"user"},"firstName":{"type":"string","description":"User's first name","maxLength":20},"lastName":{"type":"string","description":"User's last name","maxLength":20},"socialLinks":{"type":"object","properties":{"website":{"type":"string","format":"url","maxLength":100},"facebook":{"type":"string","format":"url","maxLength":100},"instagram":{"type":"string","format":"url","maxLength":100},"linkedin":{"type":"string","format":"url","maxLength":100},"x":{"type":"string","format":"url","maxLength":100},"youtube":{"type":"string","format":"url","maxLength":100}}},"createdAt":{"type":"string","format":"date-time","description":"Timestamp of user creation","readOnly":true},"updatedAt":{"type":"string","format":"date-time","description":"Timestamp of last user update","readOnly":true}},"required":["username","email","role"]},"ValidationErrorResponse":{"type":"object","properties":{"code":{"type":"string","enum":["ValidationError"]},"errors":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ValidationErrorDetail"}}},"required":["code","errors"]},"ValidationErrorDetail":{"type":"object","properties":{"type":{"type":"string"},"value":{"type":"string"},"msg":{"type":"string"},"path":{"type":"string"},"location":{"type":"string"}}},"ErrorResponse":{"type":"object","properties":{"code":{"type":"string","description":"Application-specific error code"},"message":{"type":"string","description":"Human-readable error message"}},"required":["code","message"]}},"responses":{"BadRequestValidation":{"description":"Invalid input data provided. See errors object for details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}},"Unauthorized":{"description":"Authentication information is missing or invalid (e.g., missing/expired token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ServerError":{"description":"An unexpected error occurred on the server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"paths":{"/blogs/":{"get":{"tags":["Blogs"],"summary":"Get All Blogs","description":"Retrieves a paginated list of blogs. Admins see all; users see only 'published'. Sorted by creation date descending.","operationId":"getAllBlogs","parameters":[{"$ref":"#/components/parameters/LimitParam"},{"$ref":"#/components/parameters/OffsetParam"}],"responses":{"200":{"description":"A list of blogs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedBlogs"}}}},"400":{"$ref":"#/components/responses/BadRequestValidation"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/ServerError"}}}}}}
```

## Create Blog Post (Admin)

> Creates a new blog post. Requires banner image upload. Admin role required.

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"tags":[{"name":"Blogs","description":"Blog post management"}],"servers":[{"url":"https://blog-api.codewithsadee.com/api/v1","description":"API v1 Base Path"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT Access Token obtained via login/register/refresh"}},"schemas":{"BlogInput":{"type":"object","properties":{"title":{"type":"string","maxLength":180},"content":{"type":"string","description":"HTML content"},"status":{"type":"string","enum":["draft","published"],"default":"draft"},"banner_image":{"type":"string","format":"binary","description":"Banner image file (png/jpg/webp, max 2MB)"}},"required":["title","content","banner_image"]},"Blog":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","readOnly":true},"title":{"type":"string","maxLength":180},"slug":{"type":"string","readOnly":true,"description":"URL-friendly identifier, automatically generated"},"content":{"type":"string","description":"HTML content of the blog post"},"banner":{"type":"object","properties":{"url":{"type":"string","format":"url","description":"URL of the banner image","readOnly":true},"width":{"type":"integer","description":"Width of the banner image","readOnly":true},"height":{"type":"integer","description":"Height of the banner image","readOnly":true}}},"author":{"$ref":"#/components/schemas/User","readOnly":true},"viewsCount":{"type":"integer","default":0,"readOnly":true},"likesCount":{"type":"integer","default":0,"readOnly":true},"commentsCount":{"type":"integer","default":0,"readOnly":true},"status":{"type":"string","enum":["draft","published"],"default":"draft"},"publishedAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["title","content","banner","author","status"]},"User":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","description":"Unique identifier for the user","readOnly":true},"username":{"type":"string","description":"User's unique username","maxLength":20},"email":{"type":"string","format":"email","description":"User's unique email address","maxLength":50},"role":{"type":"string","enum":["admin","user"],"description":"User role","readOnly":true,"default":"user"},"firstName":{"type":"string","description":"User's first name","maxLength":20},"lastName":{"type":"string","description":"User's last name","maxLength":20},"socialLinks":{"type":"object","properties":{"website":{"type":"string","format":"url","maxLength":100},"facebook":{"type":"string","format":"url","maxLength":100},"instagram":{"type":"string","format":"url","maxLength":100},"linkedin":{"type":"string","format":"url","maxLength":100},"x":{"type":"string","format":"url","maxLength":100},"youtube":{"type":"string","format":"url","maxLength":100}}},"createdAt":{"type":"string","format":"date-time","description":"Timestamp of user creation","readOnly":true},"updatedAt":{"type":"string","format":"date-time","description":"Timestamp of last user update","readOnly":true}},"required":["username","email","role"]},"ValidationErrorResponse":{"type":"object","properties":{"code":{"type":"string","enum":["ValidationError"]},"errors":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ValidationErrorDetail"}}},"required":["code","errors"]},"ValidationErrorDetail":{"type":"object","properties":{"type":{"type":"string"},"value":{"type":"string"},"msg":{"type":"string"},"path":{"type":"string"},"location":{"type":"string"}}},"ErrorResponse":{"type":"object","properties":{"code":{"type":"string","description":"Application-specific error code"},"message":{"type":"string","description":"Human-readable error message"}},"required":["code","message"]}},"responses":{"BadRequestValidation":{"description":"Invalid input data provided. See errors object for details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}},"Unauthorized":{"description":"Authentication information is missing or invalid (e.g., missing/expired token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"Forbidden":{"description":"Access denied due to insufficient permissions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ServerError":{"description":"An unexpected error occurred on the server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"paths":{"/blogs/":{"post":{"tags":["Blogs"],"summary":"Create Blog Post (Admin)","description":"Creates a new blog post. Requires banner image upload. Admin role required.","operationId":"createBlog","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/BlogInput"},"encoding":{"banner_image":{"contentType":"image/png, image/jpeg, image/webp"}}}}},"responses":{"201":{"description":"Blog post created successfully.","content":{"application/json":{"schema":{"type":"object","properties":{"blog":{"$ref":"#/components/schemas/Blog"}}}}}},"400":{"$ref":"#/components/responses/BadRequestValidation"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"413":{"description":"Uploaded file exceeds size limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"$ref":"#/components/responses/ServerError"}}}}}}
```

## Get Blogs by User

> Retrieves a paginated list of blogs by a specific user. Admins see all; users see only 'published'. Sorted by creation date descending.

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"tags":[{"name":"Blogs","description":"Blog post management"}],"servers":[{"url":"https://blog-api.codewithsadee.com/api/v1","description":"API v1 Base Path"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT Access Token obtained via login/register/refresh"}},"parameters":{"UserIdParam":{"in":"path","name":"userId","schema":{"type":"string","format":"objectid"},"required":true,"description":"ID of the user."},"LimitParam":{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":50,"default":20},"description":"Maximum number of items to return.","required":false},"OffsetParam":{"in":"query","name":"offset","schema":{"type":"integer","minimum":0,"default":0},"description":"Number of items to skip for pagination.","required":false}},"schemas":{"PaginatedBlogs":{"type":"object","properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"},"blogs":{"type":"array","items":{"$ref":"#/components/schemas/Blog"}}}},"Blog":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","readOnly":true},"title":{"type":"string","maxLength":180},"slug":{"type":"string","readOnly":true,"description":"URL-friendly identifier, automatically generated"},"content":{"type":"string","description":"HTML content of the blog post"},"banner":{"type":"object","properties":{"url":{"type":"string","format":"url","description":"URL of the banner image","readOnly":true},"width":{"type":"integer","description":"Width of the banner image","readOnly":true},"height":{"type":"integer","description":"Height of the banner image","readOnly":true}}},"author":{"$ref":"#/components/schemas/User","readOnly":true},"viewsCount":{"type":"integer","default":0,"readOnly":true},"likesCount":{"type":"integer","default":0,"readOnly":true},"commentsCount":{"type":"integer","default":0,"readOnly":true},"status":{"type":"string","enum":["draft","published"],"default":"draft"},"publishedAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["title","content","banner","author","status"]},"User":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","description":"Unique identifier for the user","readOnly":true},"username":{"type":"string","description":"User's unique username","maxLength":20},"email":{"type":"string","format":"email","description":"User's unique email address","maxLength":50},"role":{"type":"string","enum":["admin","user"],"description":"User role","readOnly":true,"default":"user"},"firstName":{"type":"string","description":"User's first name","maxLength":20},"lastName":{"type":"string","description":"User's last name","maxLength":20},"socialLinks":{"type":"object","properties":{"website":{"type":"string","format":"url","maxLength":100},"facebook":{"type":"string","format":"url","maxLength":100},"instagram":{"type":"string","format":"url","maxLength":100},"linkedin":{"type":"string","format":"url","maxLength":100},"x":{"type":"string","format":"url","maxLength":100},"youtube":{"type":"string","format":"url","maxLength":100}}},"createdAt":{"type":"string","format":"date-time","description":"Timestamp of user creation","readOnly":true},"updatedAt":{"type":"string","format":"date-time","description":"Timestamp of last user update","readOnly":true}},"required":["username","email","role"]},"ValidationErrorResponse":{"type":"object","properties":{"code":{"type":"string","enum":["ValidationError"]},"errors":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ValidationErrorDetail"}}},"required":["code","errors"]},"ValidationErrorDetail":{"type":"object","properties":{"type":{"type":"string"},"value":{"type":"string"},"msg":{"type":"string"},"path":{"type":"string"},"location":{"type":"string"}}},"ErrorResponse":{"type":"object","properties":{"code":{"type":"string","description":"Application-specific error code"},"message":{"type":"string","description":"Human-readable error message"}},"required":["code","message"]}},"responses":{"BadRequestValidation":{"description":"Invalid input data provided. See errors object for details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}},"Unauthorized":{"description":"Authentication information is missing or invalid (e.g., missing/expired token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ServerError":{"description":"An unexpected error occurred on the server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"paths":{"/blogs/user/{userId}":{"get":{"tags":["Blogs"],"summary":"Get Blogs by User","description":"Retrieves a paginated list of blogs by a specific user. Admins see all; users see only 'published'. Sorted by creation date descending.","operationId":"getBlogsByUser","parameters":[{"$ref":"#/components/parameters/UserIdParam"},{"$ref":"#/components/parameters/LimitParam"},{"$ref":"#/components/parameters/OffsetParam"}],"responses":{"200":{"description":"A list of blogs by the specified user.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedBlogs"}}}},"400":{"$ref":"#/components/responses/BadRequestValidation"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/ServerError"}}}}}}
```

## Get Blog by Slug

> Retrieves a single blog post by its unique slug. Regular users cannot view 'draft' posts.

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"tags":[{"name":"Blogs","description":"Blog post management"}],"servers":[{"url":"https://blog-api.codewithsadee.com/api/v1","description":"API v1 Base Path"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT Access Token obtained via login/register/refresh"}},"parameters":{"BlogSlugParam":{"in":"path","name":"slug","schema":{"type":"string"},"required":true,"description":"Slug of the blog post."}},"schemas":{"Blog":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","readOnly":true},"title":{"type":"string","maxLength":180},"slug":{"type":"string","readOnly":true,"description":"URL-friendly identifier, automatically generated"},"content":{"type":"string","description":"HTML content of the blog post"},"banner":{"type":"object","properties":{"url":{"type":"string","format":"url","description":"URL of the banner image","readOnly":true},"width":{"type":"integer","description":"Width of the banner image","readOnly":true},"height":{"type":"integer","description":"Height of the banner image","readOnly":true}}},"author":{"$ref":"#/components/schemas/User","readOnly":true},"viewsCount":{"type":"integer","default":0,"readOnly":true},"likesCount":{"type":"integer","default":0,"readOnly":true},"commentsCount":{"type":"integer","default":0,"readOnly":true},"status":{"type":"string","enum":["draft","published"],"default":"draft"},"publishedAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["title","content","banner","author","status"]},"User":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","description":"Unique identifier for the user","readOnly":true},"username":{"type":"string","description":"User's unique username","maxLength":20},"email":{"type":"string","format":"email","description":"User's unique email address","maxLength":50},"role":{"type":"string","enum":["admin","user"],"description":"User role","readOnly":true,"default":"user"},"firstName":{"type":"string","description":"User's first name","maxLength":20},"lastName":{"type":"string","description":"User's last name","maxLength":20},"socialLinks":{"type":"object","properties":{"website":{"type":"string","format":"url","maxLength":100},"facebook":{"type":"string","format":"url","maxLength":100},"instagram":{"type":"string","format":"url","maxLength":100},"linkedin":{"type":"string","format":"url","maxLength":100},"x":{"type":"string","format":"url","maxLength":100},"youtube":{"type":"string","format":"url","maxLength":100}}},"createdAt":{"type":"string","format":"date-time","description":"Timestamp of user creation","readOnly":true},"updatedAt":{"type":"string","format":"date-time","description":"Timestamp of last user update","readOnly":true}},"required":["username","email","role"]},"ValidationErrorResponse":{"type":"object","properties":{"code":{"type":"string","enum":["ValidationError"]},"errors":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ValidationErrorDetail"}}},"required":["code","errors"]},"ValidationErrorDetail":{"type":"object","properties":{"type":{"type":"string"},"value":{"type":"string"},"msg":{"type":"string"},"path":{"type":"string"},"location":{"type":"string"}}},"ErrorResponse":{"type":"object","properties":{"code":{"type":"string","description":"Application-specific error code"},"message":{"type":"string","description":"Human-readable error message"}},"required":["code","message"]}},"responses":{"BadRequestValidation":{"description":"Invalid input data provided. See errors object for details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}},"Unauthorized":{"description":"Authentication information is missing or invalid (e.g., missing/expired token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"Forbidden":{"description":"Access denied due to insufficient permissions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"NotFound":{"description":"The specified resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ServerError":{"description":"An unexpected error occurred on the server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"paths":{"/blogs/{slug}":{"get":{"tags":["Blogs"],"summary":"Get Blog by Slug","description":"Retrieves a single blog post by its unique slug. Regular users cannot view 'draft' posts.","operationId":"getBlogBySlug","parameters":[{"$ref":"#/components/parameters/BlogSlugParam"}],"responses":{"200":{"description":"The requested blog post.","content":{"application/json":{"schema":{"type":"object","properties":{"blog":{"$ref":"#/components/schemas/Blog"}}}}}},"400":{"$ref":"#/components/responses/BadRequestValidation"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/ServerError"}}}}}}
```

## Update Blog Post (Admin)

> Updates an existing blog post. Banner update optional. Admin role required (route security).

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"tags":[{"name":"Blogs","description":"Blog post management"}],"servers":[{"url":"https://blog-api.codewithsadee.com/api/v1","description":"API v1 Base Path"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT Access Token obtained via login/register/refresh"}},"parameters":{"BlogIdParam":{"in":"path","name":"blogId","schema":{"type":"string","format":"objectid"},"required":true,"description":"ID of the blog post."}},"schemas":{"BlogUpdateInput":{"type":"object","properties":{"title":{"type":"string","maxLength":180},"content":{"type":"string","description":"HTML content"},"status":{"type":"string","enum":["draft","published"]},"banner_image":{"type":"string","format":"binary","description":"New banner image file (png/jpg/webp, max 2MB)"}}},"Blog":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","readOnly":true},"title":{"type":"string","maxLength":180},"slug":{"type":"string","readOnly":true,"description":"URL-friendly identifier, automatically generated"},"content":{"type":"string","description":"HTML content of the blog post"},"banner":{"type":"object","properties":{"url":{"type":"string","format":"url","description":"URL of the banner image","readOnly":true},"width":{"type":"integer","description":"Width of the banner image","readOnly":true},"height":{"type":"integer","description":"Height of the banner image","readOnly":true}}},"author":{"$ref":"#/components/schemas/User","readOnly":true},"viewsCount":{"type":"integer","default":0,"readOnly":true},"likesCount":{"type":"integer","default":0,"readOnly":true},"commentsCount":{"type":"integer","default":0,"readOnly":true},"status":{"type":"string","enum":["draft","published"],"default":"draft"},"publishedAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["title","content","banner","author","status"]},"User":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","description":"Unique identifier for the user","readOnly":true},"username":{"type":"string","description":"User's unique username","maxLength":20},"email":{"type":"string","format":"email","description":"User's unique email address","maxLength":50},"role":{"type":"string","enum":["admin","user"],"description":"User role","readOnly":true,"default":"user"},"firstName":{"type":"string","description":"User's first name","maxLength":20},"lastName":{"type":"string","description":"User's last name","maxLength":20},"socialLinks":{"type":"object","properties":{"website":{"type":"string","format":"url","maxLength":100},"facebook":{"type":"string","format":"url","maxLength":100},"instagram":{"type":"string","format":"url","maxLength":100},"linkedin":{"type":"string","format":"url","maxLength":100},"x":{"type":"string","format":"url","maxLength":100},"youtube":{"type":"string","format":"url","maxLength":100}}},"createdAt":{"type":"string","format":"date-time","description":"Timestamp of user creation","readOnly":true},"updatedAt":{"type":"string","format":"date-time","description":"Timestamp of last user update","readOnly":true}},"required":["username","email","role"]},"ValidationErrorResponse":{"type":"object","properties":{"code":{"type":"string","enum":["ValidationError"]},"errors":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ValidationErrorDetail"}}},"required":["code","errors"]},"ValidationErrorDetail":{"type":"object","properties":{"type":{"type":"string"},"value":{"type":"string"},"msg":{"type":"string"},"path":{"type":"string"},"location":{"type":"string"}}},"ErrorResponse":{"type":"object","properties":{"code":{"type":"string","description":"Application-specific error code"},"message":{"type":"string","description":"Human-readable error message"}},"required":["code","message"]}},"responses":{"BadRequestValidation":{"description":"Invalid input data provided. See errors object for details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}},"Unauthorized":{"description":"Authentication information is missing or invalid (e.g., missing/expired token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"Forbidden":{"description":"Access denied due to insufficient permissions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"NotFound":{"description":"The specified resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ServerError":{"description":"An unexpected error occurred on the server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"paths":{"/blogs/{blogId}":{"put":{"tags":["Blogs"],"summary":"Update Blog Post (Admin)","description":"Updates an existing blog post. Banner update optional. Admin role required (route security).","operationId":"updateBlog","parameters":[{"$ref":"#/components/parameters/BlogIdParam"}],"requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/BlogUpdateInput"},"encoding":{"banner_image":{"contentType":"image/png, image/jpeg, image/webp"}}}}},"responses":{"200":{"description":"Blog post updated successfully.","content":{"application/json":{"schema":{"type":"object","properties":{"blog":{"$ref":"#/components/schemas/Blog"}}}}}},"400":{"$ref":"#/components/responses/BadRequestValidation"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"description":"Uploaded file exceeds size limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"$ref":"#/components/responses/ServerError"}}}}}}
```

## Delete Blog Post (Admin)

> Deletes a blog post by ID. Also removes banner. Admin role required (route security).

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"tags":[{"name":"Blogs","description":"Blog post management"}],"servers":[{"url":"https://blog-api.codewithsadee.com/api/v1","description":"API v1 Base Path"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT Access Token obtained via login/register/refresh"}},"parameters":{"BlogIdParam":{"in":"path","name":"blogId","schema":{"type":"string","format":"objectid"},"required":true,"description":"ID of the blog post."}},"responses":{"NoContent":{"description":"Request successful, no response body."},"Unauthorized":{"description":"Authentication information is missing or invalid (e.g., missing/expired token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"Forbidden":{"description":"Access denied due to insufficient permissions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"NotFound":{"description":"The specified resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ServerError":{"description":"An unexpected error occurred on the server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"schemas":{"ErrorResponse":{"type":"object","properties":{"code":{"type":"string","description":"Application-specific error code"},"message":{"type":"string","description":"Human-readable error message"}},"required":["code","message"]}}},"paths":{"/blogs/{blogId}":{"delete":{"tags":["Blogs"],"summary":"Delete Blog Post (Admin)","description":"Deletes a blog post by ID. Also removes banner. Admin role required (route security).","operationId":"deleteBlog","parameters":[{"$ref":"#/components/parameters/BlogIdParam"}],"responses":{"204":{"$ref":"#/components/responses/NoContent"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/ServerError"}}}}}}
```


# Likes

Liking/Unliking operations

## Like Blog Post

> Adds a like to a specific blog post. Increments likes count.

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"tags":[{"name":"Likes","description":"Liking/Unliking operations"}],"servers":[{"url":"https://blog-api.codewithsadee.com/api/v1","description":"API v1 Base Path"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT Access Token obtained via login/register/refresh"}},"parameters":{"BlogIdParam":{"in":"path","name":"blogId","schema":{"type":"string","format":"objectid"},"required":true,"description":"ID of the blog post."}},"schemas":{"ErrorResponse":{"type":"object","properties":{"code":{"type":"string","description":"Application-specific error code"},"message":{"type":"string","description":"Human-readable error message"}},"required":["code","message"]}},"responses":{"Unauthorized":{"description":"Authentication information is missing or invalid (e.g., missing/expired token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"NotFound":{"description":"The specified resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ServerError":{"description":"An unexpected error occurred on the server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"paths":{"/likes/blog/{blogId}":{"post":{"tags":["Likes"],"summary":"Like Blog Post","description":"Adds a like to a specific blog post. Increments likes count.","operationId":"likeBlog","parameters":[{"$ref":"#/components/parameters/BlogIdParam"}],"responses":{"200":{"description":"Blog liked successfully. Returns new likes count.","content":{"application/json":{"schema":{"type":"object","properties":{"likesCount":{"type":"integer"}}}}}},"400":{"description":"User has already liked this blog.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/ServerError"}}}}}}
```

## Unlike Blog Post

> Removes a like from a specific blog post. Decrements likes count.

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"tags":[{"name":"Likes","description":"Liking/Unliking operations"}],"servers":[{"url":"https://blog-api.codewithsadee.com/api/v1","description":"API v1 Base Path"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT Access Token obtained via login/register/refresh"}},"parameters":{"BlogIdParam":{"in":"path","name":"blogId","schema":{"type":"string","format":"objectid"},"required":true,"description":"ID of the blog post."}},"responses":{"NoContent":{"description":"Request successful, no response body."},"Unauthorized":{"description":"Authentication information is missing or invalid (e.g., missing/expired token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"NotFound":{"description":"The specified resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ServerError":{"description":"An unexpected error occurred on the server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"schemas":{"ErrorResponse":{"type":"object","properties":{"code":{"type":"string","description":"Application-specific error code"},"message":{"type":"string","description":"Human-readable error message"}},"required":["code","message"]}}},"paths":{"/likes/blog/{blogId}":{"delete":{"tags":["Likes"],"summary":"Unlike Blog Post","description":"Removes a like from a specific blog post. Decrements likes count.","operationId":"unlikeBlog","parameters":[{"$ref":"#/components/parameters/BlogIdParam"}],"responses":{"204":{"$ref":"#/components/responses/NoContent"},"400":{"description":"User has not liked this blog previously.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/ServerError"}}}}}}
```


# Comments

Comment management

## Get Comments by Blog

> Retrieves all comments associated with a specific blog post.

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"tags":[{"name":"Comments","description":"Comment management"}],"servers":[{"url":"https://blog-api.codewithsadee.com/api/v1","description":"API v1 Base Path"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT Access Token obtained via login/register/refresh"}},"parameters":{"BlogIdParam":{"in":"path","name":"blogId","schema":{"type":"string","format":"objectid"},"required":true,"description":"ID of the blog post."}},"schemas":{"Comment":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","readOnly":true},"blogId":{"type":"string","format":"objectid","description":"ID of the associated blog post"},"userId":{"type":"string","format":"objectid","description":"ID of the user who commented","readOnly":true},"content":{"type":"string","maxLength":1000,"description":"The comment text"},"likesCount":{"type":"integer","default":0,"readOnly":true},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["blogId","userId","content"]},"ErrorResponse":{"type":"object","properties":{"code":{"type":"string","description":"Application-specific error code"},"message":{"type":"string","description":"Human-readable error message"}},"required":["code","message"]}},"responses":{"Unauthorized":{"description":"Authentication information is missing or invalid (e.g., missing/expired token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"NotFound":{"description":"The specified resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ServerError":{"description":"An unexpected error occurred on the server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"paths":{"/comments/blog/{blogId}":{"get":{"tags":["Comments"],"summary":"Get Comments by Blog","description":"Retrieves all comments associated with a specific blog post.","operationId":"getCommentsByBlog","parameters":[{"$ref":"#/components/parameters/BlogIdParam"}],"responses":{"200":{"description":"A list of comments for the blog.","content":{"application/json":{"schema":{"type":"object","properties":{"comments":{"type":"array","items":{"$ref":"#/components/schemas/Comment"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/ServerError"}}}}}}
```

## Create Comment

> Adds a new comment to a specific blog post. Increments comments count.

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"tags":[{"name":"Comments","description":"Comment management"}],"servers":[{"url":"https://blog-api.codewithsadee.com/api/v1","description":"API v1 Base Path"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT Access Token obtained via login/register/refresh"}},"parameters":{"BlogIdParam":{"in":"path","name":"blogId","schema":{"type":"string","format":"objectid"},"required":true,"description":"ID of the blog post."}},"schemas":{"CommentInput":{"type":"object","properties":{"content":{"type":"string","maxLength":1000,"description":"The comment text"}},"required":["content"]},"Comment":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","readOnly":true},"blogId":{"type":"string","format":"objectid","description":"ID of the associated blog post"},"userId":{"type":"string","format":"objectid","description":"ID of the user who commented","readOnly":true},"content":{"type":"string","maxLength":1000,"description":"The comment text"},"likesCount":{"type":"integer","default":0,"readOnly":true},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["blogId","userId","content"]},"ValidationErrorResponse":{"type":"object","properties":{"code":{"type":"string","enum":["ValidationError"]},"errors":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ValidationErrorDetail"}}},"required":["code","errors"]},"ValidationErrorDetail":{"type":"object","properties":{"type":{"type":"string"},"value":{"type":"string"},"msg":{"type":"string"},"path":{"type":"string"},"location":{"type":"string"}}},"ErrorResponse":{"type":"object","properties":{"code":{"type":"string","description":"Application-specific error code"},"message":{"type":"string","description":"Human-readable error message"}},"required":["code","message"]}},"responses":{"BadRequestValidation":{"description":"Invalid input data provided. See errors object for details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}},"Unauthorized":{"description":"Authentication information is missing or invalid (e.g., missing/expired token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"NotFound":{"description":"The specified resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ServerError":{"description":"An unexpected error occurred on the server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"paths":{"/comments/blog/{blogId}":{"post":{"tags":["Comments"],"summary":"Create Comment","description":"Adds a new comment to a specific blog post. Increments comments count.","operationId":"createComment","parameters":[{"$ref":"#/components/parameters/BlogIdParam"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentInput"}}}},"responses":{"201":{"description":"Comment created successfully.","content":{"application/json":{"schema":{"type":"object","properties":{"comment":{"$ref":"#/components/schemas/Comment"}}}}}},"400":{"$ref":"#/components/responses/BadRequestValidation"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/ServerError"}}}}}}
```

## Delete Comment

> Deletes a specific comment. Requires user to be author or admin. Decrements comments count.

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"tags":[{"name":"Comments","description":"Comment management"}],"servers":[{"url":"https://blog-api.codewithsadee.com/api/v1","description":"API v1 Base Path"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT Access Token obtained via login/register/refresh"}},"parameters":{"CommentIdParam":{"in":"path","name":"commentId","schema":{"type":"string","format":"objectid"},"required":true,"description":"ID of the comment."}},"responses":{"NoContent":{"description":"Request successful, no response body."},"Unauthorized":{"description":"Authentication information is missing or invalid (e.g., missing/expired token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"Forbidden":{"description":"Access denied due to insufficient permissions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"NotFound":{"description":"The specified resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ServerError":{"description":"An unexpected error occurred on the server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"schemas":{"ErrorResponse":{"type":"object","properties":{"code":{"type":"string","description":"Application-specific error code"},"message":{"type":"string","description":"Human-readable error message"}},"required":["code","message"]}}},"paths":{"/comments/{commentId}":{"delete":{"tags":["Comments"],"summary":"Delete Comment","description":"Deletes a specific comment. Requires user to be author or admin. Decrements comments count.","operationId":"deleteComment","parameters":[{"$ref":"#/components/parameters/CommentIdParam"}],"responses":{"204":{"$ref":"#/components/responses/NoContent"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/ServerError"}}}}}}
```


# Models

## The User object

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"components":{"schemas":{"User":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","description":"Unique identifier for the user","readOnly":true},"username":{"type":"string","description":"User's unique username","maxLength":20},"email":{"type":"string","format":"email","description":"User's unique email address","maxLength":50},"role":{"type":"string","enum":["admin","user"],"description":"User role","readOnly":true,"default":"user"},"firstName":{"type":"string","description":"User's first name","maxLength":20},"lastName":{"type":"string","description":"User's last name","maxLength":20},"socialLinks":{"type":"object","properties":{"website":{"type":"string","format":"url","maxLength":100},"facebook":{"type":"string","format":"url","maxLength":100},"instagram":{"type":"string","format":"url","maxLength":100},"linkedin":{"type":"string","format":"url","maxLength":100},"x":{"type":"string","format":"url","maxLength":100},"youtube":{"type":"string","format":"url","maxLength":100}}},"createdAt":{"type":"string","format":"date-time","description":"Timestamp of user creation","readOnly":true},"updatedAt":{"type":"string","format":"date-time","description":"Timestamp of last user update","readOnly":true}},"required":["username","email","role"]}}}}
```

## The UserInputRequired object

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"components":{"schemas":{"UserInputRequired":{"type":"object","properties":{"email":{"type":"string","format":"email","description":"User's email address","maxLength":50},"password":{"type":"string","description":"User's password","minLength":8,"writeOnly":true},"role":{"type":"string","enum":["admin","user"],"description":"User role (optional for registration)"}},"required":["email","password"]}}}}
```

## The UserUpdateInput object

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"components":{"schemas":{"UserUpdateInput":{"type":"object","properties":{"username":{"type":"string","description":"User's unique username","maxLength":20},"email":{"type":"string","format":"email","description":"User's unique email address","maxLength":50},"password":{"type":"string","description":"New password (min 8 chars)","minLength":8,"writeOnly":true},"first_name":{"type":"string","description":"User's first name","maxLength":20},"last_name":{"type":"string","description":"User's last name","maxLength":20},"website":{"type":"string","format":"url","maxLength":100},"facebook":{"type":"string","format":"url","maxLength":100},"instagram":{"type":"string","format":"url","maxLength":100},"linkedin":{"type":"string","format":"url","maxLength":100},"x":{"type":"string","format":"url","maxLength":100},"youtube":{"type":"string","format":"url","maxLength":100}}}}}}
```

## The Blog object

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"components":{"schemas":{"Blog":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","readOnly":true},"title":{"type":"string","maxLength":180},"slug":{"type":"string","readOnly":true,"description":"URL-friendly identifier, automatically generated"},"content":{"type":"string","description":"HTML content of the blog post"},"banner":{"type":"object","properties":{"url":{"type":"string","format":"url","description":"URL of the banner image","readOnly":true},"width":{"type":"integer","description":"Width of the banner image","readOnly":true},"height":{"type":"integer","description":"Height of the banner image","readOnly":true}}},"author":{"$ref":"#/components/schemas/User","readOnly":true},"viewsCount":{"type":"integer","default":0,"readOnly":true},"likesCount":{"type":"integer","default":0,"readOnly":true},"commentsCount":{"type":"integer","default":0,"readOnly":true},"status":{"type":"string","enum":["draft","published"],"default":"draft"},"publishedAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["title","content","banner","author","status"]},"User":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","description":"Unique identifier for the user","readOnly":true},"username":{"type":"string","description":"User's unique username","maxLength":20},"email":{"type":"string","format":"email","description":"User's unique email address","maxLength":50},"role":{"type":"string","enum":["admin","user"],"description":"User role","readOnly":true,"default":"user"},"firstName":{"type":"string","description":"User's first name","maxLength":20},"lastName":{"type":"string","description":"User's last name","maxLength":20},"socialLinks":{"type":"object","properties":{"website":{"type":"string","format":"url","maxLength":100},"facebook":{"type":"string","format":"url","maxLength":100},"instagram":{"type":"string","format":"url","maxLength":100},"linkedin":{"type":"string","format":"url","maxLength":100},"x":{"type":"string","format":"url","maxLength":100},"youtube":{"type":"string","format":"url","maxLength":100}}},"createdAt":{"type":"string","format":"date-time","description":"Timestamp of user creation","readOnly":true},"updatedAt":{"type":"string","format":"date-time","description":"Timestamp of last user update","readOnly":true}},"required":["username","email","role"]}}}}
```

## The BlogInput object

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"components":{"schemas":{"BlogInput":{"type":"object","properties":{"title":{"type":"string","maxLength":180},"content":{"type":"string","description":"HTML content"},"status":{"type":"string","enum":["draft","published"],"default":"draft"},"banner_image":{"type":"string","format":"binary","description":"Banner image file (png/jpg/webp, max 2MB)"}},"required":["title","content","banner_image"]}}}}
```

## The BlogUpdateInput object

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"components":{"schemas":{"BlogUpdateInput":{"type":"object","properties":{"title":{"type":"string","maxLength":180},"content":{"type":"string","description":"HTML content"},"status":{"type":"string","enum":["draft","published"]},"banner_image":{"type":"string","format":"binary","description":"New banner image file (png/jpg/webp, max 2MB)"}}}}}}
```

## The Comment object

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"components":{"schemas":{"Comment":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","readOnly":true},"blogId":{"type":"string","format":"objectid","description":"ID of the associated blog post"},"userId":{"type":"string","format":"objectid","description":"ID of the user who commented","readOnly":true},"content":{"type":"string","maxLength":1000,"description":"The comment text"},"likesCount":{"type":"integer","default":0,"readOnly":true},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["blogId","userId","content"]}}}}
```

## The CommentInput object

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"components":{"schemas":{"CommentInput":{"type":"object","properties":{"content":{"type":"string","maxLength":1000,"description":"The comment text"}},"required":["content"]}}}}
```

## The PaginatedUsers object

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"components":{"schemas":{"PaginatedUsers":{"type":"object","properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"},"users":{"type":"array","items":{"$ref":"#/components/schemas/User"}}}},"User":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","description":"Unique identifier for the user","readOnly":true},"username":{"type":"string","description":"User's unique username","maxLength":20},"email":{"type":"string","format":"email","description":"User's unique email address","maxLength":50},"role":{"type":"string","enum":["admin","user"],"description":"User role","readOnly":true,"default":"user"},"firstName":{"type":"string","description":"User's first name","maxLength":20},"lastName":{"type":"string","description":"User's last name","maxLength":20},"socialLinks":{"type":"object","properties":{"website":{"type":"string","format":"url","maxLength":100},"facebook":{"type":"string","format":"url","maxLength":100},"instagram":{"type":"string","format":"url","maxLength":100},"linkedin":{"type":"string","format":"url","maxLength":100},"x":{"type":"string","format":"url","maxLength":100},"youtube":{"type":"string","format":"url","maxLength":100}}},"createdAt":{"type":"string","format":"date-time","description":"Timestamp of user creation","readOnly":true},"updatedAt":{"type":"string","format":"date-time","description":"Timestamp of last user update","readOnly":true}},"required":["username","email","role"]}}}}
```

## The PaginatedBlogs object

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"components":{"schemas":{"PaginatedBlogs":{"type":"object","properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"},"blogs":{"type":"array","items":{"$ref":"#/components/schemas/Blog"}}}},"Blog":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","readOnly":true},"title":{"type":"string","maxLength":180},"slug":{"type":"string","readOnly":true,"description":"URL-friendly identifier, automatically generated"},"content":{"type":"string","description":"HTML content of the blog post"},"banner":{"type":"object","properties":{"url":{"type":"string","format":"url","description":"URL of the banner image","readOnly":true},"width":{"type":"integer","description":"Width of the banner image","readOnly":true},"height":{"type":"integer","description":"Height of the banner image","readOnly":true}}},"author":{"$ref":"#/components/schemas/User","readOnly":true},"viewsCount":{"type":"integer","default":0,"readOnly":true},"likesCount":{"type":"integer","default":0,"readOnly":true},"commentsCount":{"type":"integer","default":0,"readOnly":true},"status":{"type":"string","enum":["draft","published"],"default":"draft"},"publishedAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["title","content","banner","author","status"]},"User":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","description":"Unique identifier for the user","readOnly":true},"username":{"type":"string","description":"User's unique username","maxLength":20},"email":{"type":"string","format":"email","description":"User's unique email address","maxLength":50},"role":{"type":"string","enum":["admin","user"],"description":"User role","readOnly":true,"default":"user"},"firstName":{"type":"string","description":"User's first name","maxLength":20},"lastName":{"type":"string","description":"User's last name","maxLength":20},"socialLinks":{"type":"object","properties":{"website":{"type":"string","format":"url","maxLength":100},"facebook":{"type":"string","format":"url","maxLength":100},"instagram":{"type":"string","format":"url","maxLength":100},"linkedin":{"type":"string","format":"url","maxLength":100},"x":{"type":"string","format":"url","maxLength":100},"youtube":{"type":"string","format":"url","maxLength":100}}},"createdAt":{"type":"string","format":"date-time","description":"Timestamp of user creation","readOnly":true},"updatedAt":{"type":"string","format":"date-time","description":"Timestamp of last user update","readOnly":true}},"required":["username","email","role"]}}}}
```

## The AccessTokenResponse object

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"components":{"schemas":{"AccessTokenResponse":{"type":"object","properties":{"accessToken":{"type":"string","description":"JWT Access Token"}}}}}}
```

## The LoginResponse object

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"components":{"schemas":{"LoginResponse":{"allOf":[{"$ref":"#/components/schemas/AccessTokenResponse"},{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"}}}]},"AccessTokenResponse":{"type":"object","properties":{"accessToken":{"type":"string","description":"JWT Access Token"}}},"User":{"type":"object","properties":{"_id":{"type":"string","format":"objectid","description":"Unique identifier for the user","readOnly":true},"username":{"type":"string","description":"User's unique username","maxLength":20},"email":{"type":"string","format":"email","description":"User's unique email address","maxLength":50},"role":{"type":"string","enum":["admin","user"],"description":"User role","readOnly":true,"default":"user"},"firstName":{"type":"string","description":"User's first name","maxLength":20},"lastName":{"type":"string","description":"User's last name","maxLength":20},"socialLinks":{"type":"object","properties":{"website":{"type":"string","format":"url","maxLength":100},"facebook":{"type":"string","format":"url","maxLength":100},"instagram":{"type":"string","format":"url","maxLength":100},"linkedin":{"type":"string","format":"url","maxLength":100},"x":{"type":"string","format":"url","maxLength":100},"youtube":{"type":"string","format":"url","maxLength":100}}},"createdAt":{"type":"string","format":"date-time","description":"Timestamp of user creation","readOnly":true},"updatedAt":{"type":"string","format":"date-time","description":"Timestamp of last user update","readOnly":true}},"required":["username","email","role"]}}}}
```

## The ErrorResponse object

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"components":{"schemas":{"ErrorResponse":{"type":"object","properties":{"code":{"type":"string","description":"Application-specific error code"},"message":{"type":"string","description":"Human-readable error message"}},"required":["code","message"]}}}}
```

## The ValidationErrorDetail object

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"components":{"schemas":{"ValidationErrorDetail":{"type":"object","properties":{"type":{"type":"string"},"value":{"type":"string"},"msg":{"type":"string"},"path":{"type":"string"},"location":{"type":"string"}}}}}}
```

## The ValidationErrorResponse object

```json
{"openapi":"3.0.3","info":{"title":"Blog API","version":"1.0.0"},"components":{"schemas":{"ValidationErrorResponse":{"type":"object","properties":{"code":{"type":"string","enum":["ValidationError"]},"errors":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ValidationErrorDetail"}}},"required":["code","errors"]},"ValidationErrorDetail":{"type":"object","properties":{"type":{"type":"string"},"value":{"type":"string"},"msg":{"type":"string"},"path":{"type":"string"},"location":{"type":"string"}}}}}}
```


