1
1
mirror of https://github.com/go-gitea/gitea synced 2025-12-07 13:28:25 +00:00

Merge branch 'main' into Badge

This commit is contained in:
techknowlogick
2024-10-08 12:25:04 -04:00
committed by GitHub
269 changed files with 8929 additions and 1999 deletions
+4 -12
View File
@@ -452,13 +452,10 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
actions := make([]*Action, 0, opts.PageSize)
var count int64
opts.SetDefaultValues()
if opts.Page < 10 { // TODO: why it's 10 but other values? It's an experience value.
sess := db.GetEngine(ctx).Where(cond).
Select("`action`.*"). // this line will avoid select other joined table's columns
Join("INNER", "repository", "`repository`.id = `action`.repo_id")
opts.SetDefaultValues()
sess := db.GetEngine(ctx).Where(cond)
sess = db.SetSessionPagination(sess, &opts)
count, err = sess.Desc("`action`.created_unix").FindAndCount(&actions)
@@ -467,11 +464,7 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
}
} else {
// First, only query which IDs are necessary, and only then query all actions to speed up the overall query
sess := db.GetEngine(ctx).Where(cond).
Select("`action`.id").
Join("INNER", "repository", "`repository`.id = `action`.repo_id")
opts.SetDefaultValues()
sess := db.GetEngine(ctx).Where(cond).Select("`action`.id")
sess = db.SetSessionPagination(sess, &opts)
actionIDs := make([]int64, 0, opts.PageSize)
@@ -481,8 +474,7 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
count, err = db.GetEngine(ctx).Where(cond).
Table("action").
Cols("`action`.id").
Join("INNER", "repository", "`repository`.id = `action`.repo_id").Count()
Cols("`action`.id").Count()
if err != nil {
return nil, 0, fmt.Errorf("Count: %w", err)
}
+4 -2
View File
@@ -228,6 +228,8 @@ func TestNotifyWatchers(t *testing.T) {
}
func TestGetFeedsCorrupted(t *testing.T) {
// Now we will not check for corrupted data in the feeds
// users should run doctor to fix their data
assert.NoError(t, unittest.PrepareTestDatabase())
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
unittest.AssertExistsAndLoadBean(t, &activities_model.Action{
@@ -241,8 +243,8 @@ func TestGetFeedsCorrupted(t *testing.T) {
IncludePrivate: true,
})
assert.NoError(t, err)
assert.Len(t, actions, 0)
assert.Equal(t, int64(0), count)
assert.Len(t, actions, 1)
assert.Equal(t, int64(1), count)
}
func TestConsistencyUpdateAction(t *testing.T) {
+28 -2
View File
@@ -34,6 +34,7 @@ type ActivityStats struct {
OpenedPRAuthorCount int64
MergedPRs issues_model.PullRequestList
MergedPRAuthorCount int64
ActiveIssues issues_model.IssueList
OpenedIssues issues_model.IssueList
OpenedIssueAuthorCount int64
ClosedIssues issues_model.IssueList
@@ -172,7 +173,7 @@ func (stats *ActivityStats) MergedPRPerc() int {
// ActiveIssueCount returns total active issue count
func (stats *ActivityStats) ActiveIssueCount() int {
return stats.OpenedIssueCount() + stats.ClosedIssueCount()
return len(stats.ActiveIssues)
}
// OpenedIssueCount returns open issue count
@@ -285,13 +286,21 @@ func (stats *ActivityStats) FillIssues(ctx context.Context, repoID int64, fromTi
stats.ClosedIssueAuthorCount = count
// New issues
sess = issuesForActivityStatement(ctx, repoID, fromTime, false, false)
sess = newlyCreatedIssues(ctx, repoID, fromTime)
sess.OrderBy("issue.created_unix ASC")
stats.OpenedIssues = make(issues_model.IssueList, 0)
if err = sess.Find(&stats.OpenedIssues); err != nil {
return err
}
// Active issues
sess = activeIssues(ctx, repoID, fromTime)
sess.OrderBy("issue.created_unix ASC")
stats.ActiveIssues = make(issues_model.IssueList, 0)
if err = sess.Find(&stats.ActiveIssues); err != nil {
return err
}
// Opened issue authors
sess = issuesForActivityStatement(ctx, repoID, fromTime, false, false)
if _, err = sess.Select("count(distinct issue.poster_id) as `count`").Table("issue").Get(&count); err != nil {
@@ -317,6 +326,23 @@ func (stats *ActivityStats) FillUnresolvedIssues(ctx context.Context, repoID int
return sess.Find(&stats.UnresolvedIssues)
}
func newlyCreatedIssues(ctx context.Context, repoID int64, fromTime time.Time) *xorm.Session {
sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID).
And("issue.is_pull = ?", false). // Retain the is_pull check to exclude pull requests
And("issue.created_unix >= ?", fromTime.Unix()) // Include all issues created after fromTime
return sess
}
func activeIssues(ctx context.Context, repoID int64, fromTime time.Time) *xorm.Session {
sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID).
And("issue.is_pull = ?", false).
And("issue.created_unix >= ?", fromTime.Unix()).
Or("issue.closed_unix >= ?", fromTime.Unix())
return sess
}
func issuesForActivityStatement(ctx context.Context, repoID int64, fromTime time.Time, closed, unresolved bool) *xorm.Session {
sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID).
And("issue.is_closed = ?", closed)
-21
View File
@@ -179,27 +179,6 @@ func GetMigratingTask(ctx context.Context, repoID int64) (*Task, error) {
return &task, nil
}
// GetMigratingTaskByID returns the migrating task by repo's id
func GetMigratingTaskByID(ctx context.Context, id, doerID int64) (*Task, *migration.MigrateOptions, error) {
task := Task{
ID: id,
DoerID: doerID,
Type: structs.TaskTypeMigrateRepo,
}
has, err := db.GetEngine(ctx).Get(&task)
if err != nil {
return nil, nil, err
} else if !has {
return nil, nil, ErrTaskDoesNotExist{id, 0, task.Type}
}
var opts migration.MigrateOptions
if err := json.Unmarshal([]byte(task.PayloadContent), &opts); err != nil {
return nil, nil, err
}
return &task, &opts, nil
}
// CreateTask creates a task on database
func CreateTask(ctx context.Context, task *Task) error {
return db.Insert(ctx, task)
+2 -2
View File
@@ -114,7 +114,7 @@ func readArmoredSign(r io.Reader) (body io.Reader, err error) {
return nil, err
}
if block.Type != openpgp.SignatureType {
return nil, fmt.Errorf("expected '" + openpgp.SignatureType + "', got: " + block.Type)
return nil, fmt.Errorf("expected '%s', got: %s", openpgp.SignatureType, block.Type)
}
return block.Body, nil
}
@@ -139,7 +139,7 @@ func tryGetKeyIDFromSignature(sig *packet.Signature) string {
if sig.IssuerKeyId != nil && (*sig.IssuerKeyId) != 0 {
return fmt.Sprintf("%016X", *sig.IssuerKeyId)
}
if sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) > 0 {
if len(sig.IssuerFingerprint) > 0 {
return fmt.Sprintf("%016X", sig.IssuerFingerprint[12:20])
}
return ""
+2 -2
View File
@@ -39,7 +39,7 @@ func (d *postgresSchemaDriver) Open(name string) (driver.Conn, error) {
// golangci lint is incorrect here - there is no benefit to using driver.ExecerContext here
// and in any case pq does not implement it
if execer, ok := conn.(driver.Execer); ok { //nolint
if execer, ok := conn.(driver.Execer); ok { //nolint:staticcheck
_, err := execer.Exec(`SELECT set_config(
'search_path',
$1 || ',' || current_setting('search_path'),
@@ -64,7 +64,7 @@ func (d *postgresSchemaDriver) Open(name string) (driver.Conn, error) {
// driver.String.ConvertValue will never return err for string
// golangci lint is incorrect here - there is no benefit to using stmt.ExecWithContext here
_, err = stmt.Exec([]driver.Value{schemaValue}) //nolint
_, err = stmt.Exec([]driver.Value{schemaValue}) //nolint:staticcheck
if err != nil {
_ = conn.Close()
return nil, err
+19
View File
@@ -83,3 +83,22 @@
issue_id: 2 # in repo_id 1
review_id: 20
created_unix: 946684810
-
id: 10
type: 22 # review
poster_id: 5
issue_id: 3 # in repo_id 1
content: "reviewed by user5"
review_id: 21
created_unix: 946684816
-
id: 11
type: 27 # review request
poster_id: 2
issue_id: 3 # in repo_id 1
content: "review request for user5"
review_id: 22
assignee_id: 5
created_unix: 946684817
+1
View File
@@ -0,0 +1 @@
[] # empty
+1 -1
View File
@@ -26,7 +26,7 @@
fork_id: 0
is_template: false
template_id: 0
size: 7597
size: 8478
is_fsck_enabled: true
close_issues_via_commit_in_any_branch: false
+19
View File
@@ -179,3 +179,22 @@
content: "Review Comment"
updated_unix: 946684810
created_unix: 946684810
-
id: 21
type: 2
reviewer_id: 5
issue_id: 3
content: "reviewed by user5"
commit_id: 4a357436d925b5c974181ff12a994538ddc5a269
updated_unix: 946684816
created_unix: 946684816
-
id: 22
type: 4
reviewer_id: 5
issue_id: 3
content: "review request for user5"
updated_unix: 946684817
created_unix: 946684817
+8 -8
View File
@@ -48,12 +48,12 @@ func (issue *Issue) ProjectColumnID(ctx context.Context) int64 {
}
// LoadIssuesFromColumn load issues assigned to this column
func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column) (IssueList, error) {
issueList, err := Issues(ctx, &IssuesOptions{
ProjectColumnID: b.ID,
ProjectID: b.ProjectID,
SortType: "project-column-sorting",
})
func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column, opts *IssuesOptions) (IssueList, error) {
issueList, err := Issues(ctx, opts.Copy(func(o *IssuesOptions) {
o.ProjectColumnID = b.ID
o.ProjectID = b.ProjectID
o.SortType = "project-column-sorting"
}))
if err != nil {
return nil, err
}
@@ -78,10 +78,10 @@ func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column) (IssueLi
}
// LoadIssuesFromColumnList load issues assigned to the columns
func LoadIssuesFromColumnList(ctx context.Context, bs project_model.ColumnList) (map[int64]IssueList, error) {
func LoadIssuesFromColumnList(ctx context.Context, bs project_model.ColumnList, opts *IssuesOptions) (map[int64]IssueList, error) {
issuesMap := make(map[int64]IssueList, len(bs))
for i := range bs {
il, err := LoadIssuesFromColumn(ctx, bs[i])
il, err := LoadIssuesFromColumn(ctx, bs[i], opts)
if err != nil {
return nil, err
}
+13
View File
@@ -54,6 +54,19 @@ type IssuesOptions struct { //nolint
User *user_model.User // issues permission scope
}
// Copy returns a copy of the options.
// Be careful, it's not a deep copy, so `IssuesOptions.RepoIDs = {...}` is OK while `IssuesOptions.RepoIDs[0] = ...` is not.
func (o *IssuesOptions) Copy(edit ...func(options *IssuesOptions)) *IssuesOptions {
if o == nil {
return nil
}
v := *o
for _, e := range edit {
e(&v)
}
return &v
}
// applySorts sort an issues-related session based on the provided
// sortType string
func applySorts(sess *xorm.Session, sortType string, priorityRepoID int64) {
+5 -1
View File
@@ -268,6 +268,10 @@ func (pr *PullRequest) LoadAttributes(ctx context.Context) (err error) {
return nil
}
func (pr *PullRequest) IsAgitFlow() bool {
return pr.Flow == PullRequestFlowAGit
}
// LoadHeadRepo loads the head repository, pr.HeadRepo will remain nil if it does not exist
// and thus ErrRepoNotExist will never be returned
func (pr *PullRequest) LoadHeadRepo(ctx context.Context) (err error) {
@@ -410,7 +414,7 @@ func (pr *PullRequest) getReviewedByLines(ctx context.Context, writer io.Writer)
// Note: This doesn't page as we only expect a very limited number of reviews
reviews, err := FindLatestReviews(ctx, FindReviewOptions{
Type: ReviewTypeApprove,
Types: []ReviewType{ReviewTypeApprove},
IssueID: pr.IssueID,
OfficialOnly: setting.Repository.PullRequest.DefaultMergeMessageOfficialApproversOnly,
})
+5
View File
@@ -26,6 +26,7 @@ type PullRequestsOptions struct {
SortType string
Labels []int64
MilestoneID int64
PosterID int64
}
func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullRequestsOptions) *xorm.Session {
@@ -46,6 +47,10 @@ func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullR
sess.And("issue.milestone_id=?", opts.MilestoneID)
}
if opts.PosterID > 0 {
sess.And("issue.poster_id=?", opts.PosterID)
}
return sess
}
+2 -2
View File
@@ -247,7 +247,7 @@ func (r *Review) TooltipContent() string {
}
return "repo.issues.review.official"
case ReviewTypeComment:
return "repo.issues.review.comment"
return "repo.issues.review.commented"
case ReviewTypeReject:
return "repo.issues.review.rejected"
case ReviewTypeRequest:
@@ -389,7 +389,7 @@ func GetCurrentReview(ctx context.Context, reviewer *user_model.User, issue *Iss
return nil, nil
}
reviews, err := FindReviews(ctx, FindReviewOptions{
Type: ReviewTypePending,
Types: []ReviewType{ReviewTypePending},
IssueID: issue.ID,
ReviewerID: reviewer.ID,
})
+3 -3
View File
@@ -92,7 +92,7 @@ func (reviews ReviewList) LoadIssues(ctx context.Context) error {
// FindReviewOptions represent possible filters to find reviews
type FindReviewOptions struct {
db.ListOptions
Type ReviewType
Types []ReviewType
IssueID int64
ReviewerID int64
OfficialOnly bool
@@ -107,8 +107,8 @@ func (opts *FindReviewOptions) toCond() builder.Cond {
if opts.ReviewerID > 0 {
cond = cond.And(builder.Eq{"reviewer_id": opts.ReviewerID})
}
if opts.Type != ReviewTypeUnknown {
cond = cond.And(builder.Eq{"type": opts.Type})
if len(opts.Types) > 0 {
cond = cond.And(builder.In("type", opts.Types))
}
if opts.OfficialOnly {
cond = cond.And(builder.Eq{"official": true})
+2 -2
View File
@@ -63,7 +63,7 @@ func TestReviewType_Icon(t *testing.T) {
func TestFindReviews(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
reviews, err := issues_model.FindReviews(db.DefaultContext, issues_model.FindReviewOptions{
Type: issues_model.ReviewTypeApprove,
Types: []issues_model.ReviewType{issues_model.ReviewTypeApprove},
IssueID: 2,
ReviewerID: 1,
})
@@ -75,7 +75,7 @@ func TestFindReviews(t *testing.T) {
func TestFindLatestReviews(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
reviews, err := issues_model.FindLatestReviews(db.DefaultContext, issues_model.FindReviewOptions{
Type: issues_model.ReviewTypeApprove,
Types: []issues_model.ReviewType{issues_model.ReviewTypeApprove},
IssueID: 11,
})
assert.NoError(t, err)
+3 -1
View File
@@ -500,7 +500,7 @@ var migrations = []Migration{
// v259 -> v260
NewMigration("Convert scoped access tokens", v1_20.ConvertScopedAccessTokens),
// Gitea 1.20.0 ends at 260
// Gitea 1.20.0 ends at v260
// v260 -> v261
NewMigration("Drop custom_labels column of action_runner table", v1_21.DropCustomLabelsColumnOfActionRunner),
@@ -601,6 +601,8 @@ var migrations = []Migration{
NewMigration("Add metadata column for comment table", v1_23.AddCommentMetaDataColumn),
// v304 -> v305
NewMigration("Add index for release sha1", v1_23.AddIndexForReleaseSha1),
// v305 -> v306
NewMigration("Add Repository Licenses", v1_23.AddRepositoryLicenses),
}
// GetCurrentDBVersion returns the current db version
+1 -1
View File
@@ -83,7 +83,7 @@ func UnwrapLDAPSourceCfg(x *xorm.Engine) error {
if err != nil {
return fmt.Errorf("failed to unmarshal %s: %w", source.Cfg, err)
}
if wrapped.Source != nil && len(wrapped.Source) > 0 {
if len(wrapped.Source) > 0 {
bs, err := json.Marshal(wrapped.Source)
if err != nil {
return err
+23
View File
@@ -0,0 +1,23 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_23 //nolint
import (
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/xorm"
)
func AddRepositoryLicenses(x *xorm.Engine) error {
type RepoLicense struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE(s) NOT NULL"`
CommitID string
License string `xorm:"VARCHAR(255) UNIQUE(s) NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX UPDATED"`
}
return x.Sync(new(RepoLicense))
}
+45
View File
@@ -9,7 +9,9 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log"
"xorm.io/builder"
@@ -112,6 +114,49 @@ func IsUserOrgOwner(ctx context.Context, users user_model.UserList, orgID int64)
return results
}
// GetOrgAssignees returns all users that have write access and can be assigned to issues
// of the any repository in the organization.
func GetOrgAssignees(ctx context.Context, orgID int64) (_ []*user_model.User, err error) {
e := db.GetEngine(ctx)
userIDs := make([]int64, 0, 10)
if err = e.Table("access").
Join("INNER", "repository", "`repository`.id = `access`.repo_id").
Where("`repository`.owner_id = ? AND `access`.mode >= ?", orgID, perm.AccessModeWrite).
Select("user_id").
Find(&userIDs); err != nil {
return nil, err
}
additionalUserIDs := make([]int64, 0, 10)
if err = e.Table("team_user").
Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
Join("INNER", "repository", "`repository`.id = `team_repo`.repo_id").
Where("`repository`.owner_id = ? AND (`team_unit`.access_mode >= ? OR (`team_unit`.access_mode = ? AND `team_unit`.`type` = ?))",
orgID, perm.AccessModeWrite, perm.AccessModeRead, unit.TypePullRequests).
Distinct("`team_user`.uid").
Select("`team_user`.uid").
Find(&additionalUserIDs); err != nil {
return nil, err
}
uniqueUserIDs := make(container.Set[int64])
uniqueUserIDs.AddMultiple(userIDs...)
uniqueUserIDs.AddMultiple(additionalUserIDs...)
users := make([]*user_model.User, 0, len(uniqueUserIDs))
if len(userIDs) > 0 {
if err = e.In("id", uniqueUserIDs.Values()).
Where(builder.Eq{"`user`.is_active": true}).
OrderBy(user_model.GetOrderByName()).
Find(&users); err != nil {
return nil, err
}
}
return users, nil
}
func loadOrganizationOwners(ctx context.Context, users user_model.UserList, orgID int64) (map[int64]*TeamUser, error) {
if len(users) == 0 {
return nil, nil
+120
View File
@@ -0,0 +1,120 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
"context"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/timeutil"
)
func init() {
db.RegisterModel(new(RepoLicense))
}
type RepoLicense struct { //revive:disable-line:exported
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE(s) NOT NULL"`
CommitID string
License string `xorm:"VARCHAR(255) UNIQUE(s) NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX UPDATED"`
}
// RepoLicenseList defines a list of repo licenses
type RepoLicenseList []*RepoLicense //revive:disable-line:exported
func (rll RepoLicenseList) StringList() []string {
var licenses []string
for _, rl := range rll {
licenses = append(licenses, rl.License)
}
return licenses
}
// GetRepoLicenses returns the license statistics for a repository
func GetRepoLicenses(ctx context.Context, repo *Repository) (RepoLicenseList, error) {
licenses := make(RepoLicenseList, 0)
if err := db.GetEngine(ctx).Where("`repo_id` = ?", repo.ID).Asc("`license`").Find(&licenses); err != nil {
return nil, err
}
return licenses, nil
}
// UpdateRepoLicenses updates the license statistics for repository
func UpdateRepoLicenses(ctx context.Context, repo *Repository, commitID string, licenses []string) error {
oldLicenses, err := GetRepoLicenses(ctx, repo)
if err != nil {
return err
}
for _, license := range licenses {
upd := false
for _, o := range oldLicenses {
// Update already existing license
if o.License == license {
if _, err := db.GetEngine(ctx).ID(o.ID).Cols("`commit_id`").Update(o); err != nil {
return err
}
upd = true
break
}
}
// Insert new license
if !upd {
if err := db.Insert(ctx, &RepoLicense{
RepoID: repo.ID,
CommitID: commitID,
License: license,
}); err != nil {
return err
}
}
}
// Delete old licenses
licenseToDelete := make([]int64, 0, len(oldLicenses))
for _, o := range oldLicenses {
if o.CommitID != commitID {
licenseToDelete = append(licenseToDelete, o.ID)
}
}
if len(licenseToDelete) > 0 {
if _, err := db.GetEngine(ctx).In("`id`", licenseToDelete).Delete(&RepoLicense{}); err != nil {
return err
}
}
return nil
}
// CopyLicense Copy originalRepo license information to destRepo (use for forked repo)
func CopyLicense(ctx context.Context, originalRepo, destRepo *Repository) error {
repoLicenses, err := GetRepoLicenses(ctx, originalRepo)
if err != nil {
return err
}
if len(repoLicenses) > 0 {
newRepoLicenses := make(RepoLicenseList, 0, len(repoLicenses))
for _, rl := range repoLicenses {
newRepoLicense := &RepoLicense{
RepoID: destRepo.ID,
CommitID: rl.CommitID,
License: rl.License,
}
newRepoLicenses = append(newRepoLicenses, newRepoLicense)
}
if err := db.Insert(ctx, &newRepoLicenses); err != nil {
return err
}
}
return nil
}
// CleanRepoLicenses will remove all license record of the repo
func CleanRepoLicenses(ctx context.Context, repo *Repository) error {
return db.DeleteBeans(ctx, &RepoLicense{
RepoID: repo.ID,
})
}
+6
View File
@@ -234,6 +234,7 @@ type FindReleasesOptions struct {
IsDraft optional.Option[bool]
TagNames []string
HasSha1 optional.Option[bool] // useful to find draft releases which are created with existing tags
NamePattern optional.Option[string]
}
func (opts FindReleasesOptions) ToConds() builder.Cond {
@@ -261,6 +262,11 @@ func (opts FindReleasesOptions) ToConds() builder.Cond {
cond = cond.And(builder.Eq{"sha1": ""})
}
}
if opts.NamePattern.Has() && opts.NamePattern.Value() != "" {
cond = cond.And(builder.Like{"lower_tag_name", strings.ToLower(opts.NamePattern.Value())})
}
return cond
}
+1 -1
View File
@@ -749,7 +749,7 @@ func GetUserRepositories(ctx context.Context, opts *SearchRepoOptions) (Reposito
cond = cond.And(builder.Eq{"is_private": false})
}
if opts.LowerNames != nil && len(opts.LowerNames) > 0 {
if len(opts.LowerNames) > 0 {
cond = cond.And(builder.In("lower_name", opts.LowerNames))
}
+13 -1
View File
@@ -65,7 +65,19 @@ func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Sess
builder.Like{"LOWER(full_name)", lowerKeyword},
)
if opts.SearchByEmail {
keywordCond = keywordCond.Or(builder.Like{"LOWER(email)", lowerKeyword})
var emailCond builder.Cond
emailCond = builder.Like{"LOWER(email)", lowerKeyword}
if opts.Actor == nil {
emailCond = emailCond.And(builder.Eq{"keep_email_private": false})
} else if !opts.Actor.IsAdmin {
emailCond = emailCond.And(
builder.Or(
builder.Eq{"keep_email_private": false},
builder.Eq{"id": opts.Actor.ID},
),
)
}
keywordCond = keywordCond.Or(emailCond)
}
cond = cond.And(keywordCond)
+4
View File
@@ -14,4 +14,8 @@ const (
UserActivityPubPrivPem = "activitypub.priv_pem"
// UserActivityPubPubPem is user's public key
UserActivityPubPubPem = "activitypub.pub_pem"
// SignupIP is the IP address that the user signed up with
SignupIP = "signup.ip"
// SignupUserAgent is the user agent that the user signed up with
SignupUserAgent = "signup.user_agent"
)
+33 -5
View File
@@ -150,6 +150,14 @@ type User struct {
KeepActivityPrivate bool `xorm:"NOT NULL DEFAULT false"`
}
// Meta defines the meta information of a user, to be stored in the K/V table
type Meta struct {
// Store the initial registration of the user, to aid in spam prevention
// Ensure that one IP isn't creating many accounts (following mediawiki approach)
InitialIP string
InitialUserAgent string
}
func init() {
db.RegisterModel(new(User))
}
@@ -400,6 +408,10 @@ func (u *User) IsIndividual() bool {
return u.Type == UserTypeIndividual
}
func (u *User) IsUser() bool {
return u.Type == UserTypeIndividual || u.Type == UserTypeBot
}
// IsBot returns whether or not the user is of type bot
func (u *User) IsBot() bool {
return u.Type == UserTypeBot
@@ -615,17 +627,17 @@ type CreateUserOverwriteOptions struct {
}
// CreateUser creates record of a new user.
func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
return createUser(ctx, u, false, overwriteDefault...)
func CreateUser(ctx context.Context, u *User, meta *Meta, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
return createUser(ctx, u, meta, false, overwriteDefault...)
}
// AdminCreateUser is used by admins to manually create users
func AdminCreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
return createUser(ctx, u, true, overwriteDefault...)
func AdminCreateUser(ctx context.Context, u *User, meta *Meta, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
return createUser(ctx, u, meta, true, overwriteDefault...)
}
// createUser creates record of a new user.
func createUser(ctx context.Context, u *User, createdByAdmin bool, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
func createUser(ctx context.Context, u *User, meta *Meta, createdByAdmin bool, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
if err = IsUsableUsername(u.Name); err != nil {
return err
}
@@ -745,6 +757,22 @@ func createUser(ctx context.Context, u *User, createdByAdmin bool, overwriteDefa
return err
}
if setting.RecordUserSignupMetadata {
// insert initial IP and UserAgent
if err = SetUserSetting(ctx, u.ID, SignupIP, meta.InitialIP); err != nil {
return err
}
// trim user agent string to a reasonable length, if necessary
userAgent := strings.TrimSpace(meta.InitialUserAgent)
if len(userAgent) > 255 {
userAgent = userAgent[:255]
}
if err = SetUserSetting(ctx, u.ID, SignupUserAgent, userAgent); err != nil {
return err
}
}
// insert email address
if err := db.Insert(ctx, &EmailAddress{
UID: u.ID,
+4 -4
View File
@@ -227,7 +227,7 @@ func TestCreateUserInvalidEmail(t *testing.T) {
MustChangePassword: false,
}
err := user_model.CreateUser(db.DefaultContext, user)
err := user_model.CreateUser(db.DefaultContext, user, &user_model.Meta{})
assert.Error(t, err)
assert.True(t, user_model.IsErrEmailCharIsNotSupported(err))
}
@@ -241,7 +241,7 @@ func TestCreateUserEmailAlreadyUsed(t *testing.T) {
user.Name = "testuser"
user.LowerName = strings.ToLower(user.Name)
user.ID = 0
err := user_model.CreateUser(db.DefaultContext, user)
err := user_model.CreateUser(db.DefaultContext, user, &user_model.Meta{})
assert.Error(t, err)
assert.True(t, user_model.IsErrEmailAlreadyUsed(err))
}
@@ -258,7 +258,7 @@ func TestCreateUserCustomTimestamps(t *testing.T) {
user.ID = 0
user.Email = "unique@example.com"
user.CreatedUnix = creationTimestamp
err := user_model.CreateUser(db.DefaultContext, user)
err := user_model.CreateUser(db.DefaultContext, user, &user_model.Meta{})
assert.NoError(t, err)
fetched, err := user_model.GetUserByID(context.Background(), user.ID)
@@ -283,7 +283,7 @@ func TestCreateUserWithoutCustomTimestamps(t *testing.T) {
user.Email = "unique@example.com"
user.CreatedUnix = 0
user.UpdatedUnix = 0
err := user_model.CreateUser(db.DefaultContext, user)
err := user_model.CreateUser(db.DefaultContext, user, &user_model.Meta{})
assert.NoError(t, err)
timestampEnd := time.Now().Unix()