Checks / verify (push) Failing after 9m52s
统一单入口 Go 工程与前端依赖目录,增加后台供应商配置、候选审核、计划预算、成交冲正、持仓复盘和站内通知。接入策略版本、Python 回测、可配置 A 股公开行情及 zap 文件轮转日志,补充迁移、契约与验证。
130 lines
5.6 KiB
Go
130 lines
5.6 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
"git.sundynix.cn/Blizzard/Veyra/internal/apperror"
|
|
"git.sundynix.cn/Blizzard/Veyra/internal/model"
|
|
)
|
|
|
|
type IdentityRepository struct{ pool *pgxpool.Pool }
|
|
|
|
func NewIdentityRepository(pool *pgxpool.Pool) *IdentityRepository {
|
|
return &IdentityRepository{pool: pool}
|
|
}
|
|
|
|
func (r *IdentityRepository) CreateUser(ctx context.Context, user model.User, credential model.Credential) (model.User, error) {
|
|
tx, err := r.pool.Begin(ctx)
|
|
if err != nil {
|
|
return model.User{}, apperror.ErrUnavailable
|
|
}
|
|
defer tx.Rollback(context.Background())
|
|
err = tx.QueryRow(ctx, `INSERT INTO veyra_user(id,display_name,status,role) VALUES($1,$2,$3,$4) RETURNING created_at,updated_at`, user.ID, user.DisplayName, user.Status, user.Role).Scan(&user.CreatedAt, &user.UpdatedAt)
|
|
if err != nil {
|
|
return model.User{}, mapError(err)
|
|
}
|
|
_, err = tx.Exec(ctx, `INSERT INTO veyra_credential(id,user_id,login_name,password_hash) VALUES($1,$2,$3,$4)`, credential.ID, credential.UserID, credential.LoginName, credential.PasswordHash)
|
|
if err != nil {
|
|
return model.User{}, mapError(err)
|
|
}
|
|
if err = tx.Commit(ctx); err != nil {
|
|
return model.User{}, apperror.ErrUnavailable
|
|
}
|
|
return user, nil
|
|
}
|
|
|
|
func (r *IdentityRepository) FindCredential(ctx context.Context, login string) (model.User, model.Credential, error) {
|
|
var u model.User
|
|
var c model.Credential
|
|
err := r.pool.QueryRow(ctx, `SELECT u.id,u.display_name,u.status,u.role,u.created_at,u.updated_at,c.id,c.user_id,c.login_name,c.password_hash,c.created_at FROM veyra_user u JOIN veyra_credential c ON c.user_id=u.id WHERE c.login_name=$1`, login).Scan(&u.ID, &u.DisplayName, &u.Status, &u.Role, &u.CreatedAt, &u.UpdatedAt, &c.ID, &c.UserID, &c.LoginName, &c.PasswordHash, &c.CreatedAt)
|
|
return u, c, mapError(err)
|
|
}
|
|
|
|
func (r *IdentityRepository) CreateSession(ctx context.Context, s model.Session, verifiedPasswordHash string) error {
|
|
tx, err := r.pool.Begin(ctx)
|
|
if err != nil {
|
|
return mapError(err)
|
|
}
|
|
defer tx.Rollback(context.Background())
|
|
var userID string
|
|
err = tx.QueryRow(ctx, `SELECT u.id FROM veyra_user u JOIN veyra_credential c ON c.user_id=u.id WHERE u.id=$1 AND c.password_hash=$2 AND u.status='ACTIVE' AND (($3='admin' AND u.role='ADMIN') OR ($3='app' AND u.role='USER')) FOR SHARE OF u,c`, s.UserID, verifiedPasswordHash, s.Audience).Scan(&userID)
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return apperror.ErrUnauthenticated
|
|
}
|
|
if err != nil {
|
|
return mapError(err)
|
|
}
|
|
_, err = tx.Exec(ctx, `INSERT INTO veyra_session(id,user_id,token_hash,audience,expires_at) VALUES($1,$2,$3,$4,$5)`, s.ID, s.UserID, s.TokenHash, s.Audience, s.ExpiresAt)
|
|
if err != nil {
|
|
return mapError(err)
|
|
}
|
|
return mapError(tx.Commit(ctx))
|
|
}
|
|
|
|
func (r *IdentityRepository) FindSessionUser(ctx context.Context, hash, audience string, now time.Time) (model.User, error) {
|
|
var u model.User
|
|
err := r.pool.QueryRow(ctx, `SELECT u.id,u.display_name,u.status,u.role,u.created_at,u.updated_at FROM veyra_session s JOIN veyra_user u ON u.id=s.user_id WHERE s.token_hash=$1 AND s.audience=$2 AND s.expires_at>$3 AND s.revoked_at IS NULL AND u.status='ACTIVE' AND (($2='admin' AND u.role='ADMIN') OR ($2='app' AND u.role='USER'))`, hash, audience, now).Scan(&u.ID, &u.DisplayName, &u.Status, &u.Role, &u.CreatedAt, &u.UpdatedAt)
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return model.User{}, apperror.ErrUnauthenticated
|
|
}
|
|
return u, mapError(err)
|
|
}
|
|
|
|
func (r *IdentityRepository) RevokeSession(ctx context.Context, hash, audience string, now time.Time) error {
|
|
_, err := r.pool.Exec(ctx, `UPDATE veyra_session SET revoked_at=$3 WHERE token_hash=$1 AND audience=$2 AND revoked_at IS NULL`, hash, audience, now)
|
|
return mapError(err)
|
|
}
|
|
|
|
func (r *IdentityRepository) ListUsers(ctx context.Context, limit int) ([]model.User, error) {
|
|
rows, err := r.pool.Query(ctx, `SELECT id,display_name,status,role,created_at,updated_at FROM veyra_user WHERE role='USER' ORDER BY created_at DESC,id DESC LIMIT $1`, limit)
|
|
if err != nil {
|
|
return nil, apperror.ErrUnavailable
|
|
}
|
|
defer rows.Close()
|
|
users := make([]model.User, 0)
|
|
for rows.Next() {
|
|
var u model.User
|
|
if err := rows.Scan(&u.ID, &u.DisplayName, &u.Status, &u.Role, &u.CreatedAt, &u.UpdatedAt); err != nil {
|
|
return nil, apperror.ErrUnavailable
|
|
}
|
|
users = append(users, u)
|
|
}
|
|
return users, mapError(rows.Err())
|
|
}
|
|
|
|
func (r *IdentityRepository) ChangeAdminCredential(ctx context.Context, previousLogin, login, passwordHash string, now time.Time) (string, error) {
|
|
tx, err := r.pool.Begin(ctx)
|
|
if err != nil {
|
|
return "", mapError(err)
|
|
}
|
|
defer tx.Rollback(context.Background())
|
|
var userID, role, status string
|
|
err = tx.QueryRow(ctx, `SELECT u.id,u.role,u.status FROM veyra_user u JOIN veyra_credential c ON c.user_id=u.id WHERE c.login_name=$1 FOR UPDATE OF u,c`, previousLogin).Scan(&userID, &role, &status)
|
|
if err != nil {
|
|
return "", mapError(err)
|
|
}
|
|
if role != "ADMIN" || status != "ACTIVE" {
|
|
return "", apperror.ErrForbidden
|
|
}
|
|
if _, err := tx.Exec(ctx, `UPDATE veyra_credential SET login_name=$2,password_hash=$3 WHERE user_id=$1`, userID, login, passwordHash); err != nil {
|
|
return "", mapError(err)
|
|
}
|
|
tag, err := tx.Exec(ctx, `UPDATE veyra_session SET revoked_at=$2 WHERE user_id=$1 AND revoked_at IS NULL`, userID, now)
|
|
if err != nil {
|
|
return "", mapError(err)
|
|
}
|
|
if err := appendAudit(ctx, tx, userID, "ADMIN_CREDENTIAL_CHANGED", "user", userID, uuid.NewString(), map[string]any{"sessions_revoked": tag.RowsAffected()}); err != nil {
|
|
return "", err
|
|
}
|
|
if err := tx.Commit(ctx); err != nil {
|
|
return "", mapError(err)
|
|
}
|
|
return userID, nil
|
|
}
|