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>
292 lines
7.4 KiB
Go
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)
|
|
}
|