gitea/routers/api/v1/org/profile.go
logikonline b816ee4eec feat: add Phases 3-5 enhancements (org profiles, pages, wiki v2 API)
Phase 3: Organization Public Profile Page
- Pinned repositories with groups
- Public members display with roles
- API endpoints for pinned repos and groups

Phase 4: Gitea Pages Foundation
- Landing page templates (simple, docs, product, portfolio)
- Custom domain support with verification
- YAML configuration parser (.gitea/landing.yaml)
- Repository settings UI for pages

Phase 5: Enhanced Wiki System with V2 API
- Full CRUD operations via v2 API
- Full-text search with WikiIndex table
- Link graph visualization
- Wiki health metrics (orphaned, dead links, outdated)
- Designed for external AI plugin integration
- Developer guide for .NET integration

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 15:14:27 -05:00

292 lines
7.4 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package org
import (
"net/http"
"code.gitea.io/gitea/models/organization"
user_model "code.gitea.io/gitea/models/user"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
org_service "code.gitea.io/gitea/services/org"
)
// GetOverview returns the organization overview for the profile page
func GetOverview(ctx *context.APIContext) {
// swagger:operation GET /orgs/{org}/overview organization orgGetOverview
// ---
// summary: Get organization overview
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/OrgOverview"
// "404":
// "$ref": "#/responses/notFound"
org := ctx.Org.Organization
// Get pinned repos
pinnedRepos, err := org_service.GetOrgPinnedReposWithDetails(ctx, org.ID)
if err != nil {
ctx.APIErrorInternal(err)
return
}
// Get pinned groups
pinnedGroups, err := organization.GetOrgPinnedGroups(ctx, org.ID)
if err != nil {
ctx.APIErrorInternal(err)
return
}
// Get public members (limit to 12 for overview)
publicMembers, totalMembers, err := organization.GetPublicOrgMembers(ctx, org.ID, 12)
if err != nil {
ctx.APIErrorInternal(err)
return
}
// Get stats
stats, err := org_service.GetOrgOverviewStats(ctx, org.ID)
if err != nil {
ctx.APIErrorInternal(err)
return
}
// Build API response
apiPinnedRepos := make([]*api.OrgPinnedRepo, 0, len(pinnedRepos))
for _, p := range pinnedRepos {
if p.Repo == nil {
continue
}
apiPinnedRepos = append(apiPinnedRepos, convertOrgPinnedRepo(ctx, p))
}
apiPinnedGroups := make([]*api.OrgPinnedGroup, len(pinnedGroups))
for i, g := range pinnedGroups {
apiPinnedGroups[i] = convertOrgPinnedGroup(g)
}
apiPublicMembers := make([]*api.OrgPublicMember, len(publicMembers))
for i, m := range publicMembers {
apiPublicMembers[i] = &api.OrgPublicMember{
User: convert.ToUser(ctx, m.User, ctx.Doer),
Role: m.Role,
}
}
overview := &api.OrgOverview{
Organization: convert.ToOrganization(ctx, org),
PinnedRepos: apiPinnedRepos,
PinnedGroups: apiPinnedGroups,
PublicMembers: apiPublicMembers,
TotalMembers: totalMembers,
Stats: &api.OrgOverviewStats{
MemberCount: stats.MemberCount,
RepoCount: stats.RepoCount,
PublicRepoCount: stats.PublicRepoCount,
TeamCount: stats.TeamCount,
},
}
ctx.JSON(http.StatusOK, overview)
}
// ListPublicMembersWithRoles returns the public members of an organization with their roles
func ListPublicMembersWithRoles(ctx *context.APIContext) {
// swagger:operation GET /orgs/{org}/public_members/roles organization orgListPublicMembersWithRoles
// ---
// summary: List an organization's public members with their roles
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// - name: page
// in: query
// description: page number of results to return (1-based)
// type: integer
// - name: limit
// in: query
// description: page size of results
// type: integer
// responses:
// "200":
// "$ref": "#/responses/OrgPublicMemberList"
// "404":
// "$ref": "#/responses/notFound"
// Get all public members (no limit)
publicMembers, total, err := organization.GetPublicOrgMembers(ctx, ctx.Org.Organization.ID, 0)
if err != nil {
ctx.APIErrorInternal(err)
return
}
apiPublicMembers := make([]*api.OrgPublicMember, len(publicMembers))
for i, m := range publicMembers {
apiPublicMembers[i] = &api.OrgPublicMember{
User: convert.ToUser(ctx, m.User, ctx.Doer),
Role: m.Role,
}
}
ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, apiPublicMembers)
}
// SetPublicMembership sets the public visibility of a member
func SetPublicMembership(ctx *context.APIContext) {
// swagger:operation PUT /orgs/{org}/public_members/{username} organization orgSetPublicMembership
// ---
// summary: Set public membership visibility for a user
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// - name: username
// in: path
// description: username of the user
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
username := ctx.PathParam("username")
// Users can only change their own visibility
if ctx.Doer.Name != username {
isOwner, err := organization.IsOrganizationOwner(ctx, ctx.Org.Organization.ID, ctx.Doer.ID)
if err != nil {
ctx.APIErrorInternal(err)
return
}
if !isOwner {
ctx.APIError(http.StatusForbidden, "You can only change your own public membership visibility")
return
}
}
// Get the user
user, err := user_model.GetUserByName(ctx, username)
if err != nil {
if user_model.IsErrUserNotExist(err) {
ctx.APIErrorNotFound("GetUserByName", err)
return
}
ctx.APIErrorInternal(err)
return
}
// Verify user is a member
isMember, err := organization.IsOrganizationMember(ctx, ctx.Org.Organization.ID, user.ID)
if err != nil {
ctx.APIErrorInternal(err)
return
}
if !isMember {
ctx.APIErrorNotFound()
return
}
if err := organization.SetMemberPublicVisibility(ctx, ctx.Org.Organization.ID, user.ID, true); err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
}
// RemovePublicMembership removes the public visibility of a member
func RemovePublicMembership(ctx *context.APIContext) {
// swagger:operation DELETE /orgs/{org}/public_members/{username} organization orgRemovePublicMembership
// ---
// summary: Remove public membership visibility for a user
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// - name: username
// in: path
// description: username of the user
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
username := ctx.PathParam("username")
// Users can only change their own visibility
if ctx.Doer.Name != username {
isOwner, err := organization.IsOrganizationOwner(ctx, ctx.Org.Organization.ID, ctx.Doer.ID)
if err != nil {
ctx.APIErrorInternal(err)
return
}
if !isOwner {
ctx.APIError(http.StatusForbidden, "You can only change your own public membership visibility")
return
}
}
// Get the user
user, err := user_model.GetUserByName(ctx, username)
if err != nil {
if user_model.IsErrUserNotExist(err) {
ctx.APIErrorNotFound("GetUserByName", err)
return
}
ctx.APIErrorInternal(err)
return
}
// Verify user is a member
isMember, err := organization.IsOrganizationMember(ctx, ctx.Org.Organization.ID, user.ID)
if err != nil {
ctx.APIErrorInternal(err)
return
}
if !isMember {
ctx.APIErrorNotFound()
return
}
if err := organization.SetMemberPublicVisibility(ctx, ctx.Org.Organization.ID, user.ID, false); err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
}