2025-12-18 13:50:39 +08:00
package repository
import (
"context"
2025-12-29 10:03:27 +08:00
"database/sql"
2025-12-25 20:52:47 +08:00
2025-12-29 10:03:27 +08:00
dbent "github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/ent/proxy"
2025-12-25 20:52:47 +08:00
"github.com/Wei-Shaw/sub2api/internal/service"
2025-12-24 21:07:21 +08:00
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
2025-12-18 13:50:39 +08:00
)
2025-12-29 10:03:27 +08:00
type sqlQuerier interface {
QueryContext ( ctx context . Context , query string , args ... any ) ( * sql . Rows , error )
}
2025-12-25 20:52:47 +08:00
type proxyRepository struct {
2025-12-29 10:03:27 +08:00
client * dbent . Client
sql sqlQuerier
}
func NewProxyRepository ( client * dbent . Client , sqlDB * sql . DB ) service . ProxyRepository {
return newProxyRepositoryWithSQL ( client , sqlDB )
2025-12-18 13:50:39 +08:00
}
2025-12-29 10:03:27 +08:00
func newProxyRepositoryWithSQL ( client * dbent . Client , sqlq sqlQuerier ) * proxyRepository {
return & proxyRepository { client : client , sql : sqlq }
2025-12-18 13:50:39 +08:00
}
2025-12-29 10:03:27 +08:00
func ( r * proxyRepository ) Create ( ctx context . Context , proxyIn * service . Proxy ) error {
builder := r . client . Proxy . Create ( ) .
SetName ( proxyIn . Name ) .
SetProtocol ( proxyIn . Protocol ) .
SetHost ( proxyIn . Host ) .
SetPort ( proxyIn . Port ) .
SetStatus ( proxyIn . Status )
if proxyIn . Username != "" {
builder . SetUsername ( proxyIn . Username )
}
if proxyIn . Password != "" {
builder . SetPassword ( proxyIn . Password )
}
created , err := builder . Save ( ctx )
2025-12-26 15:40:24 +08:00
if err == nil {
2025-12-29 10:03:27 +08:00
applyProxyEntityToService ( proxyIn , created )
2025-12-26 15:40:24 +08:00
}
return err
2025-12-18 13:50:39 +08:00
}
2025-12-26 15:40:24 +08:00
func ( r * proxyRepository ) GetByID ( ctx context . Context , id int64 ) ( * service . Proxy , error ) {
2025-12-29 10:03:27 +08:00
m , err := r . client . Proxy . Get ( ctx , id )
2025-12-18 13:50:39 +08:00
if err != nil {
2025-12-29 10:03:27 +08:00
if dbent . IsNotFound ( err ) {
return nil , service . ErrProxyNotFound
}
return nil , err
2025-12-18 13:50:39 +08:00
}
2025-12-29 10:03:27 +08:00
return proxyEntityToService ( m ) , nil
2025-12-18 13:50:39 +08:00
}
2026-02-05 18:40:49 +08:00
func ( r * proxyRepository ) ListByIDs ( ctx context . Context , ids [ ] int64 ) ( [ ] service . Proxy , error ) {
if len ( ids ) == 0 {
return [ ] service . Proxy { } , nil
}
proxies , err := r . client . Proxy . Query ( ) .
Where ( proxy . IDIn ( ids ... ) ) .
All ( ctx )
if err != nil {
return nil , err
}
out := make ( [ ] service . Proxy , 0 , len ( proxies ) )
for i := range proxies {
out = append ( out , * proxyEntityToService ( proxies [ i ] ) )
}
return out , nil
}
2025-12-29 10:03:27 +08:00
func ( r * proxyRepository ) Update ( ctx context . Context , proxyIn * service . Proxy ) error {
builder := r . client . Proxy . UpdateOneID ( proxyIn . ID ) .
SetName ( proxyIn . Name ) .
SetProtocol ( proxyIn . Protocol ) .
SetHost ( proxyIn . Host ) .
SetPort ( proxyIn . Port ) .
SetStatus ( proxyIn . Status )
if proxyIn . Username != "" {
builder . SetUsername ( proxyIn . Username )
} else {
builder . ClearUsername ( )
}
if proxyIn . Password != "" {
builder . SetPassword ( proxyIn . Password )
} else {
builder . ClearPassword ( )
}
updated , err := builder . Save ( ctx )
2025-12-26 15:40:24 +08:00
if err == nil {
2025-12-29 10:03:27 +08:00
applyProxyEntityToService ( proxyIn , updated )
return nil
}
if dbent . IsNotFound ( err ) {
return service . ErrProxyNotFound
2025-12-26 15:40:24 +08:00
}
return err
2025-12-18 13:50:39 +08:00
}
2025-12-25 20:52:47 +08:00
func ( r * proxyRepository ) Delete ( ctx context . Context , id int64 ) error {
2025-12-29 10:03:27 +08:00
_ , err := r . client . Proxy . Delete ( ) . Where ( proxy . IDEQ ( id ) ) . Exec ( ctx )
return err
2025-12-18 13:50:39 +08:00
}
2025-12-26 15:40:24 +08:00
func ( r * proxyRepository ) List ( ctx context . Context , params pagination . PaginationParams ) ( [ ] service . Proxy , * pagination . PaginationResult , error ) {
2025-12-18 13:50:39 +08:00
return r . ListWithFilters ( ctx , params , "" , "" , "" )
}
// ListWithFilters lists proxies with optional filtering by protocol, status, and search query
2025-12-26 15:40:24 +08:00
func ( r * proxyRepository ) ListWithFilters ( ctx context . Context , params pagination . PaginationParams , protocol , status , search string ) ( [ ] service . Proxy , * pagination . PaginationResult , error ) {
2025-12-29 10:03:27 +08:00
q := r . client . Proxy . Query ( )
2025-12-18 13:50:39 +08:00
if protocol != "" {
2025-12-29 10:03:27 +08:00
q = q . Where ( proxy . ProtocolEQ ( protocol ) )
2025-12-18 13:50:39 +08:00
}
if status != "" {
2025-12-29 10:03:27 +08:00
q = q . Where ( proxy . StatusEQ ( status ) )
2025-12-18 13:50:39 +08:00
}
if search != "" {
2025-12-29 10:03:27 +08:00
q = q . Where ( proxy . NameContainsFold ( search ) )
2025-12-18 13:50:39 +08:00
}
2025-12-29 10:03:27 +08:00
total , err := q . Count ( ctx )
if err != nil {
2025-12-18 13:50:39 +08:00
return nil , nil , err
}
2025-12-29 10:03:27 +08:00
proxies , err := q .
Offset ( params . Offset ( ) ) .
Limit ( params . Limit ( ) ) .
Order ( dbent . Desc ( proxy . FieldID ) ) .
All ( ctx )
if err != nil {
2025-12-18 13:50:39 +08:00
return nil , nil , err
}
2025-12-26 15:40:24 +08:00
outProxies := make ( [ ] service . Proxy , 0 , len ( proxies ) )
for i := range proxies {
2025-12-29 10:03:27 +08:00
outProxies = append ( outProxies , * proxyEntityToService ( proxies [ i ] ) )
2025-12-18 13:50:39 +08:00
}
2025-12-29 10:03:27 +08:00
return outProxies , paginationResultFromTotal ( int64 ( total ) , params ) , nil
2025-12-18 13:50:39 +08:00
}
2026-01-08 21:20:12 +08:00
// ListWithFiltersAndAccountCount lists proxies with filters and includes account count per proxy
func ( r * proxyRepository ) ListWithFiltersAndAccountCount ( ctx context . Context , params pagination . PaginationParams , protocol , status , search string ) ( [ ] service . ProxyWithAccountCount , * pagination . PaginationResult , error ) {
q := r . client . Proxy . Query ( )
if protocol != "" {
q = q . Where ( proxy . ProtocolEQ ( protocol ) )
}
if status != "" {
q = q . Where ( proxy . StatusEQ ( status ) )
}
if search != "" {
q = q . Where ( proxy . NameContainsFold ( search ) )
}
total , err := q . Count ( ctx )
if err != nil {
return nil , nil , err
}
proxies , err := q .
Offset ( params . Offset ( ) ) .
Limit ( params . Limit ( ) ) .
Order ( dbent . Desc ( proxy . FieldID ) ) .
All ( ctx )
if err != nil {
return nil , nil , err
}
// Get account counts
counts , err := r . GetAccountCountsForProxies ( ctx )
if err != nil {
return nil , nil , err
}
// Build result with account counts
result := make ( [ ] service . ProxyWithAccountCount , 0 , len ( proxies ) )
for i := range proxies {
proxyOut := proxyEntityToService ( proxies [ i ] )
if proxyOut == nil {
continue
}
result = append ( result , service . ProxyWithAccountCount {
Proxy : * proxyOut ,
AccountCount : counts [ proxyOut . ID ] ,
} )
}
return result , paginationResultFromTotal ( int64 ( total ) , params ) , nil
}
2025-12-26 15:40:24 +08:00
func ( r * proxyRepository ) ListActive ( ctx context . Context ) ( [ ] service . Proxy , error ) {
2025-12-29 10:03:27 +08:00
proxies , err := r . client . Proxy . Query ( ) .
Where ( proxy . StatusEQ ( service . StatusActive ) ) .
All ( ctx )
2025-12-26 15:40:24 +08:00
if err != nil {
return nil , err
}
outProxies := make ( [ ] service . Proxy , 0 , len ( proxies ) )
for i := range proxies {
2025-12-29 10:03:27 +08:00
outProxies = append ( outProxies , * proxyEntityToService ( proxies [ i ] ) )
2025-12-26 15:40:24 +08:00
}
return outProxies , nil
2025-12-18 13:50:39 +08:00
}
// ExistsByHostPortAuth checks if a proxy with the same host, port, username, and password exists
2025-12-25 20:52:47 +08:00
func ( r * proxyRepository ) ExistsByHostPortAuth ( ctx context . Context , host string , port int , username , password string ) ( bool , error ) {
2025-12-29 10:03:27 +08:00
q := r . client . Proxy . Query ( ) .
Where ( proxy . HostEQ ( host ) , proxy . PortEQ ( port ) )
if username == "" {
q = q . Where ( proxy . Or ( proxy . UsernameIsNil ( ) , proxy . UsernameEQ ( "" ) ) )
} else {
q = q . Where ( proxy . UsernameEQ ( username ) )
2025-12-18 13:50:39 +08:00
}
2025-12-29 10:03:27 +08:00
if password == "" {
q = q . Where ( proxy . Or ( proxy . PasswordIsNil ( ) , proxy . PasswordEQ ( "" ) ) )
} else {
q = q . Where ( proxy . PasswordEQ ( password ) )
}
count , err := q . Count ( ctx )
return count > 0 , err
2025-12-18 13:50:39 +08:00
}
// CountAccountsByProxyID returns the number of accounts using a specific proxy
2025-12-25 20:52:47 +08:00
func ( r * proxyRepository ) CountAccountsByProxyID ( ctx context . Context , proxyID int64 ) ( int64 , error ) {
2025-12-18 13:50:39 +08:00
var count int64
2026-01-14 19:45:29 +08:00
if err := scanSingleRow ( ctx , r . sql , "SELECT COUNT(*) FROM accounts WHERE proxy_id = $1 AND deleted_at IS NULL" , [ ] any { proxyID } , & count ) ; err != nil {
2025-12-29 10:03:27 +08:00
return 0 , err
}
return count , nil
2025-12-18 13:50:39 +08:00
}
2026-01-14 19:45:29 +08:00
func ( r * proxyRepository ) ListAccountSummariesByProxyID ( ctx context . Context , proxyID int64 ) ( [ ] service . ProxyAccountSummary , error ) {
rows , err := r . sql . QueryContext ( ctx , `
SELECT id , name , platform , type , notes
FROM accounts
WHERE proxy_id = $ 1 AND deleted_at IS NULL
ORDER BY id DESC
` , proxyID )
if err != nil {
return nil , err
}
defer func ( ) { _ = rows . Close ( ) } ( )
out := make ( [ ] service . ProxyAccountSummary , 0 )
for rows . Next ( ) {
var (
id int64
name string
platform string
accType string
notes sql . NullString
)
if err := rows . Scan ( & id , & name , & platform , & accType , & notes ) ; err != nil {
return nil , err
}
var notesPtr * string
if notes . Valid {
notesPtr = & notes . String
}
out = append ( out , service . ProxyAccountSummary {
ID : id ,
Name : name ,
Platform : platform ,
Type : accType ,
Notes : notesPtr ,
} )
}
if err := rows . Err ( ) ; err != nil {
return nil , err
}
return out , nil
}
2025-12-18 13:50:39 +08:00
// GetAccountCountsForProxies returns a map of proxy ID to account count for all proxies
2025-12-30 16:41:45 +08:00
func ( r * proxyRepository ) GetAccountCountsForProxies ( ctx context . Context ) ( counts map [ int64 ] int64 , err error ) {
2025-12-31 14:11:57 +08:00
rows , err := r . sql . QueryContext ( ctx , "SELECT proxy_id, COUNT(*) AS count FROM accounts WHERE proxy_id IS NOT NULL AND deleted_at IS NULL GROUP BY proxy_id" )
2025-12-18 13:50:39 +08:00
if err != nil {
return nil , err
}
2025-12-30 16:41:45 +08:00
defer func ( ) {
if closeErr := rows . Close ( ) ; closeErr != nil && err == nil {
err = closeErr
counts = nil
}
} ( )
2025-12-18 13:50:39 +08:00
2025-12-30 16:41:45 +08:00
counts = make ( map [ int64 ] int64 )
2025-12-29 10:03:27 +08:00
for rows . Next ( ) {
var proxyID , count int64
2025-12-30 16:41:45 +08:00
if err = rows . Scan ( & proxyID , & count ) ; err != nil {
2025-12-29 10:03:27 +08:00
return nil , err
}
counts [ proxyID ] = count
}
2025-12-30 16:41:45 +08:00
if err = rows . Err ( ) ; err != nil {
2025-12-29 10:03:27 +08:00
return nil , err
2025-12-18 13:50:39 +08:00
}
return counts , nil
}
// ListActiveWithAccountCount returns all active proxies with account count, sorted by creation time descending
2025-12-26 15:40:24 +08:00
func ( r * proxyRepository ) ListActiveWithAccountCount ( ctx context . Context ) ( [ ] service . ProxyWithAccountCount , error ) {
2025-12-29 10:03:27 +08:00
proxies , err := r . client . Proxy . Query ( ) .
Where ( proxy . StatusEQ ( service . StatusActive ) ) .
Order ( dbent . Desc ( proxy . FieldCreatedAt ) ) .
All ( ctx )
2025-12-18 13:50:39 +08:00
if err != nil {
return nil , err
}
// Get account counts
counts , err := r . GetAccountCountsForProxies ( ctx )
if err != nil {
return nil , err
}
// Build result with account counts
2025-12-26 15:40:24 +08:00
result := make ( [ ] service . ProxyWithAccountCount , 0 , len ( proxies ) )
for i := range proxies {
2025-12-29 10:03:27 +08:00
proxyOut := proxyEntityToService ( proxies [ i ] )
if proxyOut == nil {
2025-12-26 15:40:24 +08:00
continue
2025-12-18 13:50:39 +08:00
}
2025-12-26 15:40:24 +08:00
result = append ( result , service . ProxyWithAccountCount {
2025-12-29 10:03:27 +08:00
Proxy : * proxyOut ,
AccountCount : counts [ proxyOut . ID ] ,
2025-12-26 15:40:24 +08:00
} )
2025-12-18 13:50:39 +08:00
}
return result , nil
}
2025-12-26 15:40:24 +08:00
2025-12-29 10:03:27 +08:00
func proxyEntityToService ( m * dbent . Proxy ) * service . Proxy {
2025-12-26 15:40:24 +08:00
if m == nil {
return nil
}
2025-12-29 10:03:27 +08:00
out := & service . Proxy {
2025-12-26 15:40:24 +08:00
ID : m . ID ,
Name : m . Name ,
Protocol : m . Protocol ,
Host : m . Host ,
Port : m . Port ,
Status : m . Status ,
CreatedAt : m . CreatedAt ,
UpdatedAt : m . UpdatedAt ,
}
2025-12-29 10:03:27 +08:00
if m . Username != nil {
out . Username = * m . Username
2025-12-26 15:40:24 +08:00
}
2025-12-29 10:03:27 +08:00
if m . Password != nil {
out . Password = * m . Password
2025-12-26 15:40:24 +08:00
}
2025-12-29 10:03:27 +08:00
return out
2025-12-26 15:40:24 +08:00
}
2025-12-29 10:03:27 +08:00
func applyProxyEntityToService ( dst * service . Proxy , src * dbent . Proxy ) {
if dst == nil || src == nil {
2025-12-26 15:40:24 +08:00
return
}
2025-12-29 10:03:27 +08:00
dst . ID = src . ID
dst . CreatedAt = src . CreatedAt
dst . UpdatedAt = src . UpdatedAt
2025-12-26 15:40:24 +08:00
}