fix: update clients, update articles response

This commit is contained in:
hanif salafi 2025-10-12 12:52:06 +07:00
parent d6eb8fece3
commit b9fea3ddd0
9 changed files with 429 additions and 162 deletions

View File

@ -49,9 +49,9 @@ func ArticlesResponseMapper(
}
}
categoryName := ""
articleCategories, _ := articleCategoryDetailsRepo.FindByArticleId(articlesReq.ID)
var articleCategoriesArr []*articleCategoriesResponse.ArticleCategoriesResponse
categoryName := ""
if len(articleCategories) > 0 {
for _, result := range articleCategories {
if result.Category != nil {
@ -59,6 +59,11 @@ func ArticlesResponseMapper(
}
}
log.Info().Interface("articleCategoriesArr", articleCategoriesArr).Msg("")
// Ambil categoryName dari array pertama
if len(articleCategoriesArr) > 0 {
categoryName = articleCategoriesArr[0].Title
}
}
articleFiles, _ := articleFilesRepo.FindByArticle(clientId, articlesReq.ID)

View File

@ -53,10 +53,11 @@ func (_i *ClientsRouter) RegisterClientsRoutes() {
router.Post("/", clientsController.Save)
router.Post("/with-user", clientsController.CreateClientWithUser)
router.Put("/:id", clientsController.Update)
router.Put("/update", clientsController.UpdateWithAuth)
router.Delete("/:id", clientsController.Delete)
// Logo upload routes
router.Post("/:id/logo", clientsController.UploadLogo)
router.Post("/logo", clientsController.UploadLogo)
router.Delete("/:id/logo", clientsController.DeleteLogo)
router.Get("/:id/logo/url", clientsController.GetLogoURL)
})

View File

