diff --git a/docs/docs.go b/docs/docs.go index 029414b..635d96e 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -25,7 +25,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "auth" + "public" ], "summary": "Login", "parameters": [ @@ -83,7 +83,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "auth" + "public" ], "summary": "Register", "parameters": [ @@ -132,7 +132,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "system" + "public" ], "summary": "Health check", "responses": { @@ -163,7 +163,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "profile" + "user" ], "summary": "Get current user profile", "responses": { @@ -201,7 +201,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "profile" + "user" ], "summary": "Update current user profile", "parameters": [ @@ -261,7 +261,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "notes" + "user" ], "summary": "List notes", "parameters": [ @@ -321,7 +321,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "notes" + "user" ], "summary": "Create a note", "parameters": [ @@ -381,7 +381,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "notes" + "user" ], "summary": "Get a note", "parameters": [ @@ -447,7 +447,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "notes" + "user" ], "summary": "Update a note", "parameters": [ @@ -519,7 +519,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "notes" + "user" ], "summary": "Delete a note", "parameters": [ @@ -576,7 +576,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "site" + "public" ], "summary": "Get site settings", "responses": { @@ -608,7 +608,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "site" + "admin" ], "summary": "Update site settings", "parameters": [ @@ -668,7 +668,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "user-groups" + "admin" ], "summary": "List user groups", "parameters": [ @@ -728,7 +728,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "user-groups" + "admin" ], "summary": "Create a user group", "parameters": [ @@ -794,7 +794,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "user-groups" + "admin" ], "summary": "Get a user group", "parameters": [ @@ -860,7 +860,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "user-groups" + "admin" ], "summary": "Update a user group", "parameters": [ @@ -938,7 +938,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "user-groups" + "admin" ], "summary": "Delete a user group", "parameters": [ @@ -1006,7 +1006,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "users" + "admin" ], "summary": "List users", "parameters": [ @@ -1066,7 +1066,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "users" + "admin" ], "summary": "Create a user", "parameters": [ @@ -1132,7 +1132,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "users" + "admin" ], "summary": "Get a user", "parameters": [ @@ -1198,7 +1198,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "users" + "admin" ], "summary": "Update a user", "parameters": [ @@ -1270,7 +1270,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "users" + "admin" ], "summary": "Delete a user", "parameters": [ @@ -1780,7 +1780,21 @@ const docTemplate = `{ "name": "Authorization", "in": "header" } - } + }, + "tags": [ + { + "description": "No authentication required", + "name": "public" + }, + { + "description": "Requires a logged-in user (Bearer JWT)", + "name": "user" + }, + { + "description": "Requires an admin user (Bearer JWT + admin group)", + "name": "admin" + } + ] }` // SwaggerInfo holds exported Swagger Info so clients can modify it @@ -1790,7 +1804,7 @@ var SwaggerInfo = &swag.Spec{ BasePath: "/api", Schemes: []string{}, Title: "Rill API", - Description: "Rill server HTTP API documentation. All endpoints are prefixed with api.prefix (default /api); requests and responses are JSON.\nSwagger UI: {prefix}/swagger/index.html; OpenAPI JSON: {prefix}/swagger/doc.json.\nExcept for health, swagger, and auth, all endpoints require a Bearer JWT: call /auth/login to get a token, then send Authorization: Bearer {token}.", + Description: "Rill server HTTP API documentation. All endpoints are prefixed with api.prefix (default /api); requests and responses are JSON.\nSwagger UI: {prefix}/swagger/index.html; OpenAPI JSON: {prefix}/swagger/doc.json.\nEndpoints are grouped by required permission: public needs no authentication, user needs a Bearer JWT obtained from /auth/login, admin needs an admin user.", InfoInstanceName: "swagger", SwaggerTemplate: docTemplate, LeftDelim: "{{", diff --git a/docs/swagger.json b/docs/swagger.json index f76eef0..452ebc6 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1,7 +1,7 @@ { "swagger": "2.0", "info": { - "description": "Rill server HTTP API documentation. All endpoints are prefixed with api.prefix (default /api); requests and responses are JSON.\nSwagger UI: {prefix}/swagger/index.html; OpenAPI JSON: {prefix}/swagger/doc.json.\nExcept for health, swagger, and auth, all endpoints require a Bearer JWT: call /auth/login to get a token, then send Authorization: Bearer {token}.", + "description": "Rill server HTTP API documentation. All endpoints are prefixed with api.prefix (default /api); requests and responses are JSON.\nSwagger UI: {prefix}/swagger/index.html; OpenAPI JSON: {prefix}/swagger/doc.json.\nEndpoints are grouped by required permission: public needs no authentication, user needs a Bearer JWT obtained from /auth/login, admin needs an admin user.", "title": "Rill API", "contact": {}, "version": "1.0" @@ -18,7 +18,7 @@ "application/json" ], "tags": [ - "auth" + "public" ], "summary": "Login", "parameters": [ @@ -76,7 +76,7 @@ "application/json" ], "tags": [ - "auth" + "public" ], "summary": "Register", "parameters": [ @@ -125,7 +125,7 @@ "application/json" ], "tags": [ - "system" + "public" ], "summary": "Health check", "responses": { @@ -156,7 +156,7 @@ "application/json" ], "tags": [ - "profile" + "user" ], "summary": "Get current user profile", "responses": { @@ -194,7 +194,7 @@ "application/json" ], "tags": [ - "profile" + "user" ], "summary": "Update current user profile", "parameters": [ @@ -254,7 +254,7 @@ "application/json" ], "tags": [ - "notes" + "user" ], "summary": "List notes", "parameters": [ @@ -314,7 +314,7 @@ "application/json" ], "tags": [ - "notes" + "user" ], "summary": "Create a note", "parameters": [ @@ -374,7 +374,7 @@ "application/json" ], "tags": [ - "notes" + "user" ], "summary": "Get a note", "parameters": [ @@ -440,7 +440,7 @@ "application/json" ], "tags": [ - "notes" + "user" ], "summary": "Update a note", "parameters": [ @@ -512,7 +512,7 @@ "application/json" ], "tags": [ - "notes" + "user" ], "summary": "Delete a note", "parameters": [ @@ -569,7 +569,7 @@ "application/json" ], "tags": [ - "site" + "public" ], "summary": "Get site settings", "responses": { @@ -601,7 +601,7 @@ "application/json" ], "tags": [ - "site" + "admin" ], "summary": "Update site settings", "parameters": [ @@ -661,7 +661,7 @@ "application/json" ], "tags": [ - "user-groups" + "admin" ], "summary": "List user groups", "parameters": [ @@ -721,7 +721,7 @@ "application/json" ], "tags": [ - "user-groups" + "admin" ], "summary": "Create a user group", "parameters": [ @@ -787,7 +787,7 @@ "application/json" ], "tags": [ - "user-groups" + "admin" ], "summary": "Get a user group", "parameters": [ @@ -853,7 +853,7 @@ "application/json" ], "tags": [ - "user-groups" + "admin" ], "summary": "Update a user group", "parameters": [ @@ -931,7 +931,7 @@ "application/json" ], "tags": [ - "user-groups" + "admin" ], "summary": "Delete a user group", "parameters": [ @@ -999,7 +999,7 @@ "application/json" ], "tags": [ - "users" + "admin" ], "summary": "List users", "parameters": [ @@ -1059,7 +1059,7 @@ "application/json" ], "tags": [ - "users" + "admin" ], "summary": "Create a user", "parameters": [ @@ -1125,7 +1125,7 @@ "application/json" ], "tags": [ - "users" + "admin" ], "summary": "Get a user", "parameters": [ @@ -1191,7 +1191,7 @@ "application/json" ], "tags": [ - "users" + "admin" ], "summary": "Update a user", "parameters": [ @@ -1263,7 +1263,7 @@ "application/json" ], "tags": [ - "users" + "admin" ], "summary": "Delete a user", "parameters": [ @@ -1773,5 +1773,19 @@ "name": "Authorization", "in": "header" } - } + }, + "tags": [ + { + "description": "No authentication required", + "name": "public" + }, + { + "description": "Requires a logged-in user (Bearer JWT)", + "name": "user" + }, + { + "description": "Requires an admin user (Bearer JWT + admin group)", + "name": "admin" + } + ] } \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 68052ba..97fa4aa 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -326,7 +326,7 @@ info: description: |- Rill server HTTP API documentation. All endpoints are prefixed with api.prefix (default /api); requests and responses are JSON. Swagger UI: {prefix}/swagger/index.html; OpenAPI JSON: {prefix}/swagger/doc.json. - Except for health, swagger, and auth, all endpoints require a Bearer JWT: call /auth/login to get a token, then send Authorization: Bearer {token}. + Endpoints are grouped by required permission: public needs no authentication, user needs a Bearer JWT obtained from /auth/login, admin needs an admin user. title: Rill API version: "1.0" paths: @@ -368,7 +368,7 @@ paths: $ref: '#/definitions/httpx.ErrorResponse' summary: Login tags: - - auth + - public /auth/register: post: consumes: @@ -404,7 +404,7 @@ paths: $ref: '#/definitions/httpx.ErrorResponse' summary: Register tags: - - auth + - public /health: get: description: Check service and database connectivity; returns 503 when the database @@ -422,7 +422,7 @@ paths: $ref: '#/definitions/api.HealthResponse' summary: Health check tags: - - system + - public /me: get: description: Return the authenticated user's profile, including groups. @@ -445,7 +445,7 @@ paths: - BearerAuth: [] summary: Get current user profile tags: - - profile + - user put: consumes: - application/json @@ -486,7 +486,7 @@ paths: - BearerAuth: [] summary: Update current user profile tags: - - profile + - user /notes: get: description: List notes ordered by id DESC. page starts at 1; page_size is 1-100, @@ -525,7 +525,7 @@ paths: - BearerAuth: [] summary: List notes tags: - - notes + - user post: consumes: - application/json @@ -564,7 +564,7 @@ paths: - BearerAuth: [] summary: Create a note tags: - - notes + - user /notes/{id}: delete: description: Delete a note by id; returns 204 with no body on success. @@ -604,7 +604,7 @@ paths: - BearerAuth: [] summary: Delete a note tags: - - notes + - user get: description: Get a note by id. parameters: @@ -645,7 +645,7 @@ paths: - BearerAuth: [] summary: Get a note tags: - - notes + - user put: consumes: - application/json @@ -694,7 +694,7 @@ paths: - BearerAuth: [] summary: Update a note tags: - - notes + - user /site: get: description: 'Public site settings: site name, logo URL, and footer text. Returns @@ -712,7 +712,7 @@ paths: $ref: '#/definitions/httpx.ErrorResponse' summary: Get site settings tags: - - site + - public put: consumes: - application/json @@ -752,7 +752,7 @@ paths: - BearerAuth: [] summary: Update site settings tags: - - site + - admin /user-groups: get: description: List user groups ordered by id ASC. page starts at 1; page_size @@ -791,7 +791,7 @@ paths: - BearerAuth: [] summary: List user groups tags: - - user-groups + - admin post: consumes: - application/json @@ -835,7 +835,7 @@ paths: - BearerAuth: [] summary: Create a user group tags: - - user-groups + - admin /user-groups/{id}: delete: description: Delete a user group by id; returns 204 with no body on success. @@ -880,7 +880,7 @@ paths: - BearerAuth: [] summary: Delete a user group tags: - - user-groups + - admin get: description: Get a user group by id; id 0 is the built-in admin group. parameters: @@ -921,7 +921,7 @@ paths: - BearerAuth: [] summary: Get a user group tags: - - user-groups + - admin put: consumes: - application/json @@ -975,7 +975,7 @@ paths: - BearerAuth: [] summary: Update a user group tags: - - user-groups + - admin /users: get: description: List users with their groups, ordered by id DESC. page starts at @@ -1014,7 +1014,7 @@ paths: - BearerAuth: [] summary: List users tags: - - users + - admin post: consumes: - application/json @@ -1060,7 +1060,7 @@ paths: - BearerAuth: [] summary: Create a user tags: - - users + - admin /users/{id}: delete: description: Delete a user and their group memberships; returns 204 with no @@ -1101,7 +1101,7 @@ paths: - BearerAuth: [] summary: Delete a user tags: - - users + - admin get: description: Get a user by id, including groups. parameters: @@ -1142,7 +1142,7 @@ paths: - BearerAuth: [] summary: Get a user tags: - - users + - admin put: consumes: - application/json @@ -1193,7 +1193,7 @@ paths: - BearerAuth: [] summary: Update a user tags: - - users + - admin securityDefinitions: BearerAuth: description: 'Bearer JWT, format: Bearer {token}, obtained from /auth/login' @@ -1201,3 +1201,10 @@ securityDefinitions: name: Authorization type: apiKey swagger: "2.0" +tags: +- description: No authentication required + name: public +- description: Requires a logged-in user (Bearer JWT) + name: user +- description: Requires an admin user (Bearer JWT + admin group) + name: admin diff --git a/internal/api/api.go b/internal/api/api.go index a41850f..ab1eeb9 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -88,7 +88,7 @@ type HealthResponse struct { // @Summary Health check // @Description Check service and database connectivity; returns 503 when the database is unavailable. -// @Tags system +// @Tags public // @Produce json // @Success 200 {object} api.HealthResponse // @Failure 503 {object} api.HealthResponse diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 9c64052..5feb0de 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -55,7 +55,7 @@ type LoginResponse struct { // @Summary Register // @Description Public registration. Creates a regular user in the default user group (id 1). username is 3-50 chars and unique; email is unique; password is 6-72 chars. -// @Tags auth +// @Tags public // @Accept json // @Produce json // @Param user body auth.RegisterRequest true "Registration payload" @@ -108,7 +108,7 @@ func Register(db *gorm.DB) gin.HandlerFunc { // @Summary Login // @Description Login with username or email; returns a JWT (TTL from auth.token_ttl) and the user. -// @Tags auth +// @Tags public // @Accept json // @Produce json // @Param credentials body auth.LoginRequest true "Login credentials" diff --git a/internal/auth/profile.go b/internal/auth/profile.go index 0c4c6f3..f1cb105 100644 --- a/internal/auth/profile.go +++ b/internal/auth/profile.go @@ -19,7 +19,7 @@ type UpdateProfileRequest struct { // @Summary Get current user profile // @Description Return the authenticated user's profile, including groups. -// @Tags profile +// @Tags user // @Produce json // @Success 200 {object} model.User // @Security BearerAuth @@ -39,7 +39,7 @@ func Me() gin.HandlerFunc { // @Summary Update current user profile // @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. -// @Tags profile +// @Tags user // @Accept json // @Produce json // @Param profile body auth.UpdateProfileRequest true "Fields to update" diff --git a/internal/note/note.go b/internal/note/note.go index 2f64c7d..73ada5b 100644 --- a/internal/note/note.go +++ b/internal/note/note.go @@ -27,7 +27,7 @@ type ListResponse struct { // @Summary List notes // @Description List notes ordered by id DESC. page starts at 1; page_size is 1-100, default 20. -// @Tags notes +// @Tags user // @Produce json // @Param page query int false "Page number, default 1" example(1) // @Param page_size query int false "Page size, default 20, max 100" example(20) @@ -69,7 +69,7 @@ func List(db *gorm.DB) gin.HandlerFunc { // @Summary Create a note // @Description Create a note. title is required (max 200 chars); content is optional. -// @Tags notes +// @Tags user // @Accept json // @Produce json // @Param note body note.Request true "Note payload" @@ -99,7 +99,7 @@ func Create(db *gorm.DB) gin.HandlerFunc { // @Summary Get a note // @Description Get a note by id. -// @Tags notes +// @Tags user // @Produce json // @Param id path int true "Note ID" example(1) // @Success 200 {object} model.Note @@ -128,7 +128,7 @@ func Get(db *gorm.DB) gin.HandlerFunc { // @Summary Update a note // @Description Update title and content; validation is the same as create. -// @Tags notes +// @Tags user // @Accept json // @Produce json // @Param id path int true "Note ID" example(1) @@ -173,7 +173,7 @@ func Update(db *gorm.DB) gin.HandlerFunc { // @Summary Delete a note // @Description Delete a note by id; returns 204 with no body on success. -// @Tags notes +// @Tags user // @Produce json // @Param id path int true "Note ID" example(1) // @Success 204 "Deleted" diff --git a/internal/site/site.go b/internal/site/site.go index 5719c99..782dd4f 100644 --- a/internal/site/site.go +++ b/internal/site/site.go @@ -22,7 +22,7 @@ type UpdateRequest struct { // @Summary Get site settings // @Description Public site settings: site name, logo URL, and footer text. Returns built-in defaults when the settings row is missing. -// @Tags site +// @Tags public // @Produce json // @Success 200 {object} model.SiteSetting // @Failure 500 {object} httpx.ErrorResponse @@ -45,7 +45,7 @@ func Get(db *gorm.DB) gin.HandlerFunc { // @Summary Update site settings // @Description Admin only. Update the site name, logo URL, and footer text; returns the updated settings. -// @Tags site +// @Tags admin // @Accept json // @Produce json // @Param site body site.UpdateRequest true "Site settings" diff --git a/internal/user/user.go b/internal/user/user.go index 90ec190..fe89ed3 100644 --- a/internal/user/user.go +++ b/internal/user/user.go @@ -54,7 +54,7 @@ type ListResponse struct { // @Summary List users // @Description List users with their groups, ordered by id DESC. page starts at 1; page_size is 1-100, default 20. -// @Tags users +// @Tags admin // @Produce json // @Param page query int false "Page number, default 1" example(1) // @Param page_size query int false "Page size, default 20, max 100" example(20) @@ -100,7 +100,7 @@ func List(db *gorm.DB) gin.HandlerFunc { // @Summary Create a user // @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. -// @Tags users +// @Tags admin // @Accept json // @Produce json // @Param user body user.CreateRequest true "User payload" @@ -178,7 +178,7 @@ func Create(db *gorm.DB) gin.HandlerFunc { // @Summary Get a user // @Description Get a user by id, including groups. -// @Tags users +// @Tags admin // @Produce json // @Param id path int true "User ID" example(1) // @Success 200 {object} model.User @@ -215,7 +215,7 @@ func Get(db *gorm.DB) gin.HandlerFunc { // @Summary Update a user // @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. -// @Tags users +// @Tags admin // @Accept json // @Produce json // @Param id path int true "User ID" example(1) @@ -326,7 +326,7 @@ func Update(db *gorm.DB) gin.HandlerFunc { // @Summary Delete a user // @Description Delete a user and their group memberships; returns 204 with no body on success. -// @Tags users +// @Tags admin // @Produce json // @Param id path int true "User ID" example(1) // @Success 204 "Deleted" diff --git a/internal/usergroup/user_group.go b/internal/usergroup/user_group.go index 1fdfbb0..ae7eb90 100644 --- a/internal/usergroup/user_group.go +++ b/internal/usergroup/user_group.go @@ -28,7 +28,7 @@ type ListResponse struct { // @Summary List user groups // @Description List user groups ordered by id ASC. page starts at 1; page_size is 1-100, default 20. -// @Tags user-groups +// @Tags admin // @Produce json // @Param page query int false "Page number, default 1" example(1) // @Param page_size query int false "Page size, default 20, max 100" example(20) @@ -70,7 +70,7 @@ func List(db *gorm.DB) gin.HandlerFunc { // @Summary Create a user group // @Description Create a user group. name is required and unique (max 50 chars); description is optional (max 255 chars); id is assigned by the server. -// @Tags user-groups +// @Tags admin // @Accept json // @Produce json // @Param group body usergroup.Request true "User group payload" @@ -114,7 +114,7 @@ func Create(db *gorm.DB) gin.HandlerFunc { // @Summary Get a user group // @Description Get a user group by id; id 0 is the built-in admin group. -// @Tags user-groups +// @Tags admin // @Produce json // @Param id path int true "User group ID" example(1) // @Success 200 {object} model.UserGroup @@ -143,7 +143,7 @@ func Get(db *gorm.DB) gin.HandlerFunc { // @Summary Update a user group // @Description Update a user group's name and description; name is required and unique. -// @Tags user-groups +// @Tags admin // @Accept json // @Produce json // @Param id path int true "User group ID" example(1) @@ -189,7 +189,7 @@ func Update(db *gorm.DB) gin.HandlerFunc { // @Summary Delete a user group // @Description Delete a user group by id; returns 204 with no body on success. Returns 409 for system groups or when the group still has members. -// @Tags user-groups +// @Tags admin // @Produce json // @Param id path int true "User group ID" example(2) // @Success 204 "Deleted" diff --git a/main.go b/main.go index fc7587d..3ff9f20 100644 --- a/main.go +++ b/main.go @@ -32,8 +32,14 @@ import ( // @version 1.0 // @description Rill server HTTP API documentation. All endpoints are prefixed with api.prefix (default /api); requests and responses are JSON. // @description Swagger UI: {prefix}/swagger/index.html; OpenAPI JSON: {prefix}/swagger/doc.json. -// @description Except for health, swagger, and auth, all endpoints require a Bearer JWT: call /auth/login to get a token, then send Authorization: Bearer {token}. +// @description Endpoints are grouped by required permission: public needs no authentication, user needs a Bearer JWT obtained from /auth/login, admin needs an admin user. // @BasePath /api +// @tag.name public +// @tag.description No authentication required +// @tag.name user +// @tag.description Requires a logged-in user (Bearer JWT) +// @tag.name admin +// @tag.description Requires an admin user (Bearer JWT + admin group) // @securityDefinitions.apikey BearerAuth // @in header // @name Authorization