forgejo/models/actions/user.go
Earl Warren bf7c63a2ae
feat: add ActionUser model & fields to ActionRun
ActionUser is to keep track of pull requests posters that are
permanently trusted. It has a used field to track when it was last
used so records can be expired instead of accumulating forever.

ActionRun has new fields to make it possible to look them up given
either the pull request ID or the poster ID.
2025-11-06 11:07:38 +01:00

97 lines
2.6 KiB
Go

// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package actions
import (
"context"
"fmt"
"time"
"forgejo.org/models/db"
"forgejo.org/modules/timeutil"
"xorm.io/builder"
)
type ActionUser struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"INDEX UNIQUE(action_user_index) REFERENCES(user, id)"`
RepoID int64 `xorm:"INDEX UNIQUE(action_user_index) REFERENCES(repository, id)"`
TrustedWithPullRequests bool
LastAccess timeutil.TimeStamp `xorm:"INDEX"`
}
func init() {
db.RegisterModel(new(ActionUser))
}
type ErrUserNotExist struct {
UserID int64
RepoID int64
}
func IsErrUserNotExist(err error) bool {
_, ok := err.(ErrUserNotExist)
return ok
}
func (err ErrUserNotExist) Error() string {
return fmt.Sprintf("ActionUser does not exist [user_id: %d, repo_id: %d]", err.UserID, err.RepoID)
}
func InsertActionUser(ctx context.Context, user *ActionUser) error {
user.LastAccess = timeutil.TimeStampNow()
return db.Insert(ctx, user)
}
func DeleteActionUserByUserIDAndRepoID(ctx context.Context, userID, repoID int64) error {
_, err := db.GetEngine(ctx).Table(&ActionUser{}).Where("user_id=? AND repo_id=?", userID, repoID).Delete()
return err
}
var updateFrequency = 24 * time.Hour
func MaybeUpdateAccess(ctx context.Context, user *ActionUser) error {
// Keep track of the last time the record was accessed to identify which one
// are never accessed so they can be removed eventually. But only every updateFrequency
// to not stress the underlying database.
if timeutil.TimeStampNow() > user.LastAccess.AddDuration(updateFrequency) {
user.LastAccess = timeutil.TimeStampNow()
if _, err := db.GetEngine(ctx).ID(user.ID).Cols("last_access").Update(user); err != nil {
return err
}
}
return nil
}
func GetActionUserByUserIDAndRepoID(ctx context.Context, userID, repoID int64) (*ActionUser, error) {
user := new(ActionUser)
has, err := db.GetEngine(ctx).Where("user_id=? AND repo_id=?", userID, repoID).Get(user)
if err != nil {
return nil, err
} else if !has {
return nil, ErrUserNotExist{userID, repoID}
}
return user, nil
}
func GetActionUserByUserIDAndRepoIDAndUpdateAccess(ctx context.Context, userID, repoID int64) (*ActionUser, error) {
user, err := GetActionUserByUserIDAndRepoID(ctx, userID, repoID)
if err != nil {
return nil, err
}
return user, MaybeUpdateAccess(ctx, user)
}
var expire = 3 * 30 * 24 * time.Hour
func RevokeInactiveActionUser(ctx context.Context) error {
olderThan := timeutil.TimeStampNow().AddDuration(-expire)
_, err := db.GetEngine(ctx).Where(builder.Lt{"last_access": olderThan}).Delete(&ActionUser{})
return err
}