@ -23,6 +23,7 @@ type ClientsController interface {
Show(c *fiber.Ctx) error
Save(c *fiber.Ctx) error
Update(c *fiber.Ctx) error
UpdateWithAuth(c *fiber.Ctx) error
Delete(c *fiber.Ctx) error
// New hierarchy endpoints
@ -205,6 +206,42 @@ func (_i *clientsController) Update(c *fiber.Ctx) error {
})
}
// UpdateWithAuth update Clients using auth token
// @Summary update Clients with auth token
// @Description API for update Clients using client ID from auth token
// @Tags Clients
// @Security Bearer
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
// @Param payload body request.ClientsUpdateRequest true "Required payload"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /clients/update [put]
func (_i *clientsController) UpdateWithAuth(c *fiber.Ctx) error {
// Get Authorization token from header
authToken := c.Get("Authorization")
_i.Log.Info().Str("authToken", authToken).Msg("")
req := new(request.ClientsUpdateRequest)
if err := utilVal.ParseAndValidate(c, req); err != nil {
return err
}
err := _i.clientsService.UpdateWithAuth(authToken, *req)
if err != nil {
return utilRes.Resp(c, utilRes.Response{
Success: false,
Messages: utilRes.Messages{err.Error()},
})
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Clients successfully updated"},
})
}
// Delete delete Clients
// @Summary delete Clients
// @Description API for delete Clients
@ -498,26 +535,22 @@ func (_i *clientsController) CreateClientWithUser(c *fiber.Ctx) error {
// UploadLogo uploads client logo
// @Summary Upload client logo
// @Description API for uploading client logo image to MinIO
// @Description API for uploading client logo image to MinIO (uses client ID from auth token)
// @Tags Clients
// @Security Bearer
// @Param id path string true "Client ID"
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
// @Param logo formData file true "Logo image file (jpg, jpeg, png, gif, webp, max 5MB)"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /clients/{id}/logo [post]
// @Router /clients/logo [post]
func (_i *clientsController) UploadLogo(c *fiber.Ctx) error {
clientId, err := uuid.Parse(c.Params("id"))
if err != nil {
return utilRes.Resp(c, utilRes.Response{
Success: false,
Messages: utilRes.Messages{"Invalid client ID"},
})
}
// Get Authorization token from header
authToken := c.Get("Authorization")
_i.Log.Info().Str("authToken", authToken).Msg("")
imagePath, err := _i.clientsService.UploadLogo(clientId, c)
imagePath, err := _i.clientsService.UploadLogo(authToken, c)
if err != nil {
return utilRes.Resp(c, utilRes.Response{
Success: false,

View File

@ -36,6 +36,7 @@ type ClientsService interface {
Show(id uuid.UUID) (clients *response.ClientsResponse, err error)
Save(req request.ClientsCreateRequest, authToken string) (clients *entity.Clients, err error)
Update(id uuid.UUID, req request.ClientsUpdateRequest) (err error)
UpdateWithAuth(authToken string, req request.ClientsUpdateRequest) (err error)
Delete(id uuid.UUID) error
// New hierarchy methods
@ -49,7 +50,7 @@ type ClientsService interface {
CreateClientWithUser(req request.ClientWithUserCreateRequest) (*response.ClientWithUserResponse, error)
// Logo upload methods
UploadLogo(clientId uuid.UUID, c *fiber.Ctx) (string, error)
UploadLogo(authToken string, c *fiber.Ctx) (string, error)
DeleteLogo(clientId uuid.UUID, imagePath string) error
GetLogoURL(imagePath string) (string, error)
}
@ -148,6 +149,44 @@ func (_i *clientsService) Update(id uuid.UUID, req request.ClientsUpdateRequest)
return _i.Repo.Update(id, updateReq)
}
func (_i *clientsService) UpdateWithAuth(authToken string, req request.ClientsUpdateRequest) (err error) {
_i.Log.Info().Interface("data", req).Msg("")
// Extract clientId from authToken
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user == nil {
_i.Log.Error().Msg("User not found from auth token")
return fmt.Errorf("user not found")
}
if user.ClientId == nil {
_i.Log.Error().Msg("Client ID not found in user token")
return fmt.Errorf("client ID not found in user token")
}
clientId := *user.ClientId
_i.Log.Info().Str("clientId", clientId.String()).Msg("Updating client with auth token")
// Convert request to entity
updateReq := &entity.Clients{
Name: *req.Name,
Description: req.Description,
ClientType: *req.ClientType,
ParentClientId: req.ParentClientId,
LogoUrl: req.LogoUrl,
LogoImagePath: req.LogoImagePath,
Address: req.Address,
PhoneNumber: req.PhoneNumber,
Website: req.Website,
MaxUsers: req.MaxUsers,
MaxStorage: req.MaxStorage,
Settings: req.Settings,
IsActive: req.IsActive,
}
return _i.Repo.Update(clientId, updateReq)
}
func (_i *clientsService) Delete(id uuid.UUID) error {
result, err := _i.Repo.FindOne(id)
if err != nil {
@ -435,7 +474,20 @@ func (_i *clientsService) CreateClientWithUser(req request.ClientWithUserCreateR
// =====================================================================
// UploadLogo uploads client logo to MinIO
func (_i *clientsService) UploadLogo(clientId uuid.UUID, c *fiber.Ctx) (string, error) {
func (_i *clientsService) UploadLogo(authToken string, c *fiber.Ctx) (string, error) {
// Extract clientId from authToken
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user == nil {
_i.Log.Error().Msg("User not found from auth token")
return "", fmt.Errorf("user not found")
}
if user.ClientId == nil {
_i.Log.Error().Msg("Client ID not found in user token")
return "", fmt.Errorf("client ID not found in user token")
}
clientId := *user.ClientId
_i.Log.Info().Str("clientId", clientId.String()).Msg("Uploading client logo")
// Upload logo using the upload service

View File

@ -8,15 +8,16 @@ API untuk mengelola logo client dengan integrasi MinIO storage. Mendukung upload
### 1. Upload Client Logo
**POST** `/clients/{id}/logo`
**POST** `/clients/logo`
Upload logo image untuk client tertentu.
Upload logo image untuk client yang sedang login (menggunakan client ID dari auth token).
#### Request
- **Content-Type**: `multipart/form-data`
- **Parameter**:
- `id` (path): Client ID (UUID)
- `logo` (form-data): File image (jpg, jpeg, png, gif, webp, max 5MB)
- **Headers**:
- `Authorization`: Bearer token (required)
#### Response
```json
@ -32,7 +33,7 @@ Upload logo image untuk client tertentu.
#### Example cURL
```bash
curl -X POST \
http://localhost:8080/clients/123e4567-e89b-12d3-a456-426614174000/logo \
http://localhost:8080/clients/logo \
-H "Authorization: Bearer your-token" \
-F "logo=@/path/to/logo.png"
```

View File

@ -0,0 +1,169 @@
# Client Update with Auth Token API Documentation
## Overview
API endpoint untuk mengupdate data client menggunakan client ID yang diambil dari auth token, tanpa perlu menyertakan client ID sebagai path parameter.
## Endpoint
### Update Client with Auth Token
**PUT** `/clients/update`
#### Description
Mengupdate data client yang sedang login menggunakan client ID dari auth token.
#### Parameters
- Tidak ada path parameter, client ID diambil dari auth token
#### Headers
- **Authorization** (required): Bearer token untuk autentikasi user
#### Request Body
```json
{
"name": "Updated Client Name",
"description": "Updated client description",
"clientType": "standalone",
"parentClientId": null,
"maxUsers": 100,
"maxStorage": 1073741824,
"settings": "{\"theme\": \"dark\"}",
"isActive": true,
"logoUrl": "https://example.com/logo.png",
"logoImagePath": "clients/logos/client-id/logo.png",
"address": "Jl. Example No. 123",
"phoneNumber": "+62-123-456-7890",
"website": "https://example.com"
}
```
#### Response Fields
- **name** (string, optional): Nama client
- **description** (string, optional): Deskripsi client
- **clientType** (string, optional): Tipe client (`parent_client`, `sub_client`, `standalone`)
- **parentClientId** (string, optional): ID parent client (untuk sub client)
- **maxUsers** (integer, optional): Batas maksimal user
- **maxStorage** (integer, optional): Batas maksimal storage dalam bytes
- **settings** (string, optional): JSON string untuk custom settings
- **isActive** (boolean, optional): Status aktif client
- **logoUrl** (string, optional): URL logo client
- **logoImagePath** (string, optional): Path logo di MinIO storage
- **address** (string, optional): Alamat client
- **phoneNumber** (string, optional): Nomor telepon client
- **website** (string, optional): Website resmi client
#### Response
##### Success Response (200)
```json
{
"success": true,
"messages": ["Clients successfully updated"],
"data": null
}
```
#### Error Responses
##### 400 Bad Request
```json
{
"success": false,
"messages": ["Validation error"],
"data": {
"field": "error message"
}
}
```
##### 401 Unauthorized
```json
{
"success": false,
"messages": ["user not found"]
}
```
##### 500 Internal Server Error
```json
{
"success": false,
"messages": ["client ID not found in user token"]
}
```
## Usage Examples
### cURL Example
```bash
curl -X PUT "http://localhost:8080/clients/update" \
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
-H "Content-Type: application/json" \
-d '{
"name": "Updated Client Name",
"description": "Updated description",
"clientType": "standalone",
"maxUsers": 100,
"isActive": true,
"address": "Jl. Example No. 123",
"phoneNumber": "+62-123-456-7890",
"website": "https://example.com"
}'
```
### JavaScript Example
```javascript
const updateClient = async (clientData) => {
try {
const response = await fetch('/clients/update', {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(clientData)
});
const data = await response.json();
if (data.success) {
console.log('Client updated successfully');
} else {
console.error('Update failed:', data.messages);
}
} catch (error) {
console.error('Error updating client:', error);
}
};
// Usage
updateClient({
name: "Updated Client Name",
description: "Updated description",
clientType: "standalone",
maxUsers: 100,
isActive: true,
address: "Jl. Example No. 123",
phoneNumber: "+62-123-456-7890",
website: "https://example.com"
});
```
## Use Cases
1. **Profile Update**: User mengupdate profil client mereka sendiri
2. **Settings Management**: Mengubah pengaturan client
3. **Contact Information**: Mengupdate informasi kontak client
4. **Logo Management**: Mengupdate URL atau path logo client
5. **Resource Limits**: Mengubah batas user atau storage
## Security Features
- **Authentication Required**: Harus menggunakan Bearer token yang valid
- **Client Isolation**: User hanya bisa mengupdate client mereka sendiri
- **Token Validation**: Client ID diambil dari token yang sudah diverifikasi
- **Input Validation**: Semua input divalidasi sebelum diproses
## Notes
- Endpoint ini menggunakan middleware `UserMiddleware` untuk mengekstrak informasi user dari JWT token
- Client ID diambil dari `user.ClientId` dalam token
- Jika user tidak ditemukan atau client ID tidak ada dalam token, akan mengembalikan error
- Semua field dalam request body bersifat optional
- Endpoint ini lebih aman daripada endpoint update dengan path parameter karena mencegah user mengupdate client lain

View File

@ -9744,6 +9744,62 @@ const docTemplate = `{
}
}
},
"/clients/logo": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "API for uploading client logo image to MinIO (uses client ID from auth token)",
"tags": [
"Clients"
],
"summary": "Upload client logo",
"parameters": [
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"type": "file",
"description": "Logo image file (jpg, jpeg, png, gif, webp, max 5MB)",
"name": "logo",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
}
},
"/clients/with-user": {
"post": {
"description": "API for creating a client and its admin user in a single request (Public endpoint)",
@ -9986,60 +10042,6 @@ const docTemplate = `{
}
},
"/clients/{id}/logo": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "API for uploading client logo image to MinIO",
"tags": [
"Clients"
],
"summary": "Upload client logo",
"parameters": [
{
"type": "string",
"description": "Client ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "file",
"description": "Logo image file (jpg, jpeg, png, gif, webp, max 5MB)",
"name": "logo",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
},
"delete": {
"security": [
{

View File

@ -9733,6 +9733,62 @@
}
}
},
"/clients/logo": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "API for uploading client logo image to MinIO (uses client ID from auth token)",
"tags": [
"Clients"
],
"summary": "Upload client logo",
"parameters": [
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"type": "file",
"description": "Logo image file (jpg, jpeg, png, gif, webp, max 5MB)",
"name": "logo",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
}
},
"/clients/with-user": {
"post": {
"description": "API for creating a client and its admin user in a single request (Public endpoint)",
@ -9975,60 +10031,6 @@
}
},
"/clients/{id}/logo": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "API for uploading client logo image to MinIO",
"tags": [
"Clients"
],
"summary": "Upload client logo",
"parameters": [
{
"type": "string",
"description": "Client ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "file",
"description": "Logo image file (jpg, jpeg, png, gif, webp, max 5MB)",
"name": "logo",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
},
"delete": {
"security": [
{

View File

@ -8042,41 +8042,6 @@ paths:
summary: Delete client logo
tags:
- Clients
post:
description: API for uploading client logo image to MinIO
parameters:
- description: Client ID
in: path
name: id
required: true
type: string
- description: Logo image file (jpg, jpeg, png, gif, webp, max 5MB)
in: formData
name: logo
required: true
type: file
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.BadRequestError'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.UnauthorizedError'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.InternalServerError'
security:
- Bearer: []
summary: Upload client logo
tags:
- Clients
/clients/{id}/logo/url:
get:
description: API for generating presigned URL for client logo
@ -8275,6 +8240,43 @@ paths:
summary: Bulk create sub-clients
tags:
- Clients
/clients/logo:
post:
description: API for uploading client logo image to MinIO (uses client ID from
auth token)
parameters:
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Logo image file (jpg, jpeg, png, gif, webp, max 5MB)
in: formData
name: logo
required: true
type: file
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.BadRequestError'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.UnauthorizedError'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.InternalServerError'
security:
- Bearer: []
summary: Upload client logo
tags:
- Clients
/clients/with-user:
post:
description: API for creating a client and its admin user in a single request