diff --git a/docs/docs.go b/docs/docs.go index d4cda1a..35b6ad9 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -151,6 +151,104 @@ const docTemplate = `{ } } }, + "/me": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Return the authenticated user's profile, including groups.", + "produces": [ + "application/json" + ], + "tags": [ + "profile" + ], + "summary": "Get current user profile", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.User" + } + }, + "401": { + "description": "unauthorized or session expired", + "schema": { + "$ref": "#/definitions/httpx.ErrorResponse" + } + }, + "403": { + "description": "account disabled", + "schema": { + "$ref": "#/definitions/httpx.ErrorResponse" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update the authenticated user's nickname, gender, and birthday. birthday accepts YYYY-MM-DD, an empty string clears it, and omitting it keeps the current value.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "profile" + ], + "summary": "Update current user profile", + "parameters": [ + { + "description": "Fields to update", + "name": "profile", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/auth.UpdateProfileRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.User" + } + }, + "400": { + "description": "invalid request", + "schema": { + "$ref": "#/definitions/httpx.ErrorResponse" + } + }, + "401": { + "description": "unauthorized or session expired", + "schema": { + "$ref": "#/definitions/httpx.ErrorResponse" + } + }, + "403": { + "description": "account disabled", + "schema": { + "$ref": "#/definitions/httpx.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/httpx.ErrorResponse" + } + } + } + } + }, "/notes": { "get": { "security": [ @@ -873,7 +971,7 @@ const docTemplate = `{ "BearerAuth": [] } ], - "description": "Create a user and assign groups. username and email are unique; password is 6-72 chars; defaults to the regular user group (id 1) when group_ids is omitted.", + "description": "Create a user and assign groups. username and email are unique; password is 6-72 chars; gender is male/female/other; birthday is YYYY-MM-DD and cannot be in the future; defaults to the regular user group (id 1) when group_ids is omitted.", "consumes": [ "application/json" ], @@ -1005,7 +1103,7 @@ const docTemplate = `{ "BearerAuth": [] } ], - "description": "Update user fields. group_ids replaces all groups; a non-empty password resets the password.", + "description": "Update user fields. group_ids replaces all groups; a non-empty password resets the password; birthday accepts YYYY-MM-DD, an empty string clears it, and omitting it keeps the current value.", "consumes": [ "application/json" ], @@ -1210,6 +1308,24 @@ const docTemplate = `{ } } }, + "auth.UpdateProfileRequest": { + "type": "object", + "properties": { + "birthday": { + "type": "string", + "example": "1995-06-15" + }, + "gender": { + "type": "string", + "example": "male" + }, + "nickname": { + "type": "string", + "maxLength": 50, + "example": "Alice" + } + } + }, "httpx.ErrorResponse": { "type": "object", "properties": { @@ -1245,12 +1361,20 @@ const docTemplate = `{ "avatar": { "type": "string" }, + "birthday": { + "type": "string", + "example": "1995-06-15" + }, "created_at": { "type": "string" }, "email": { "type": "string" }, + "gender": { + "type": "string", + "example": "male" + }, "groups": { "type": "array", "items": { @@ -1350,11 +1474,24 @@ const docTemplate = `{ "maxLength": 255, "example": "https://example.com/avatar.png" }, + "birthday": { + "type": "string", + "example": "1995-06-15" + }, "email": { "type": "string", "maxLength": 255, "example": "alice@example.com" }, + "gender": { + "type": "string", + "enum": [ + "male", + "female", + "other" + ], + "example": "male" + }, "group_ids": { "type": "array", "items": { @@ -1421,6 +1558,19 @@ const docTemplate = `{ "maxLength": 255, "example": "https://example.com/avatar.png" }, + "birthday": { + "type": "string", + "example": "1995-06-15" + }, + "gender": { + "type": "string", + "enum": [ + "male", + "female", + "other" + ], + "example": "male" + }, "group_ids": { "type": "array", "items": { diff --git a/docs/swagger.json b/docs/swagger.json index 828b239..80141d5 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -144,6 +144,104 @@ } } }, + "/me": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Return the authenticated user's profile, including groups.", + "produces": [ + "application/json" + ], + "tags": [ + "profile" + ], + "summary": "Get current user profile", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.User" + } + }, + "401": { + "description": "unauthorized or session expired", + "schema": { + "$ref": "#/definitions/httpx.ErrorResponse" + } + }, + "403": { + "description": "account disabled", + "schema": { + "$ref": "#/definitions/httpx.ErrorResponse" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update the authenticated user's nickname, gender, and birthday. birthday accepts YYYY-MM-DD, an empty string clears it, and omitting it keeps the current value.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "profile" + ], + "summary": "Update current user profile", + "parameters": [ + { + "description": "Fields to update", + "name": "profile", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/auth.UpdateProfileRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.User" + } + }, + "400": { + "description": "invalid request", + "schema": { + "$ref": "#/definitions/httpx.ErrorResponse" + } + }, + "401": { + "description": "unauthorized or session expired", + "schema": { + "$ref": "#/definitions/httpx.ErrorResponse" + } + }, + "403": { + "description": "account disabled", + "schema": { + "$ref": "#/definitions/httpx.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/httpx.ErrorResponse" + } + } + } + } + }, "/notes": { "get": { "security": [ @@ -866,7 +964,7 @@ "BearerAuth": [] } ], - "description": "Create a user and assign groups. username and email are unique; password is 6-72 chars; defaults to the regular user group (id 1) when group_ids is omitted.", + "description": "Create a user and assign groups. username and email are unique; password is 6-72 chars; gender is male/female/other; birthday is YYYY-MM-DD and cannot be in the future; defaults to the regular user group (id 1) when group_ids is omitted.", "consumes": [ "application/json" ], @@ -998,7 +1096,7 @@ "BearerAuth": [] } ], - "description": "Update user fields. group_ids replaces all groups; a non-empty password resets the password.", + "description": "Update user fields. group_ids replaces all groups; a non-empty password resets the password; birthday accepts YYYY-MM-DD, an empty string clears it, and omitting it keeps the current value.", "consumes": [ "application/json" ], @@ -1203,6 +1301,24 @@ } } }, + "auth.UpdateProfileRequest": { + "type": "object", + "properties": { + "birthday": { + "type": "string", + "example": "1995-06-15" + }, + "gender": { + "type": "string", + "example": "male" + }, + "nickname": { + "type": "string", + "maxLength": 50, + "example": "Alice" + } + } + }, "httpx.ErrorResponse": { "type": "object", "properties": { @@ -1238,12 +1354,20 @@ "avatar": { "type": "string" }, + "birthday": { + "type": "string", + "example": "1995-06-15" + }, "created_at": { "type": "string" }, "email": { "type": "string" }, + "gender": { + "type": "string", + "example": "male" + }, "groups": { "type": "array", "items": { @@ -1343,11 +1467,24 @@ "maxLength": 255, "example": "https://example.com/avatar.png" }, + "birthday": { + "type": "string", + "example": "1995-06-15" + }, "email": { "type": "string", "maxLength": 255, "example": "alice@example.com" }, + "gender": { + "type": "string", + "enum": [ + "male", + "female", + "other" + ], + "example": "male" + }, "group_ids": { "type": "array", "items": { @@ -1414,6 +1551,19 @@ "maxLength": 255, "example": "https://example.com/avatar.png" }, + "birthday": { + "type": "string", + "example": "1995-06-15" + }, + "gender": { + "type": "string", + "enum": [ + "male", + "female", + "other" + ], + "example": "male" + }, "group_ids": { "type": "array", "items": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 0a5edef..89216f2 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -53,6 +53,19 @@ definitions: - password - username type: object + auth.UpdateProfileRequest: + properties: + birthday: + example: "1995-06-15" + type: string + gender: + example: male + type: string + nickname: + example: Alice + maxLength: 50 + type: string + type: object httpx.ErrorResponse: properties: error: @@ -76,10 +89,16 @@ definitions: properties: avatar: type: string + birthday: + example: "1995-06-15" + type: string created_at: type: string email: type: string + gender: + example: male + type: string groups: items: $ref: '#/definitions/model.UserGroup' @@ -144,10 +163,20 @@ definitions: example: https://example.com/avatar.png maxLength: 255 type: string + birthday: + example: "1995-06-15" + type: string email: example: alice@example.com maxLength: 255 type: string + gender: + enum: + - male + - female + - other + example: male + type: string group_ids: example: - 1 @@ -200,6 +229,16 @@ definitions: example: https://example.com/avatar.png maxLength: 255 type: string + birthday: + example: "1995-06-15" + type: string + gender: + enum: + - male + - female + - other + example: male + type: string group_ids: example: - 1 @@ -353,6 +392,70 @@ paths: summary: Health check tags: - system + /me: + get: + description: Return the authenticated user's profile, including groups. + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/model.User' + "401": + description: unauthorized or session expired + schema: + $ref: '#/definitions/httpx.ErrorResponse' + "403": + description: account disabled + schema: + $ref: '#/definitions/httpx.ErrorResponse' + security: + - BearerAuth: [] + summary: Get current user profile + tags: + - profile + put: + consumes: + - application/json + description: Update the authenticated user's nickname, gender, and birthday. + birthday accepts YYYY-MM-DD, an empty string clears it, and omitting it keeps + the current value. + parameters: + - description: Fields to update + in: body + name: profile + required: true + schema: + $ref: '#/definitions/auth.UpdateProfileRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/model.User' + "400": + description: invalid request + schema: + $ref: '#/definitions/httpx.ErrorResponse' + "401": + description: unauthorized or session expired + schema: + $ref: '#/definitions/httpx.ErrorResponse' + "403": + description: account disabled + schema: + $ref: '#/definitions/httpx.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/httpx.ErrorResponse' + security: + - BearerAuth: [] + summary: Update current user profile + tags: + - profile /notes: get: description: List notes ordered by id DESC. page starts at 1; page_size is 1-100, @@ -827,8 +930,9 @@ paths: consumes: - application/json description: Create a user and assign groups. username and email are unique; - password is 6-72 chars; defaults to the regular user group (id 1) when group_ids - is omitted. + password is 6-72 chars; gender is male/female/other; birthday is YYYY-MM-DD + and cannot be in the future; defaults to the regular user group (id 1) when + group_ids is omitted. parameters: - description: User payload in: body @@ -954,7 +1058,8 @@ paths: consumes: - application/json description: Update user fields. group_ids replaces all groups; a non-empty - password resets the password. + password resets the password; birthday accepts YYYY-MM-DD, an empty string + clears it, and omitting it keeps the current value. parameters: - description: User ID example: 1 diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts index e6d0eb6..003598a 100644 --- a/frontend/src/api/auth.ts +++ b/frontend/src/api/auth.ts @@ -13,6 +13,8 @@ export interface AuthUser { email: string nickname: string avatar: string + gender: string + birthday: string | null status: number groups: AuthUserGroup[] } diff --git a/frontend/src/api/profile.ts b/frontend/src/api/profile.ts new file mode 100644 index 0000000..da03966 --- /dev/null +++ b/frontend/src/api/profile.ts @@ -0,0 +1,19 @@ +import { request } from './http' +import type { AuthUser } from './auth' + +export interface UpdateProfilePayload { + nickname: string + gender: string + birthday: string +} + +export function getProfile(): Promise { + return request('/me') +} + +export function updateProfile(payload: UpdateProfilePayload): Promise { + return request('/me', { + method: 'PUT', + body: JSON.stringify(payload), + }) +} diff --git a/frontend/src/components/layout/AppHeader.vue b/frontend/src/components/layout/AppHeader.vue index 4c3a83b..23a1532 100644 --- a/frontend/src/components/layout/AppHeader.vue +++ b/frontend/src/components/layout/AppHeader.vue @@ -95,6 +95,13 @@ function logout() { v-if="menuOpen" class="absolute right-0 top-full mt-2 w-36 overflow-hidden rounded-xl border border-line bg-surface py-1 shadow-lg" > + + {{ t('header.profile') }} +