1caf26453f
Generic: Project CI / Tests (push) Blocked by required conditions
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 12s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 41s
Generic: Project CI / Lint & Validate (pull_request) Successful in 43s
Generic: Project CI / Lint & Validate (push) Successful in 45s
PR RC Release / Build RC Release (pull_request) Failing after 39s
Universal: Build & Release / Build & Release Pipeline (pull_request) Failing after 31s
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Issue Status/Priority/Type API:
- Expose status_id, priority_id, type_id (with resolved names) on Issue API struct
- New endpoints: GET /orgs/{org}/issue-statuses, /issue-priorities, /issue-types
- CreateIssue and EditIssue handlers accept status_id, priority_id, type_id
- MCP tools: 5 new tools + updated create/update with metadata params
Org Wiki Tab:
- Convention repos: wiki (public) and wiki-private (members-only)
- Inline wiki rendering with markdown pipeline, sidebar, footer, page list
- Public/private view dropdown (same UX as org profile README selector)
- External wiki mode: link to outside URL from wiki tab
- Wiki mode setting in org settings (internal vs external with URL field)
- Migration 354: add wiki_mode and wiki_url to user table
220 lines
8.2 KiB
Go
220 lines
8.2 KiB
Go
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package user
|
|
|
|
import (
|
|
"errors"
|
|
"net/url"
|
|
|
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/organization"
|
|
access_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/perm/access"
|
|
project_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/project"
|
|
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/unit"
|
|
user_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
|
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/git"
|
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/gitrepo"
|
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
|
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/markup"
|
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/markup/markdown"
|
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/optional"
|
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/util"
|
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
|
|
)
|
|
|
|
// prepareContextForProfileBigAvatar set the context for big avatar view on the profile page
|
|
func prepareContextForProfileBigAvatar(ctx *context.Context) {
|
|
ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
|
|
ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail && ctx.ContextUser.Email != "" && ctx.IsSigned && !ctx.ContextUser.KeepEmailPrivate
|
|
if setting.Service.UserLocationMapURL != "" {
|
|
ctx.Data["ContextUserLocationMapURL"] = setting.Service.UserLocationMapURL + url.QueryEscape(ctx.ContextUser.Location)
|
|
}
|
|
// Show OpenID URIs
|
|
openIDs, err := user_model.GetUserOpenIDs(ctx, ctx.ContextUser.ID)
|
|
if err != nil {
|
|
ctx.ServerError("GetUserOpenIDs", err)
|
|
return
|
|
}
|
|
ctx.Data["OpenIDs"] = openIDs
|
|
if len(ctx.ContextUser.Description) != 0 {
|
|
content, err := markdown.RenderString(markup.NewRenderContext(ctx).WithMetas(markup.ComposeSimpleDocumentMetas()), ctx.ContextUser.Description)
|
|
if err != nil {
|
|
ctx.ServerError("RenderString", err)
|
|
return
|
|
}
|
|
ctx.Data["RenderedDescription"] = content
|
|
}
|
|
|
|
orgs, err := db.Find[organization.Organization](ctx, organization.FindOrgOptions{
|
|
UserID: ctx.ContextUser.ID,
|
|
IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, ctx.ContextUser),
|
|
ListOptions: db.ListOptions{
|
|
Page: 1,
|
|
// query one more result (without a separate counting) to see whether we need to add the "show more orgs" link
|
|
PageSize: setting.UI.User.OrgPagingNum + 1,
|
|
},
|
|
})
|
|
if err != nil {
|
|
ctx.ServerError("FindOrgs", err)
|
|
return
|
|
}
|
|
if len(orgs) > setting.UI.User.OrgPagingNum {
|
|
orgs = orgs[:setting.UI.User.OrgPagingNum]
|
|
ctx.Data["ShowMoreOrgs"] = true
|
|
}
|
|
ctx.Data["Orgs"] = orgs
|
|
ctx.Data["HasOrgsVisible"] = organization.HasOrgsVisible(ctx, orgs, ctx.Doer)
|
|
|
|
badges, _, err := user_model.GetUserBadges(ctx, ctx.ContextUser)
|
|
if err != nil {
|
|
ctx.ServerError("GetUserBadges", err)
|
|
return
|
|
}
|
|
ctx.Data["Badges"] = badges
|
|
|
|
// in case the numbers are already provided by other functions, no need to query again (which is slow)
|
|
if _, ok := ctx.Data["NumFollowers"]; !ok {
|
|
_, ctx.Data["NumFollowers"], _ = user_model.GetUserFollowers(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{PageSize: 1, Page: 1})
|
|
}
|
|
if _, ok := ctx.Data["NumFollowing"]; !ok {
|
|
_, ctx.Data["NumFollowing"], _ = user_model.GetUserFollowing(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{PageSize: 1, Page: 1})
|
|
}
|
|
|
|
if ctx.Doer != nil {
|
|
ctx.Data["UserBlocking"], err = user_model.GetBlocking(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
|
|
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
|
ctx.ServerError("GetBlocking", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func FindOwnerProfileReadme(ctx *context.Context, doer *user_model.User, optProfileRepoName ...string) (profileDbRepo *repo_model.Repository, profileReadmeBlob *git.Blob) {
|
|
profileRepoName := util.OptionalArg(optProfileRepoName, RepoNameProfile)
|
|
profileDbRepo, err := repo_model.GetRepositoryByName(ctx, ctx.ContextUser.ID, profileRepoName)
|
|
if err != nil {
|
|
if !repo_model.IsErrRepoNotExist(err) {
|
|
log.Error("FindOwnerProfileReadme failed to GetRepositoryByName: %v", err)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
perm, err := access_model.GetDoerRepoPermission(ctx, profileDbRepo, doer)
|
|
if err != nil {
|
|
log.Error("FindOwnerProfileReadme failed to GetRepositoryByName: %v", err)
|
|
return nil, nil
|
|
}
|
|
if profileDbRepo.IsEmpty || !perm.CanRead(unit.TypeCode) {
|
|
return nil, nil
|
|
}
|
|
|
|
profileGitRepo, err := gitrepo.RepositoryFromRequestContextOrOpen(ctx, profileDbRepo)
|
|
if err != nil {
|
|
log.Error("FindOwnerProfileReadme failed to OpenRepository: %v", err)
|
|
return nil, nil
|
|
}
|
|
|
|
commit, err := profileGitRepo.GetBranchCommit(profileDbRepo.DefaultBranch)
|
|
if err != nil {
|
|
log.Error("FindOwnerProfileReadme failed to GetBranchCommit: %v", err)
|
|
return nil, nil
|
|
}
|
|
|
|
profileReadmeBlob, _ = commit.GetBlobByPath("README.md") // no need to handle this error
|
|
return profileDbRepo, profileReadmeBlob
|
|
}
|
|
|
|
type PrepareOwnerHeaderResult struct {
|
|
ProfilePublicRepo *repo_model.Repository
|
|
ProfilePublicReadmeBlob *git.Blob
|
|
ProfilePrivateRepo *repo_model.Repository
|
|
ProfilePrivateReadmeBlob *git.Blob
|
|
HasOrgProfileReadme bool
|
|
}
|
|
|
|
const (
|
|
RepoNameProfilePrivate = ".profile-private"
|
|
RepoNameProfile = ".profile"
|
|
RepoNameWikiPublic = "wiki"
|
|
RepoNameWikiPrivate = "wiki-private"
|
|
)
|
|
|
|
func RenderUserOrgHeader(ctx *context.Context) (result *PrepareOwnerHeaderResult, err error) {
|
|
ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
|
|
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
|
|
ctx.Data["EnableFeed"] = setting.Other.EnableFeed
|
|
ctx.Data["FeedURL"] = ctx.ContextUser.HomeLink()
|
|
|
|
if err := loadHeaderCount(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result = &PrepareOwnerHeaderResult{}
|
|
if ctx.ContextUser.IsOrganization() {
|
|
result.ProfilePublicRepo, result.ProfilePublicReadmeBlob = FindOwnerProfileReadme(ctx, ctx.Doer)
|
|
result.ProfilePrivateRepo, result.ProfilePrivateReadmeBlob = FindOwnerProfileReadme(ctx, ctx.Doer, RepoNameProfilePrivate)
|
|
result.HasOrgProfileReadme = result.ProfilePublicReadmeBlob != nil || result.ProfilePrivateReadmeBlob != nil
|
|
ctx.Data["HasOrgProfileReadme"] = result.HasOrgProfileReadme // many pages need it to show the "overview" tab
|
|
|
|
// Check if org has a wiki (internal convention repos or external URL).
|
|
orgUser := ctx.ContextUser
|
|
if orgUser.WikiMode == "external" && orgUser.WikiURL != "" {
|
|
ctx.Data["HasOrgWiki"] = true
|
|
ctx.Data["OrgWikiIsExternal"] = true
|
|
ctx.Data["OrgWikiExternalURL"] = orgUser.WikiURL
|
|
} else {
|
|
hasWiki := OrgWikiRepoExists(ctx, ctx.ContextUser.ID, RepoNameWikiPublic) ||
|
|
OrgWikiRepoExists(ctx, ctx.ContextUser.ID, RepoNameWikiPrivate)
|
|
ctx.Data["HasOrgWiki"] = hasWiki
|
|
}
|
|
} else {
|
|
_, profileReadmeBlob := FindOwnerProfileReadme(ctx, ctx.Doer)
|
|
ctx.Data["HasUserProfileReadme"] = profileReadmeBlob != nil
|
|
prepareContextForProfileBigAvatar(ctx)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func loadHeaderCount(ctx *context.Context) error {
|
|
repoCount, err := repo_model.CountRepository(ctx, repo_model.SearchRepoOptions{
|
|
Actor: ctx.Doer,
|
|
OwnerID: ctx.ContextUser.ID,
|
|
Private: ctx.IsSigned,
|
|
Collaborate: optional.Some(false),
|
|
IncludeDescription: setting.UI.SearchRepoDescription,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx.Data["RepoCount"] = repoCount
|
|
|
|
var projectType project_model.Type
|
|
if ctx.ContextUser.IsOrganization() {
|
|
projectType = project_model.TypeOrganization
|
|
} else {
|
|
projectType = project_model.TypeIndividual
|
|
}
|
|
projectCount, err := db.Count[project_model.Project](ctx, project_model.SearchOptions{
|
|
OwnerID: ctx.ContextUser.ID,
|
|
IsClosed: optional.Some(false),
|
|
Type: projectType,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx.Data["ProjectCount"] = projectCount
|
|
|
|
return nil
|
|
}
|
|
|
|
// OrgWikiRepoExists checks whether a convention wiki repo exists and is non-empty.
|
|
func OrgWikiRepoExists(ctx *context.Context, ownerID int64, repoName string) bool {
|
|
dbRepo, err := repo_model.GetRepositoryByName(ctx, ownerID, repoName)
|
|
if err != nil || dbRepo.IsEmpty {
|
|
return false
|
|
}
|
|
return true
|
|
}
|