ilk
This commit is contained in:
286
svc/repository/activitylog_repository.go
Normal file
286
svc/repository/activitylog_repository.go
Normal file
@@ -0,0 +1,286 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ActivityLogRow struct {
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
RequestStartedAt *time.Time `json:"request_started_at,omitempty"`
|
||||
RequestFinishedAt *time.Time `json:"request_finished_at,omitempty"`
|
||||
DurationMs *int `json:"duration_ms,omitempty"`
|
||||
HttpStatus *int `json:"http_status,omitempty"`
|
||||
|
||||
Username string `json:"username"`
|
||||
RoleCode string `json:"role_code"`
|
||||
|
||||
ActionCategory string `json:"action_category"`
|
||||
ActionType string `json:"action_type"`
|
||||
ActionTarget string `json:"action_target"`
|
||||
Description string `json:"description"`
|
||||
|
||||
// ✅ TARGET USER
|
||||
TargetDfUsrID int64 `json:"target_dfusr_id"`
|
||||
TargetUsername string `json:"target_username"`
|
||||
|
||||
// ✅ DIFF
|
||||
ChangeBefore string `json:"change_before"`
|
||||
ChangeAfter string `json:"change_after"`
|
||||
|
||||
IpAddress string `json:"ip_address"`
|
||||
IsSuccess bool `json:"is_success"`
|
||||
ErrorMsg string `json:"error_message"`
|
||||
UserAgent string `json:"user_agent"`
|
||||
SessionID string `json:"session_id"`
|
||||
|
||||
// audit ids
|
||||
UserUUID string `json:"user_id"`
|
||||
DfUsrID int64 `json:"dfusr_id"`
|
||||
|
||||
Email string `json:"email"`
|
||||
IsActive bool `json:"is_active"`
|
||||
}
|
||||
|
||||
type ActivityLogQuery struct {
|
||||
Username string
|
||||
RoleCode string
|
||||
ActionCategory string
|
||||
ActionType string
|
||||
ActionTarget string
|
||||
Success *bool
|
||||
|
||||
StatusMin *int
|
||||
StatusMax *int
|
||||
|
||||
DateFrom *time.Time
|
||||
DateTo *time.Time
|
||||
|
||||
Page int
|
||||
Limit int
|
||||
}
|
||||
|
||||
type ActivityLogResult struct {
|
||||
Total int64 `json:"total"`
|
||||
Items []ActivityLogRow `json:"items"`
|
||||
}
|
||||
|
||||
func ListActivityLogs(
|
||||
ctx context.Context,
|
||||
db *sql.DB,
|
||||
q ActivityLogQuery,
|
||||
) (ActivityLogResult, error) {
|
||||
|
||||
// ---------- defaults ----------
|
||||
if q.Page <= 0 {
|
||||
q.Page = 1
|
||||
}
|
||||
// limit <=0 → unlimited
|
||||
if q.Limit < 0 {
|
||||
q.Limit = 50
|
||||
}
|
||||
if q.Limit > 200 {
|
||||
q.Limit = 200
|
||||
}
|
||||
|
||||
offset := 0
|
||||
if q.Limit > 0 {
|
||||
offset = (q.Page - 1) * q.Limit
|
||||
}
|
||||
|
||||
where := []string{}
|
||||
args := []interface{}{}
|
||||
|
||||
add := func(cond string, val interface{}) {
|
||||
where = append(where, cond)
|
||||
args = append(args, val)
|
||||
}
|
||||
|
||||
// ---------- filters ----------
|
||||
if strings.TrimSpace(q.Username) != "" {
|
||||
add(fmt.Sprintf("l.username ILIKE $%d", len(args)+1),
|
||||
"%"+strings.TrimSpace(q.Username)+"%")
|
||||
}
|
||||
if strings.TrimSpace(q.RoleCode) != "" {
|
||||
add(fmt.Sprintf("r.code = $%d", len(args)+1),
|
||||
strings.TrimSpace(q.RoleCode))
|
||||
}
|
||||
if strings.TrimSpace(q.ActionCategory) != "" {
|
||||
add(fmt.Sprintf("l.action_category = $%d", len(args)+1),
|
||||
strings.TrimSpace(q.ActionCategory))
|
||||
}
|
||||
if strings.TrimSpace(q.ActionType) != "" {
|
||||
add(fmt.Sprintf("l.action_type = $%d", len(args)+1),
|
||||
strings.TrimSpace(q.ActionType))
|
||||
}
|
||||
if strings.TrimSpace(q.ActionTarget) != "" {
|
||||
add(fmt.Sprintf("l.action_target ILIKE $%d", len(args)+1),
|
||||
"%"+strings.TrimSpace(q.ActionTarget)+"%")
|
||||
}
|
||||
if q.Success != nil {
|
||||
add(fmt.Sprintf("l.is_success = $%d", len(args)+1), *q.Success)
|
||||
}
|
||||
if q.StatusMin != nil {
|
||||
add(fmt.Sprintf("l.http_status >= $%d", len(args)+1), *q.StatusMin)
|
||||
}
|
||||
if q.StatusMax != nil {
|
||||
add(fmt.Sprintf("l.http_status <= $%d", len(args)+1), *q.StatusMax)
|
||||
}
|
||||
if q.DateFrom != nil {
|
||||
add(fmt.Sprintf("l.created_at >= $%d", len(args)+1), *q.DateFrom)
|
||||
}
|
||||
if q.DateTo != nil {
|
||||
add(fmt.Sprintf("l.created_at < $%d", len(args)+1),
|
||||
q.DateTo.Add(24*time.Hour))
|
||||
}
|
||||
|
||||
whereSQL := ""
|
||||
if len(where) > 0 {
|
||||
whereSQL = "WHERE " + strings.Join(where, " AND ")
|
||||
}
|
||||
|
||||
// ---------- COUNT ----------
|
||||
countSQL := fmt.Sprintf(`
|
||||
SELECT count(*)
|
||||
FROM mk_user_activity_log l
|
||||
LEFT JOIN mk_dfusr u ON u.id = l.dfusr_id
|
||||
LEFT JOIN dfrole_usr ru ON ru.dfusr_id = u.id
|
||||
LEFT JOIN dfrole r ON r.id = ru.dfrole_id
|
||||
%s
|
||||
`, whereSQL)
|
||||
|
||||
var total int64
|
||||
if err := db.QueryRowContext(ctx, countSQL, args...).Scan(&total); err != nil {
|
||||
return ActivityLogResult{}, err
|
||||
}
|
||||
|
||||
// ---------- LIST ----------
|
||||
listArgs := append([]interface{}{}, args...)
|
||||
|
||||
listSQL := fmt.Sprintf(`
|
||||
SELECT
|
||||
l.created_at,
|
||||
l.request_started_at,
|
||||
l.request_finished_at,
|
||||
l.duration_ms,
|
||||
l.http_status,
|
||||
|
||||
COALESCE(u.username, l.username, '') AS username,
|
||||
COALESCE(r.code,''),
|
||||
|
||||
COALESCE(l.action_category,''),
|
||||
COALESCE(l.action_type,''),
|
||||
COALESCE(l.action_target,''),
|
||||
COALESCE(l.description,''),
|
||||
|
||||
COALESCE(l.target_dfusr_id, 0),
|
||||
COALESCE(l.target_username, ''),
|
||||
COALESCE(l.change_before::text, ''),
|
||||
COALESCE(l.change_after::text, ''),
|
||||
|
||||
COALESCE(l.ip_address,''),
|
||||
COALESCE(l.is_success,false),
|
||||
COALESCE(l.error_message,''),
|
||||
COALESCE(l.user_agent,''),
|
||||
COALESCE(l.session_id,''),
|
||||
|
||||
COALESCE(l.user_id::text,''),
|
||||
COALESCE(l.dfusr_id,0),
|
||||
|
||||
COALESCE(u.email,''),
|
||||
COALESCE(u.is_active,false)
|
||||
|
||||
FROM mk_user_activity_log l
|
||||
LEFT JOIN mk_dfusr u ON u.id = l.dfusr_id
|
||||
LEFT JOIN dfrole_usr ru ON ru.dfusr_id = u.id
|
||||
LEFT JOIN dfrole r ON r.id = ru.dfrole_id
|
||||
%s
|
||||
ORDER BY l.created_at DESC
|
||||
`, whereSQL)
|
||||
// LIMIT sadece istenirse
|
||||
if q.Limit > 0 {
|
||||
|
||||
listSQL += fmt.Sprintf(
|
||||
" LIMIT $%d OFFSET $%d",
|
||||
len(listArgs)+1,
|
||||
len(listArgs)+2,
|
||||
)
|
||||
|
||||
listArgs = append(listArgs, q.Limit, offset)
|
||||
}
|
||||
|
||||
rows, err := db.QueryContext(ctx, listSQL, listArgs...)
|
||||
if err != nil {
|
||||
return ActivityLogResult{}, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
items := []ActivityLogRow{}
|
||||
|
||||
for rows.Next() {
|
||||
var it ActivityLogRow
|
||||
var started, finished sql.NullTime
|
||||
var dur, status sql.NullInt64
|
||||
|
||||
if err := rows.Scan(
|
||||
&it.CreatedAt,
|
||||
&started,
|
||||
&finished,
|
||||
&dur,
|
||||
&status,
|
||||
|
||||
&it.Username,
|
||||
&it.RoleCode,
|
||||
|
||||
&it.ActionCategory,
|
||||
&it.ActionType,
|
||||
&it.ActionTarget,
|
||||
&it.Description,
|
||||
|
||||
// ✅ NEW
|
||||
&it.TargetDfUsrID,
|
||||
&it.TargetUsername,
|
||||
&it.ChangeBefore,
|
||||
&it.ChangeAfter,
|
||||
|
||||
&it.IpAddress,
|
||||
&it.IsSuccess,
|
||||
&it.ErrorMsg,
|
||||
&it.UserAgent,
|
||||
&it.SessionID,
|
||||
|
||||
&it.UserUUID,
|
||||
&it.DfUsrID,
|
||||
|
||||
&it.Email,
|
||||
&it.IsActive,
|
||||
); err != nil {
|
||||
return ActivityLogResult{}, err
|
||||
}
|
||||
|
||||
if started.Valid {
|
||||
it.RequestStartedAt = &started.Time
|
||||
}
|
||||
if finished.Valid {
|
||||
it.RequestFinishedAt = &finished.Time
|
||||
}
|
||||
if dur.Valid {
|
||||
v := int(dur.Int64)
|
||||
it.DurationMs = &v
|
||||
}
|
||||
if status.Valid {
|
||||
v := int(status.Int64)
|
||||
it.HttpStatus = &v
|
||||
}
|
||||
|
||||
items = append(items, it)
|
||||
}
|
||||
|
||||
return ActivityLogResult{
|
||||
Total: total,
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
352
svc/repository/mk_user_repository.go
Normal file
352
svc/repository/mk_user_repository.go
Normal file
@@ -0,0 +1,352 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"bssapp-backend/models"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
var ErrMkUserNotFound = errors.New("mk_user not found")
|
||||
|
||||
type MkUserRepository struct {
|
||||
DB *sql.DB
|
||||
}
|
||||
|
||||
func NewMkUserRepository(db *sql.DB) *MkUserRepository {
|
||||
return &MkUserRepository{DB: db}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// 🔍 GET BY USERNAME
|
||||
// -------------------------------------------------------
|
||||
func (r *MkUserRepository) GetByUsername(username string) (*models.MkUser, error) {
|
||||
username = strings.TrimSpace(username)
|
||||
|
||||
var u models.MkUser
|
||||
|
||||
err := r.DB.QueryRow(`SELECT
|
||||
u.id,
|
||||
u.username,
|
||||
COALESCE(u.email,'') AS email,
|
||||
u.is_active,
|
||||
COALESCE(u.password_hash,'') AS password_hash,
|
||||
u.force_password_change,
|
||||
|
||||
COALESCE(r.id, 0) AS role_id,
|
||||
COALESCE(r.code, '') AS role_code,
|
||||
|
||||
-- ✅ DEPARTMENTS
|
||||
COALESCE(
|
||||
array_agg(DISTINCT d.code)
|
||||
FILTER (WHERE d.code IS NOT NULL),
|
||||
'{}'
|
||||
) AS department_codes,
|
||||
|
||||
u.password_updated_at,
|
||||
u.created_at,
|
||||
u.updated_at,
|
||||
u.last_login_at
|
||||
|
||||
FROM mk_dfusr u
|
||||
|
||||
LEFT JOIN dfrole_usr ru
|
||||
ON ru.dfusr_id = u.id
|
||||
|
||||
LEFT JOIN dfrole r
|
||||
ON r.id = ru.dfrole_id
|
||||
|
||||
-- ✅ USER → DEPT
|
||||
LEFT JOIN dfusr_dprt ud
|
||||
ON ud.dfusr_id = u.id
|
||||
AND ud.is_active = true
|
||||
|
||||
LEFT JOIN mk_dprt d
|
||||
ON d.id = ud.dprt_id
|
||||
|
||||
WHERE LOWER(u.username) = LOWER($1)
|
||||
|
||||
GROUP BY
|
||||
u.id, r.id
|
||||
|
||||
LIMIT 1
|
||||
`, username).Scan(
|
||||
&u.ID,
|
||||
&u.Username,
|
||||
&u.Email,
|
||||
&u.IsActive,
|
||||
&u.PasswordHash,
|
||||
&u.ForcePasswordChange,
|
||||
|
||||
&u.RoleID,
|
||||
&u.RoleCode,
|
||||
|
||||
pq.Array(&u.DepartmentCodes), // ✅
|
||||
|
||||
&u.PasswordUpdatedAt,
|
||||
|
||||
&u.CreatedAt,
|
||||
&u.UpdatedAt,
|
||||
&u.LastLoginAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, ErrMkUserNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// 🔍 GET BY ID
|
||||
// -------------------------------------------------------
|
||||
func (r *MkUserRepository) GetByID(id int64) (*models.MkUser, error) {
|
||||
var u models.MkUser
|
||||
|
||||
err := r.DB.QueryRow(`
|
||||
SELECT
|
||||
u.id,
|
||||
u.username,
|
||||
COALESCE(u.email,'') AS email,
|
||||
u.is_active,
|
||||
COALESCE(u.password_hash,'') AS password_hash,
|
||||
u.force_password_change,
|
||||
|
||||
COALESCE(r.id, 0) AS role_id,
|
||||
COALESCE(r.code, '') AS role_code,
|
||||
|
||||
-- ✅ DEPARTMENTS
|
||||
COALESCE(
|
||||
array_agg(DISTINCT d.code)
|
||||
FILTER (WHERE d.code IS NOT NULL),
|
||||
'{}'
|
||||
) AS department_codes,
|
||||
|
||||
u.password_updated_at,
|
||||
u.created_at,
|
||||
u.updated_at,
|
||||
u.last_login_at
|
||||
|
||||
FROM mk_dfusr u
|
||||
|
||||
LEFT JOIN dfrole_usr ru
|
||||
ON ru.dfusr_id = u.id
|
||||
|
||||
LEFT JOIN dfrole r
|
||||
ON r.id = ru.dfrole_id
|
||||
|
||||
-- ✅ USER → DEPT
|
||||
LEFT JOIN dfusr_dprt ud
|
||||
ON ud.dfusr_id = u.id
|
||||
AND ud.is_active = true
|
||||
|
||||
LEFT JOIN mk_dprt d
|
||||
ON d.id = ud.dprt_id
|
||||
|
||||
WHERE LOWER(u.username) = LOWER($1)
|
||||
|
||||
GROUP BY
|
||||
u.id, r.id
|
||||
|
||||
LIMIT 1
|
||||
`, id).Scan(
|
||||
&u.ID,
|
||||
&u.Username,
|
||||
&u.Email,
|
||||
&u.IsActive,
|
||||
&u.PasswordHash,
|
||||
&u.ForcePasswordChange,
|
||||
|
||||
&u.RoleID,
|
||||
&u.RoleCode,
|
||||
pq.Array(&u.DepartmentCodes), // ✅
|
||||
&u.PasswordUpdatedAt,
|
||||
&u.CreatedAt,
|
||||
&u.UpdatedAt,
|
||||
&u.LastLoginAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, ErrMkUserNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// 🔁 CREATE FROM LEGACY (dfusr → mk_dfusr)
|
||||
// - id = dfusr.id ✅
|
||||
// - role / v3 YOK
|
||||
// -------------------------------------------------------
|
||||
// -------------------------------------------------------
|
||||
// 🔁 CREATE FROM LEGACY (dfusr → mk_dfusr) FULL MIGRATION
|
||||
// -------------------------------------------------------
|
||||
func (r *MkUserRepository) CreateFromLegacy(
|
||||
legacy *models.User,
|
||||
passwordHash string,
|
||||
) (*models.MkUser, error) {
|
||||
|
||||
var u models.MkUser
|
||||
|
||||
err := r.DB.QueryRow(`
|
||||
INSERT INTO mk_dfusr (
|
||||
id,
|
||||
username,
|
||||
email,
|
||||
full_name,
|
||||
mobile,
|
||||
address,
|
||||
is_active,
|
||||
password_hash,
|
||||
force_password_change,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES (
|
||||
$1,$2,$3,$4,$5,$6,$7,$8,true,now(),now()
|
||||
)
|
||||
ON CONFLICT (id)
|
||||
DO UPDATE SET
|
||||
email = EXCLUDED.email,
|
||||
full_name = EXCLUDED.full_name,
|
||||
mobile = EXCLUDED.mobile,
|
||||
address = EXCLUDED.address,
|
||||
password_hash = EXCLUDED.password_hash,
|
||||
force_password_change= true,
|
||||
updated_at = now()
|
||||
RETURNING
|
||||
id,
|
||||
username,
|
||||
COALESCE(email,''),
|
||||
is_active,
|
||||
COALESCE(password_hash,''),
|
||||
force_password_change,
|
||||
password_updated_at,
|
||||
created_at,
|
||||
updated_at,
|
||||
last_login_at
|
||||
`,
|
||||
legacy.ID,
|
||||
legacy.Username,
|
||||
legacy.Email,
|
||||
legacy.FullName,
|
||||
legacy.Mobile,
|
||||
legacy.Address,
|
||||
legacy.IsActive,
|
||||
passwordHash,
|
||||
).Scan(
|
||||
&u.ID,
|
||||
&u.Username,
|
||||
&u.Email,
|
||||
&u.IsActive,
|
||||
&u.PasswordHash,
|
||||
&u.ForcePasswordChange,
|
||||
&u.PasswordUpdatedAt,
|
||||
&u.CreatedAt,
|
||||
&u.UpdatedAt,
|
||||
&u.LastLoginAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// ➕ CREATE NEW USER (NON-LEGACY)
|
||||
// - id = sequence (>=1000)
|
||||
// -------------------------------------------------------
|
||||
func (r *MkUserRepository) CreateNewUser(
|
||||
username string,
|
||||
email string,
|
||||
isActive bool,
|
||||
) (*models.MkUser, error) {
|
||||
|
||||
var u models.MkUser
|
||||
|
||||
err := r.DB.QueryRow(`
|
||||
INSERT INTO mk_dfusr (
|
||||
username,
|
||||
email,
|
||||
is_active,
|
||||
force_password_change,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES (
|
||||
$1,$2,$3,true,now(),now()
|
||||
)
|
||||
RETURNING
|
||||
id,
|
||||
username,
|
||||
COALESCE(email,'') AS email,
|
||||
is_active,
|
||||
COALESCE(password_hash,'') AS password_hash,
|
||||
force_password_change,
|
||||
password_updated_at,
|
||||
created_at,
|
||||
updated_at,
|
||||
last_login_at
|
||||
`,
|
||||
strings.TrimSpace(username),
|
||||
strings.TrimSpace(email),
|
||||
isActive,
|
||||
).Scan(
|
||||
&u.ID,
|
||||
&u.Username,
|
||||
&u.Email,
|
||||
&u.IsActive,
|
||||
&u.PasswordHash,
|
||||
&u.ForcePasswordChange,
|
||||
&u.PasswordUpdatedAt,
|
||||
&u.CreatedAt,
|
||||
&u.UpdatedAt,
|
||||
&u.LastLoginAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// 🕒 TOUCH LAST LOGIN
|
||||
// -------------------------------------------------------
|
||||
func (r *MkUserRepository) TouchLastLogin(userID int64) error {
|
||||
_, err := r.DB.Exec(`
|
||||
UPDATE mk_dfusr
|
||||
SET last_login_at = $1,
|
||||
updated_at = $1
|
||||
WHERE id = $2
|
||||
`, time.Now(), userID)
|
||||
return err
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// 🔐 UPDATE PASSWORD
|
||||
// -------------------------------------------------------
|
||||
func (r *MkUserRepository) UpdatePassword(userID int64, newHash string) error {
|
||||
_, err := r.DB.Exec(`
|
||||
UPDATE mk_dfusr
|
||||
SET
|
||||
password_hash = $1,
|
||||
force_password_change = false,
|
||||
password_updated_at = NOW(),
|
||||
updated_at = NOW()
|
||||
WHERE id = $2
|
||||
`, newHash, userID)
|
||||
return err
|
||||
}
|
||||
33
svc/repository/permission_repository.go
Normal file
33
svc/repository/permission_repository.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// repository/permission_repository.go
|
||||
package repository
|
||||
|
||||
import "database/sql"
|
||||
|
||||
type PermissionRepository struct {
|
||||
DB *sql.DB
|
||||
}
|
||||
|
||||
func NewPermissionRepository(db *sql.DB) *PermissionRepository {
|
||||
return &PermissionRepository{DB: db}
|
||||
}
|
||||
|
||||
func (r *PermissionRepository) GetPermissionsByRoleID(roleID int64) ([]string, error) {
|
||||
rows, err := r.DB.Query(`
|
||||
SELECT route
|
||||
FROM mkdf_rol_per
|
||||
WHERE role_id = $1 AND can_access = true
|
||||
`, roleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
perms := []string{}
|
||||
for rows.Next() {
|
||||
var route string
|
||||
if err := rows.Scan(&route); err == nil {
|
||||
perms = append(perms, route)
|
||||
}
|
||||
}
|
||||
return perms, nil
|
||||
}
|
||||
80
svc/repository/refresh_token_repository.go
Normal file
80
svc/repository/refresh_token_repository.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrRefreshTokenInvalid = errors.New("refresh token invalid")
|
||||
|
||||
type RefreshTokenRepository struct {
|
||||
DB *sql.DB
|
||||
}
|
||||
|
||||
func NewRefreshTokenRepository(db *sql.DB) *RefreshTokenRepository {
|
||||
return &RefreshTokenRepository{DB: db}
|
||||
}
|
||||
|
||||
// Yeni refresh (HASH saklanır)
|
||||
func (r *RefreshTokenRepository) IssueRefreshToken(
|
||||
mkUserID int64,
|
||||
tokenHash string,
|
||||
expiresAt time.Time,
|
||||
) error {
|
||||
_, err := r.DB.Exec(`
|
||||
INSERT INTO mk_refresh_tokens (mk_user_id, token_hash, expires_at)
|
||||
VALUES ($1,$2,$3)
|
||||
`, mkUserID, tokenHash, expiresAt)
|
||||
return err
|
||||
}
|
||||
|
||||
// Tek refresh’i revoke et (rotation / logout)
|
||||
func (r *RefreshTokenRepository) RevokeByHash(hash string) error {
|
||||
_, err := r.DB.Exec(`
|
||||
UPDATE mk_refresh_tokens
|
||||
SET revoked_at = now()
|
||||
WHERE token_hash = $1
|
||||
AND revoked_at IS NULL
|
||||
`, hash)
|
||||
return err
|
||||
}
|
||||
|
||||
// Kullanıcının tüm refresh’lerini revoke et (logout-all / password change)
|
||||
func (r *RefreshTokenRepository) RevokeAllForUser(mkUserID int64) error {
|
||||
_, err := r.DB.Exec(`
|
||||
UPDATE mk_refresh_tokens
|
||||
SET revoked_at = now()
|
||||
WHERE mk_user_id = $1
|
||||
AND revoked_at IS NULL
|
||||
`, mkUserID)
|
||||
return err
|
||||
}
|
||||
|
||||
// Geçerli refresh’i tüket (validate + rotate)
|
||||
func (r *RefreshTokenRepository) ConsumeValid(tokenHash string) (int64, error) {
|
||||
var mkUserID int64
|
||||
err := r.DB.QueryRow(`
|
||||
SELECT mk_user_id
|
||||
FROM mk_refresh_tokens
|
||||
WHERE token_hash = $1
|
||||
AND revoked_at IS NULL
|
||||
AND expires_at > now()
|
||||
`, tokenHash).Scan(&mkUserID)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return 0, ErrRefreshTokenInvalid
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// tek kullanımlık: eskiyi revoke et
|
||||
_, _ = r.DB.Exec(`
|
||||
UPDATE mk_refresh_tokens
|
||||
SET revoked_at = now()
|
||||
WHERE token_hash = $1
|
||||
`, tokenHash)
|
||||
|
||||
return mkUserID, nil
|
||||
}
|
||||
276
svc/repository/user_repo.go
Normal file
276
svc/repository/user_repo.go
Normal file
@@ -0,0 +1,276 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"bssapp-backend/models"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// UserRepository → kullanıcı işlemleri için repository katmanı
|
||||
type UserRepository struct {
|
||||
DB *sql.DB
|
||||
}
|
||||
|
||||
// NewUserRepository → yeni bir UserRepository döner
|
||||
func NewUserRepository(db *sql.DB) *UserRepository {
|
||||
return &UserRepository{DB: db}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// 📋 GetUserList (User Management)
|
||||
// -------------------------------------------------------
|
||||
func (r *UserRepository) GetUserList() ([]models.User, error) {
|
||||
|
||||
query := `
|
||||
SELECT
|
||||
u.id,
|
||||
u.code,
|
||||
COALESCE(u.upass,'') as upass,
|
||||
u.is_active,
|
||||
COALESCE(u.email,''),
|
||||
COALESCE(uru.dfrole_id, 0) AS role_id,
|
||||
COALESCE(dr.code, '') AS role_code
|
||||
FROM dfusr u
|
||||
LEFT JOIN dfrole_usr uru ON u.id = uru.dfusr_id
|
||||
LEFT JOIN dfrole dr ON dr.id = uru.dfrole_id
|
||||
ORDER BY u.code
|
||||
`
|
||||
|
||||
rows, err := r.DB.Query(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var users []models.User
|
||||
for rows.Next() {
|
||||
var u models.User
|
||||
if err := rows.Scan(
|
||||
&u.ID,
|
||||
&u.Username,
|
||||
&u.Upass, // ✅ dfusr.upass
|
||||
&u.IsActive,
|
||||
&u.Email,
|
||||
&u.RoleID,
|
||||
&u.RoleCode,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
users = append(users, u)
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// ➕ CreateUser (legacy dfusr insert) [kullanıyorsan kalsın]
|
||||
// -------------------------------------------------------
|
||||
func (r *UserRepository) CreateUser(u *models.User) (int, error) {
|
||||
|
||||
query := `
|
||||
INSERT INTO dfusr (
|
||||
code,
|
||||
upass,
|
||||
is_active,
|
||||
email,
|
||||
v3_username,
|
||||
v3usergroup
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING id
|
||||
`
|
||||
|
||||
var newID int
|
||||
err := r.DB.QueryRow(
|
||||
query,
|
||||
u.Username,
|
||||
u.Upass, // ✅ upass
|
||||
u.IsActive,
|
||||
u.Email,
|
||||
u.V3Username,
|
||||
u.V3UserGroup,
|
||||
).Scan(&newID)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return newID, nil
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// 🔍 GetUserByUsername (legacy) [login için şart değil ama düzelttim]
|
||||
// -------------------------------------------------------
|
||||
func (r *UserRepository) GetUserByUsername(username string) (*models.User, error) {
|
||||
|
||||
clean := strings.TrimSpace(username)
|
||||
|
||||
query := `
|
||||
SELECT
|
||||
u.id,
|
||||
u.code,
|
||||
COALESCE(u.upass,'') as upass,
|
||||
u.is_active,
|
||||
COALESCE(u.email,''),
|
||||
|
||||
COALESCE(uru.dfrole_id,0) AS role_id,
|
||||
COALESCE(dr.code,'') AS role_code,
|
||||
|
||||
COALESCE(u.v3_username,'') as v3_username,
|
||||
COALESCE(u.v3_usergroup,0) as v3_usergroup,
|
||||
COALESCE(u.force_password_change,false) as force_password_change
|
||||
|
||||
FROM dfusr u
|
||||
|
||||
LEFT JOIN dfrole_usr uru
|
||||
ON uru.dfusr_id = u.id
|
||||
|
||||
LEFT JOIN dfrole dr
|
||||
ON dr.id = uru.dfrole_id
|
||||
|
||||
WHERE u.is_active = true
|
||||
AND (
|
||||
LOWER(u.code) = LOWER($1)
|
||||
OR LOWER(u.email) = LOWER($1)
|
||||
)
|
||||
|
||||
LIMIT 1;
|
||||
`
|
||||
|
||||
row := r.DB.QueryRow(query, clean)
|
||||
|
||||
var user models.User
|
||||
|
||||
err := row.Scan(
|
||||
&user.ID,
|
||||
&user.Username,
|
||||
&user.Upass,
|
||||
&user.IsActive,
|
||||
&user.Email,
|
||||
|
||||
&user.RoleID,
|
||||
&user.RoleCode,
|
||||
|
||||
&user.V3Username,
|
||||
&user.V3UserGroup,
|
||||
&user.ForcePasswordChange,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, errors.New("kullanıcı bulunamadı")
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// 🧪 DebugListUsers
|
||||
// -------------------------------------------------------
|
||||
func (r *UserRepository) DebugListUsers() {
|
||||
rows, err := r.DB.Query(`SELECT id, code FROM dfusr ORDER BY id LIMIT 10`)
|
||||
if err != nil {
|
||||
fmt.Println("❌ [DEBUG] Listeleme hatası:", err)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
fmt.Println("📋 [DEBUG] İlk 10 kullanıcı:")
|
||||
for rows.Next() {
|
||||
var id int
|
||||
var code string
|
||||
if err := rows.Scan(&id, &code); err == nil {
|
||||
fmt.Printf(" - %d : %s\n", id, code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// 🔁 SetUserRoles
|
||||
// -------------------------------------------------------
|
||||
func (r *UserRepository) SetUserRoles(userID int, roleIDs []int) error {
|
||||
|
||||
tx, err := r.DB.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
if _, err := tx.Exec(`DELETE FROM dfrole_usr WHERE dfusr_id = $1`, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, roleID := range roleIDs {
|
||||
if _, err := tx.Exec(
|
||||
`INSERT INTO dfrole_usr (dfrole_id, dfusr_id) VALUES ($1, $2)`,
|
||||
roleID, userID,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// 🔐 GetLegacyUserForLogin ✅ LOGIN İÇİN TEK DOĞRU FONKSİYON
|
||||
// -------------------------------------------------------
|
||||
func (r *UserRepository) GetLegacyUserForLogin(login string) (*models.User, error) {
|
||||
log.Println("🟡 LEGACY LOGIN QUERY HIT:", login)
|
||||
|
||||
login = strings.TrimSpace(login)
|
||||
|
||||
var u models.User
|
||||
|
||||
err := r.DB.QueryRow(`
|
||||
SELECT
|
||||
u.id,
|
||||
u.code,
|
||||
COALESCE(u.upass,'') as upass,
|
||||
u.is_active,
|
||||
COALESCE(u.email,''),
|
||||
COALESCE(u.dfrole_id,0) as role_id,
|
||||
COALESCE(dr.code,'') as role_code,
|
||||
COALESCE(u.force_password_change,false)
|
||||
FROM dfusr u
|
||||
LEFT JOIN dfrole dr ON dr.id = u.dfrole_id
|
||||
WHERE u.is_active = true
|
||||
AND (
|
||||
LOWER(u.code) = LOWER($1)
|
||||
OR LOWER(u.email) = LOWER($1)
|
||||
)
|
||||
LIMIT 1
|
||||
`, login).Scan(
|
||||
&u.ID,
|
||||
&u.Username,
|
||||
&u.Upass,
|
||||
&u.IsActive,
|
||||
&u.Email,
|
||||
&u.RoleID,
|
||||
&u.RoleCode,
|
||||
&u.ForcePasswordChange,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("❌ LEGACY SCAN ERROR: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prefix := u.Upass
|
||||
if len(prefix) > 4 {
|
||||
prefix = prefix[:4]
|
||||
}
|
||||
|
||||
log.Printf(
|
||||
"🧪 LEGACY UPASS OK len=%d prefix=%s",
|
||||
len(u.Upass),
|
||||
prefix,
|
||||
)
|
||||
|
||||
return &u, nil
|
||||
}
|
||||
32
svc/repository/user_role_repo.go
Normal file
32
svc/repository/user_role_repo.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package repository
|
||||
|
||||
import "database/sql"
|
||||
|
||||
type UserRole struct {
|
||||
RoleID int
|
||||
RoleCode string
|
||||
IsSystem bool
|
||||
}
|
||||
|
||||
// repository/user_roles.go
|
||||
// repository/user_roles.go
|
||||
func GetUserRolesByUserID(db *sql.DB, userID int64) ([]int, error) {
|
||||
rows, err := db.Query(`
|
||||
SELECT role_id
|
||||
FROM dfrole_usr
|
||||
WHERE mk_dfusr_id = $1
|
||||
`, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var roles []int
|
||||
for rows.Next() {
|
||||
var rid int
|
||||
if err := rows.Scan(&rid); err == nil {
|
||||
roles = append(roles, rid)
|
||||
}
|
||||
}
|
||||
return roles, nil
|
||||
}
|
||||
Reference in New Issue
Block a user