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>
135 lines
4.1 KiB
Go
135 lines
4.1 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package repo
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"code.gitea.io/gitea/models/db"
|
|
"code.gitea.io/gitea/modules/timeutil"
|
|
)
|
|
|
|
func init() {
|
|
db.RegisterModel(new(WikiIndex))
|
|
}
|
|
|
|
// WikiIndex stores the searchable index for wiki pages
|
|
type WikiIndex struct {
|
|
ID int64 `xorm:"pk autoincr"`
|
|
RepoID int64 `xorm:"INDEX NOT NULL"`
|
|
PageName string `xorm:"VARCHAR(255) NOT NULL"`
|
|
PagePath string `xorm:"VARCHAR(512) NOT NULL"` // Git path
|
|
Title string `xorm:"VARCHAR(255)"`
|
|
Content string `xorm:"LONGTEXT"` // Full content for search
|
|
ContentHash string `xorm:"VARCHAR(64)"` // For change detection
|
|
CommitSHA string `xorm:"VARCHAR(64)"` // Last indexed commit
|
|
WordCount int `xorm:"DEFAULT 0"`
|
|
LinksOut string `xorm:"TEXT"` // JSON array of outgoing links
|
|
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
|
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
|
}
|
|
|
|
// TableName returns the table name for WikiIndex
|
|
func (w *WikiIndex) TableName() string {
|
|
return "wiki_index"
|
|
}
|
|
|
|
// GetWikiIndex returns the wiki index for a specific page
|
|
func GetWikiIndex(ctx context.Context, repoID int64, pageName string) (*WikiIndex, error) {
|
|
idx := new(WikiIndex)
|
|
has, err := db.GetEngine(ctx).Where("repo_id = ? AND page_name = ?", repoID, pageName).Get(idx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !has {
|
|
return nil, nil
|
|
}
|
|
return idx, nil
|
|
}
|
|
|
|
// GetWikiIndexByRepo returns all indexed pages for a repository
|
|
func GetWikiIndexByRepo(ctx context.Context, repoID int64) ([]*WikiIndex, error) {
|
|
indexes := make([]*WikiIndex, 0)
|
|
return indexes, db.GetEngine(ctx).Where("repo_id = ?", repoID).OrderBy("page_name ASC").Find(&indexes)
|
|
}
|
|
|
|
// CreateOrUpdateWikiIndex creates or updates a wiki index entry
|
|
func CreateOrUpdateWikiIndex(ctx context.Context, idx *WikiIndex) error {
|
|
existing, err := GetWikiIndex(ctx, idx.RepoID, idx.PageName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if existing == nil {
|
|
_, err = db.GetEngine(ctx).Insert(idx)
|
|
return err
|
|
}
|
|
|
|
idx.ID = existing.ID
|
|
_, err = db.GetEngine(ctx).ID(existing.ID).AllCols().Update(idx)
|
|
return err
|
|
}
|
|
|
|
// DeleteWikiIndex deletes a wiki index entry
|
|
func DeleteWikiIndex(ctx context.Context, repoID int64, pageName string) error {
|
|
_, err := db.GetEngine(ctx).Where("repo_id = ? AND page_name = ?", repoID, pageName).Delete(new(WikiIndex))
|
|
return err
|
|
}
|
|
|
|
// DeleteWikiIndexByRepo deletes all wiki indexes for a repository
|
|
func DeleteWikiIndexByRepo(ctx context.Context, repoID int64) error {
|
|
_, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Delete(new(WikiIndex))
|
|
return err
|
|
}
|
|
|
|
// SearchWikiOptions contains options for wiki search
|
|
type SearchWikiOptions struct {
|
|
RepoID int64
|
|
Query string
|
|
Limit int
|
|
Offset int
|
|
}
|
|
|
|
// SearchWikiPages searches wiki pages by content
|
|
func SearchWikiPages(ctx context.Context, opts *SearchWikiOptions) ([]*WikiIndex, int64, error) {
|
|
if opts.Limit <= 0 {
|
|
opts.Limit = 20
|
|
}
|
|
|
|
sess := db.GetEngine(ctx).Where("repo_id = ?", opts.RepoID)
|
|
|
|
// Simple LIKE search - searches title and content
|
|
if opts.Query != "" {
|
|
query := "%" + strings.ToLower(opts.Query) + "%"
|
|
sess = sess.And("(LOWER(title) LIKE ? OR LOWER(content) LIKE ?)", query, query)
|
|
}
|
|
|
|
// Get total count
|
|
total, err := sess.Count(new(WikiIndex))
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
// Get results
|
|
indexes := make([]*WikiIndex, 0, opts.Limit)
|
|
err = sess.Limit(opts.Limit, opts.Offset).Find(&indexes)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
return indexes, total, nil
|
|
}
|
|
|
|
// GetWikiIndexCount returns the number of indexed pages for a repository
|
|
func GetWikiIndexCount(ctx context.Context, repoID int64) (int64, error) {
|
|
return db.GetEngine(ctx).Where("repo_id = ?", repoID).Count(new(WikiIndex))
|
|
}
|
|
|
|
// GetWikiTotalWordCount returns the total word count for a repository's wiki
|
|
func GetWikiTotalWordCount(ctx context.Context, repoID int64) (int64, error) {
|
|
total, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).SumInt(new(WikiIndex), "word_count")
|
|
return total, err
|
|
}
|