diff --git a/veza-backend-api/docs/docs.go b/veza-backend-api/docs/docs.go index 7b4757abc..b6fda5641 100644 --- a/veza-backend-api/docs/docs.go +++ b/veza-backend-api/docs/docs.go @@ -6480,6 +6480,149 @@ const docTemplate = `{ } } }, + "/users/search": { + "get": { + "description": "Full-text search on users (username, display_name). Paginated. Public.", + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Search users", + "parameters": [ + { + "type": "string", + "description": "Full-text query", + "name": "q", + "in": "query" + }, + { + "type": "integer", + "default": 1, + "description": "Page", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 20, + "description": "Items per page (max 100)", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "pagination": { + "type": "object" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/definitions/veza-backend-api_internal_models.User" + } + } + } + } + } + } + ] + } + }, + "400": { + "description": "Validation (bounds)", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/users/suggestions": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Returns suggested users to follow for the authenticated user. Declarative discovery — no behavioural scoring (CLAUDE.md rule 7).", + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Get follow suggestions", + "parameters": [ + { + "type": "integer", + "default": 10, + "description": "Max items (max 50)", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "users": { + "type": "array", + "items": { + "$ref": "#/definitions/veza-backend-api_internal_models.User" + } + } + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, "/users/{id}": { "get": { "description": "Get public profile information for a user", @@ -6679,6 +6822,142 @@ const docTemplate = `{ } } }, + "/users/{id}/block": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Authenticated user blocks target user (hides their content, prevents follows). Cannot self-block.", + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Block user", + "parameters": [ + { + "type": "string", + "description": "Target user UUID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Validation / self-block", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Authenticated user unblocks target user (idempotent).", + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Unblock user", + "parameters": [ + { + "type": "string", + "description": "Target user UUID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Invalid id", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, "/users/{id}/completion": { "get": { "description": "Get profile completion percentage and missing fields", @@ -6741,6 +7020,148 @@ const docTemplate = `{ } } }, + "/users/{id}/follow": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Authenticated user follows target user. Creates a notification (F554 grouped) for the target.", + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Follow user", + "parameters": [ + { + "type": "string", + "description": "Target user UUID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Invalid id", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "404": { + "description": "User not found", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Authenticated user stops following target user (idempotent).", + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Unfollow user", + "parameters": [ + { + "type": "string", + "description": "Target user UUID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Invalid id", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, "/users/{id}/likes": { "get": { "security": [ diff --git a/veza-backend-api/docs/swagger.json b/veza-backend-api/docs/swagger.json index fd9168b81..d353d9e64 100644 --- a/veza-backend-api/docs/swagger.json +++ b/veza-backend-api/docs/swagger.json @@ -6474,6 +6474,149 @@ } } }, + "/users/search": { + "get": { + "description": "Full-text search on users (username, display_name). Paginated. Public.", + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Search users", + "parameters": [ + { + "type": "string", + "description": "Full-text query", + "name": "q", + "in": "query" + }, + { + "type": "integer", + "default": 1, + "description": "Page", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 20, + "description": "Items per page (max 100)", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "pagination": { + "type": "object" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/definitions/veza-backend-api_internal_models.User" + } + } + } + } + } + } + ] + } + }, + "400": { + "description": "Validation (bounds)", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/users/suggestions": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Returns suggested users to follow for the authenticated user. Declarative discovery — no behavioural scoring (CLAUDE.md rule 7).", + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Get follow suggestions", + "parameters": [ + { + "type": "integer", + "default": 10, + "description": "Max items (max 50)", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "users": { + "type": "array", + "items": { + "$ref": "#/definitions/veza-backend-api_internal_models.User" + } + } + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, "/users/{id}": { "get": { "description": "Get public profile information for a user", @@ -6673,6 +6816,142 @@ } } }, + "/users/{id}/block": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Authenticated user blocks target user (hides their content, prevents follows). Cannot self-block.", + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Block user", + "parameters": [ + { + "type": "string", + "description": "Target user UUID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Validation / self-block", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Authenticated user unblocks target user (idempotent).", + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Unblock user", + "parameters": [ + { + "type": "string", + "description": "Target user UUID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Invalid id", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, "/users/{id}/completion": { "get": { "description": "Get profile completion percentage and missing fields", @@ -6735,6 +7014,148 @@ } } }, + "/users/{id}/follow": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Authenticated user follows target user. Creates a notification (F554 grouped) for the target.", + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Follow user", + "parameters": [ + { + "type": "string", + "description": "Target user UUID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Invalid id", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "404": { + "description": "User not found", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Authenticated user stops following target user (idempotent).", + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Unfollow user", + "parameters": [ + { + "type": "string", + "description": "Target user UUID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Invalid id", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, "/users/{id}/likes": { "get": { "security": [ diff --git a/veza-backend-api/docs/swagger.yaml b/veza-backend-api/docs/swagger.yaml index b4a0d1358..6af3f03fc 100644 --- a/veza-backend-api/docs/swagger.yaml +++ b/veza-backend-api/docs/swagger.yaml @@ -5129,6 +5129,88 @@ paths: summary: Update Profile tags: - User + /users/{id}/block: + delete: + description: Authenticated user unblocks target user (idempotent). + parameters: + - description: Target user UUID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + message: + type: string + type: object + type: object + "400": + description: Invalid id + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "500": + description: Internal Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + security: + - BearerAuth: [] + summary: Unblock user + tags: + - User + post: + description: Authenticated user blocks target user (hides their content, prevents + follows). Cannot self-block. + parameters: + - description: Target user UUID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + message: + type: string + type: object + type: object + "400": + description: Validation / self-block + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "500": + description: Internal Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + security: + - BearerAuth: [] + summary: Block user + tags: + - User /users/{id}/completion: get: consumes: @@ -5167,6 +5249,92 @@ paths: summary: Get Profile Completion tags: - User + /users/{id}/follow: + delete: + description: Authenticated user stops following target user (idempotent). + parameters: + - description: Target user UUID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + message: + type: string + type: object + type: object + "400": + description: Invalid id + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "500": + description: Internal Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + security: + - BearerAuth: [] + summary: Unfollow user + tags: + - User + post: + description: Authenticated user follows target user. Creates a notification + (F554 grouped) for the target. + parameters: + - description: Target user UUID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + message: + type: string + type: object + type: object + "400": + description: Invalid id + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "404": + description: User not found + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "500": + description: Internal Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + security: + - BearerAuth: [] + summary: Follow user + tags: + - User /users/{id}/likes: get: description: Returns paginated tracks the given user has liked. Used for profile @@ -5379,6 +5547,95 @@ paths: summary: CCPA Do Not Sell opt-out tags: - Users + /users/search: + get: + description: Full-text search on users (username, display_name). Paginated. + Public. + parameters: + - description: Full-text query + in: query + name: q + type: string + - default: 1 + description: Page + in: query + name: page + type: integer + - default: 20 + description: Items per page (max 100) + in: query + name: limit + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + pagination: + type: object + users: + items: + $ref: '#/definitions/veza-backend-api_internal_models.User' + type: array + type: object + type: object + "400": + description: Validation (bounds) + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "500": + description: Internal Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + summary: Search users + tags: + - User + /users/suggestions: + get: + description: Returns suggested users to follow for the authenticated user. Declarative + discovery — no behavioural scoring (CLAUDE.md rule 7). + parameters: + - default: 10 + description: Max items (max 50) + in: query + name: limit + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + users: + items: + $ref: '#/definitions/veza-backend-api_internal_models.User' + type: array + type: object + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "500": + description: Internal Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + security: + - BearerAuth: [] + summary: Get follow suggestions + tags: + - User /validate: post: consumes: diff --git a/veza-backend-api/internal/handlers/profile_handler.go b/veza-backend-api/internal/handlers/profile_handler.go index 202a11351..8168a7226 100644 --- a/veza-backend-api/internal/handlers/profile_handler.go +++ b/veza-backend-api/internal/handlers/profile_handler.go @@ -6,6 +6,8 @@ import ( "time" apperrors "veza-backend-api/internal/errors" + // models imported so swaggo can resolve models.User in doc comments. + _ "veza-backend-api/internal/models" "veza-backend-api/internal/services" "veza-backend-api/internal/types" "veza-backend-api/internal/utils" @@ -267,6 +269,17 @@ func (h *ProfileHandler) ListUsers(c *gin.Context) { // SearchUsers gère la recherche d'utilisateurs // BE-API-008: Implement user search endpoint +// @Summary Search users +// @Description Full-text search on users (username, display_name). Paginated. Public. +// @Tags User +// @Produce json +// @Param q query string false "Full-text query" +// @Param page query int false "Page" default(1) +// @Param limit query int false "Items per page (max 100)" default(20) +// @Success 200 {object} handlers.APIResponse{data=object{users=[]models.User,pagination=object}} +// @Failure 400 {object} handlers.APIResponse "Validation (bounds)" +// @Failure 500 {object} handlers.APIResponse "Internal Error" +// @Router /users/search [get] func (h *ProfileHandler) SearchUsers(c *gin.Context) { // Récupérer les paramètres de recherche query := c.Query("q") @@ -308,6 +321,18 @@ func (h *ProfileHandler) SearchUsers(c *gin.Context) { // FollowUser gère le suivi d'un utilisateur // POST /api/v1/users/:id/follow // BE-API-017: Implement user follow/unfollow endpoints +// @Summary Follow user +// @Description Authenticated user follows target user. Creates a notification (F554 grouped) for the target. +// @Tags User +// @Produce json +// @Security BearerAuth +// @Param id path string true "Target user UUID" +// @Success 200 {object} handlers.APIResponse{data=object{message=string}} +// @Failure 400 {object} handlers.APIResponse "Invalid id" +// @Failure 401 {object} handlers.APIResponse "Unauthorized" +// @Failure 404 {object} handlers.APIResponse "User not found" +// @Failure 500 {object} handlers.APIResponse "Internal Error" +// @Router /users/{id}/follow [post] func (h *ProfileHandler) FollowUser(c *gin.Context) { // Récupérer l'ID de l'utilisateur à suivre depuis l'URL userIDStr := c.Param("id") @@ -366,6 +391,16 @@ func (h *ProfileHandler) FollowUser(c *gin.Context) { // GetFollowSuggestions returns users to follow (v0.10.0 F211) // GET /api/v1/users/suggestions?limit=10 +// @Summary Get follow suggestions +// @Description Returns suggested users to follow for the authenticated user. Declarative discovery — no behavioural scoring (CLAUDE.md rule 7). +// @Tags User +// @Produce json +// @Security BearerAuth +// @Param limit query int false "Max items (max 50)" default(10) +// @Success 200 {object} handlers.APIResponse{data=object{users=[]models.User}} +// @Failure 401 {object} handlers.APIResponse "Unauthorized" +// @Failure 500 {object} handlers.APIResponse "Internal Error" +// @Router /users/suggestions [get] func (h *ProfileHandler) GetFollowSuggestions(c *gin.Context) { userID, ok := GetUserIDUUID(c) if !ok { @@ -393,6 +428,17 @@ func (h *ProfileHandler) GetFollowSuggestions(c *gin.Context) { // UnfollowUser gère l'arrêt du suivi d'un utilisateur // DELETE /api/v1/users/:id/follow // BE-API-017: Implement user follow/unfollow endpoints +// @Summary Unfollow user +// @Description Authenticated user stops following target user (idempotent). +// @Tags User +// @Produce json +// @Security BearerAuth +// @Param id path string true "Target user UUID" +// @Success 200 {object} handlers.APIResponse{data=object{message=string}} +// @Failure 400 {object} handlers.APIResponse "Invalid id" +// @Failure 401 {object} handlers.APIResponse "Unauthorized" +// @Failure 500 {object} handlers.APIResponse "Internal Error" +// @Router /users/{id}/follow [delete] func (h *ProfileHandler) UnfollowUser(c *gin.Context) { // Récupérer l'ID de l'utilisateur à ne plus suivre depuis l'URL userIDStr := c.Param("id") @@ -435,6 +481,17 @@ func (h *ProfileHandler) UnfollowUser(c *gin.Context) { // BlockUser gère le blocage d'un utilisateur // POST /api/v1/users/:id/block // BE-API-018: Implement user block/unblock endpoints +// @Summary Block user +// @Description Authenticated user blocks target user (hides their content, prevents follows). Cannot self-block. +// @Tags User +// @Produce json +// @Security BearerAuth +// @Param id path string true "Target user UUID" +// @Success 200 {object} handlers.APIResponse{data=object{message=string}} +// @Failure 400 {object} handlers.APIResponse "Validation / self-block" +// @Failure 401 {object} handlers.APIResponse "Unauthorized" +// @Failure 500 {object} handlers.APIResponse "Internal Error" +// @Router /users/{id}/block [post] func (h *ProfileHandler) BlockUser(c *gin.Context) { // Récupérer l'ID de l'utilisateur à bloquer depuis l'URL userIDStr := c.Param("id") @@ -487,6 +544,17 @@ func (h *ProfileHandler) BlockUser(c *gin.Context) { // UnblockUser gère le déblocage d'un utilisateur // DELETE /api/v1/users/:id/block // BE-API-018: Implement user block/unblock endpoints +// @Summary Unblock user +// @Description Authenticated user unblocks target user (idempotent). +// @Tags User +// @Produce json +// @Security BearerAuth +// @Param id path string true "Target user UUID" +// @Success 200 {object} handlers.APIResponse{data=object{message=string}} +// @Failure 400 {object} handlers.APIResponse "Invalid id" +// @Failure 401 {object} handlers.APIResponse "Unauthorized" +// @Failure 500 {object} handlers.APIResponse "Internal Error" +// @Router /users/{id}/block [delete] func (h *ProfileHandler) UnblockUser(c *gin.Context) { // Récupérer l'ID de l'utilisateur à débloquer depuis l'URL userIDStr := c.Param("id") diff --git a/veza-backend-api/openapi.yaml b/veza-backend-api/openapi.yaml index b4a0d1358..6af3f03fc 100644 --- a/veza-backend-api/openapi.yaml +++ b/veza-backend-api/openapi.yaml @@ -5129,6 +5129,88 @@ paths: summary: Update Profile tags: - User + /users/{id}/block: + delete: + description: Authenticated user unblocks target user (idempotent). + parameters: + - description: Target user UUID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + message: + type: string + type: object + type: object + "400": + description: Invalid id + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "500": + description: Internal Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + security: + - BearerAuth: [] + summary: Unblock user + tags: + - User + post: + description: Authenticated user blocks target user (hides their content, prevents + follows). Cannot self-block. + parameters: + - description: Target user UUID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + message: + type: string + type: object + type: object + "400": + description: Validation / self-block + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "500": + description: Internal Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + security: + - BearerAuth: [] + summary: Block user + tags: + - User /users/{id}/completion: get: consumes: @@ -5167,6 +5249,92 @@ paths: summary: Get Profile Completion tags: - User + /users/{id}/follow: + delete: + description: Authenticated user stops following target user (idempotent). + parameters: + - description: Target user UUID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + message: + type: string + type: object + type: object + "400": + description: Invalid id + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "500": + description: Internal Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + security: + - BearerAuth: [] + summary: Unfollow user + tags: + - User + post: + description: Authenticated user follows target user. Creates a notification + (F554 grouped) for the target. + parameters: + - description: Target user UUID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + message: + type: string + type: object + type: object + "400": + description: Invalid id + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "404": + description: User not found + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "500": + description: Internal Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + security: + - BearerAuth: [] + summary: Follow user + tags: + - User /users/{id}/likes: get: description: Returns paginated tracks the given user has liked. Used for profile @@ -5379,6 +5547,95 @@ paths: summary: CCPA Do Not Sell opt-out tags: - Users + /users/search: + get: + description: Full-text search on users (username, display_name). Paginated. + Public. + parameters: + - description: Full-text query + in: query + name: q + type: string + - default: 1 + description: Page + in: query + name: page + type: integer + - default: 20 + description: Items per page (max 100) + in: query + name: limit + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + pagination: + type: object + users: + items: + $ref: '#/definitions/veza-backend-api_internal_models.User' + type: array + type: object + type: object + "400": + description: Validation (bounds) + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "500": + description: Internal Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + summary: Search users + tags: + - User + /users/suggestions: + get: + description: Returns suggested users to follow for the authenticated user. Declarative + discovery — no behavioural scoring (CLAUDE.md rule 7). + parameters: + - default: 10 + description: Max items (max 50) + in: query + name: limit + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + users: + items: + $ref: '#/definitions/veza-backend-api_internal_models.User' + type: array + type: object + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "500": + description: Internal Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + security: + - BearerAuth: [] + summary: Get follow suggestions + tags: + - User /validate: post: consumes: