diff --git a/internal/domain/lms_persona.go b/internal/domain/lms_persona.go index 9759b39..3273be0 100644 --- a/internal/domain/lms_persona.go +++ b/internal/domain/lms_persona.go @@ -13,7 +13,8 @@ type LmsPersona struct { ID int64 `json:"id"` Name string `json:"name"` Description *string `json:"description,omitempty"` - ProfilePicture *string `json:"profile_picture,omitempty"` // image URL (e.g. MinIO or HTTPS) + // ProfilePicture is always serialized (null when not set); clients rely on stable keys in list payloads. + ProfilePicture *string `json:"profile_picture"` // image URL (e.g. MinIO or HTTPS); JSON null when unset IsActive bool `json:"is_active"` CreatedAt time.Time `json:"created_at"` UpdatedAt *time.Time `json:"updated_at,omitempty"` diff --git a/postman/LMS-Personas.postman_collection.json b/postman/LMS-Personas.postman_collection.json new file mode 100644 index 0000000..b78d490 --- /dev/null +++ b/postman/LMS-Personas.postman_collection.json @@ -0,0 +1,274 @@ +{ + "info": { + "_postman_id": "c4e8a921-62f3-4c1e-9bad-1107dfd2a701", + "name": "LMS Personas - Catalog CRUD", + "description": "Admin API for LMS persona catalog (`/api/v1/personas`). Requires bearer token with permissions: personas.list, personas.create, personas.get, personas.update, personas.delete. Personas are assigned to practices via `persona_id`; image URL lives in JSON field `profile_picture`.", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{access_token}}", + "type": "string" + } + ] + }, + "variable": [ + { + "key": "base_url", + "value": "http://localhost:8080" + }, + { + "key": "access_token", + "value": "" + }, + { + "key": "persona_id", + "value": "1", + "type": "string" + } + ], + "item": [ + { + "name": "01 - Personas CRUD", + "item": [ + { + "name": "Create persona", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + "});", + "const body = pm.response.json();", + "pm.test(\"Persona returned in data\", function () {", + " pm.expect(body.data).to.be.an(\"object\");", + " pm.expect(body.data.id).to.be.a(\"number\");", + "});", + "pm.collectionVariables.set(\"persona_id\", String(body.data.id));" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Postman Coach\",\n \"description\": \"Smoke-test persona from Postman\",\n \"profile_picture\": \"https://cdn.example.com/personas/postman-coach.png\",\n \"is_active\": true\n}" + }, + "url": { + "raw": "{{base_url}}/api/v1/personas", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "v1", + "personas" + ] + } + }, + "response": [] + }, + { + "name": "List personas (active_only default)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "const body = pm.response.json();", + "pm.test(\"Paged list shape\", function () {", + " pm.expect(body.data.personas).to.be.an(\"array\");", + " pm.expect(body.data.total_count).to.be.a(\"number\");", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "url": { + "raw": "{{base_url}}/api/v1/personas?limit=20&offset=0", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "v1", + "personas" + ], + "query": [ + { + "key": "active_only", + "value": "true", + "disabled": true + }, + { + "key": "limit", + "value": "20" + }, + { + "key": "offset", + "value": "0" + } + ] + } + }, + "response": [] + }, + { + "name": "List personas (include inactive)", + "request": { + "method": "GET", + "url": { + "raw": "{{base_url}}/api/v1/personas?active_only=false&limit=50&offset=0", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "v1", + "personas" + ], + "query": [ + { + "key": "active_only", + "value": "false" + }, + { + "key": "limit", + "value": "50" + }, + { + "key": "offset", + "value": "0" + } + ] + } + }, + "response": [] + }, + { + "name": "Get persona by ID", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "const body = pm.response.json();", + "pm.test(\"Has profile_picture field shape\", function () {", + " pm.expect(body.data).to.be.an(\"object\");", + " pm.expect(body.data.name).to.be.a(\"string\");", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "url": { + "raw": "{{base_url}}/api/v1/personas/{{persona_id}}", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "v1", + "personas", + "{{persona_id}}" + ] + } + }, + "response": [] + }, + { + "name": "Update persona", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "const body = pm.response.json();", + "pm.test(\"Updated data returned\", function () {", + " pm.expect(body.data.profile_picture).to.include(\"alex-v2\");", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Postman Coach (updated)\",\n \"profile_picture\": \"https://cdn.example.com/personas/alex-v2.png\"\n}" + }, + "url": { + "raw": "{{base_url}}/api/v1/personas/{{persona_id}}", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "v1", + "personas", + "{{persona_id}}" + ] + } + }, + "response": [] + }, + { + "name": "Delete persona", + "request": { + "method": "DELETE", + "url": { + "raw": "{{base_url}}/api/v1/personas/{{persona_id}}", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "v1", + "personas", + "{{persona_id}}" + ] + } + }, + "response": [] + } + ] + } + ] +}