afro SMS and partial ArifPay Payment Gateway integrations

This commit is contained in:
Yared Yemane 2025-12-18 18:06:26 +03:00
parent 2bc43f5076
commit 47d70b029f
74 changed files with 3507 additions and 13783 deletions

View File

@ -11,10 +11,14 @@ import (
"Yimaru-Backend/internal/repository" "Yimaru-Backend/internal/repository"
"Yimaru-Backend/internal/services/arifpay" "Yimaru-Backend/internal/services/arifpay"
"Yimaru-Backend/internal/services/authentication" "Yimaru-Backend/internal/services/authentication"
"Yimaru-Backend/internal/services/currency" issuereporting "Yimaru-Backend/internal/services/issue_reporting"
"Yimaru-Backend/internal/services/messenger"
notificationservice "Yimaru-Backend/internal/services/notification" notificationservice "Yimaru-Backend/internal/services/notification"
"Yimaru-Backend/internal/services/recommendation" "Yimaru-Backend/internal/services/recommendation"
referralservice "Yimaru-Backend/internal/services/referal" "Yimaru-Backend/internal/services/settings"
"context"
// referralservice "Yimaru-Backend/internal/services/referal"
"Yimaru-Backend/internal/services/transaction" "Yimaru-Backend/internal/services/transaction"
"Yimaru-Backend/internal/services/user" "Yimaru-Backend/internal/services/user"
httpserver "Yimaru-Backend/internal/web_server" httpserver "Yimaru-Backend/internal/web_server"
@ -71,14 +75,14 @@ func main() {
v := customvalidator.NewCustomValidator(validator.New()) v := customvalidator.NewCustomValidator(validator.New())
// Initialize services // Initialize services
// settingRepo := repository.NewSettingStore(store) settingRepo := repository.NewSettingStore(store)
// if err := settingRepo.EnsureAllSettingsExist(context.Background()); err != nil { if err := settingRepo.EnsureAllSettingsExist(context.Background()); err != nil {
// log.Fatalf("failed to ensure settings: %v", err) log.Fatalf("failed to ensure settings: %v", err)
// } }
// settingSvc := settings.NewService(settingRepo) settingSvc := settings.NewService(settingRepo)
// messengerSvc := messenger.NewService(settingSvc, cfg) messengerSvc := messenger.NewService(settingSvc, cfg)
// statSvc := stats.NewService( // statSvc := stats.NewService(
// repository.NewCompanyStatStore(store), // repository.NewCompanyStatStore(store),
// repository.NewBranchStatStore(store), // repository.NewBranchStatStore(store),
@ -92,7 +96,7 @@ func main() {
userSvc := user.NewService( userSvc := user.NewService(
repository.NewUserStore(store), repository.NewUserStore(store),
repository.NewOTPStore(store), repository.NewOTPStore(store),
// messengerSvc, messengerSvc,
cfg, cfg,
) )
// leagueSvc := league.New(repository.NewLeagueStore(store)) // leagueSvc := league.New(repository.NewLeagueStore(store))
@ -146,8 +150,8 @@ func main() {
// logger, // logger,
// ) // )
branchSvc := branch.NewService(repository.NewBranchStore(store)) // branchSvc := branch.NewService(repository.NewBranchStore(store))
companySvc := company.NewService(repository.NewCompanyStore(store)) // companySvc := company.NewService(repository.NewCompanyStore(store))
// ticketSvc := ticke.NewService( // ticketSvc := ticke.NewService(
// repository.NewTicketStore(store), // repository.NewTicketStore(store),
@ -184,25 +188,25 @@ func main() {
// *userSvc, // *userSvc,
// ) // )
bonusSvc := bonus.NewService( // bonusSvc := bonus.NewService(
repository.NewBonusStore(store), // repository.NewBonusStore(store),
settingSvc, // settingSvc,
notificationSvc, // notificationSvc,
domain.MongoDBLogger, // domain.MongoDBLogger,
) // )
// virtualGamesRepo := repository.NewVirtualGameRepository(store) // virtualGamesRepo := repository.NewVirtualGameRepository(store)
recommendationRepo := repository.NewRecommendationRepository(store) recommendationRepo := repository.NewRecommendationRepository(store)
referalSvc := referralservice.New( // referalSvc := referralservice.New(
repository.NewReferralStore(store), // repository.NewReferralStore(store),
*settingSvc, // *settingSvc,
cfg, // cfg,
logger, // logger,
domain.MongoDBLogger, // domain.MongoDBLogger,
) // )
raffleSvc := raffle.NewService( // raffleSvc := raffle.NewService(
repository.NewRaffleStore(store), // repository.NewRaffleStore(store),
) // )
// virtualGameSvc := virtualgameservice.New(virtualGamesRepo,, store, cfg, logger) // virtualGameSvc := virtualgameservice.New(virtualGamesRepo,, store, cfg, logger)
// aleaService := alea.NewAleaPlayService(virtualGamesRepo,, cfg, logger) // aleaService := alea.NewAleaPlayService(virtualGamesRepo,, cfg, logger)
// veliCLient := veli.NewClient(cfg) // veliCLient := veli.NewClient(cfg)
@ -224,15 +228,14 @@ func main() {
// chapaClient, // chapaClient,
// ) // )
currRepo := repository.NewCurrencyPostgresRepository(store) // currRepo := repository.NewCurrencyPostgresRepository(store)
fixerFertcherSvc := currency.NewFixerFetcher( // fixerFertcherSvc := currency.NewFixerFetcher(
cfg.FIXER_API_KEY, // cfg.FIXER_API_KEY,
cfg.FIXER_BASE_URL, // cfg.FIXER_BASE_URL,
) // )
transactionSvc := transaction.NewService( transactionSvc := transaction.NewService(
repository.NewTransactionStore(store), repository.NewTransactionStore(store),
*branchSvc,
*userSvc, *userSvc,
) )
@ -263,8 +266,8 @@ func main() {
// go httpserver.SetupReportandVirtualGameCronJobs(context.Background(), reportSvc, orchestrationSvc, "C:/Users/User/Desktop") // go httpserver.SetupReportandVirtualGameCronJobs(context.Background(), reportSvc, orchestrationSvc, "C:/Users/User/Desktop")
// go httpserver.ProcessBetCashback(context.TODO(), betSvc) // go httpserver.ProcessBetCashback(context.TODO(), betSvc)
bankRepository := repository.NewBankRepository(store) // bankRepository := repository.NewBankRepository(store)
instSvc := institutions.New(bankRepository) // instSvc := institutions.New(bankRepository)
// Initialize report worker with CSV exporter // Initialize report worker with CSV exporter
// csvExporter := infrastructure.CSVExporter{ // csvExporter := infrastructure.CSVExporter{
// ExportPath: cfg.ReportExportPath, // Make sure to add this to your config // ExportPath: cfg.ReportExportPath, // Make sure to add this to your config
@ -297,11 +300,11 @@ func main() {
// 5*time.Minute, // 5*time.Minute,
// ) // )
currSvc := currency.NewService( // currSvc := currency.NewService(
currRepo, // currRepo,
cfg.BASE_CURRENCY, // cfg.BASE_CURRENCY,
fixerFertcherSvc, // fixerFertcherSvc,
) // )
// exchangeWorker := currency.NewExchangeRateWorker(fixerFertcherSvc, logger, cfg) // exchangeWorker := currency.NewExchangeRateWorker(fixerFertcherSvc, logger, cfg)
// exchangeWorker.Start(context.Background()) // exchangeWorker.Start(context.Background())
@ -330,13 +333,8 @@ func main() {
// Initialize and start HTTP server // Initialize and start HTTP server
app := httpserver.NewApp( app := httpserver.NewApp(
// directdeposit,
// telebirrSvc,
arifpaySvc, arifpaySvc,
// santimpaySvc,
issueReportingSvc, issueReportingSvc,
instSvc,
currSvc,
cfg.Port, cfg.Port,
v, v,
settingSvc, settingSvc,
@ -347,16 +345,9 @@ func main() {
JwtAccessExpiry: cfg.AccessExpiry, JwtAccessExpiry: cfg.AccessExpiry,
}, },
userSvc, userSvc,
// chapaSvc,
transactionSvc, transactionSvc,
branchSvc,
companySvc,
notificationSvc, notificationSvc,
referalSvc,
raffleSvc,
bonusSvc,
recommendationSvc, recommendationSvc,
statSvc,
cfg, cfg,
domain.MongoDBLogger, domain.MongoDBLogger,
) )

View File

@ -70,7 +70,7 @@ VALUES
'Sarah', 'Sarah',
'Connor', 'Connor',
'SarahC', 'SarahC',
'sarah.connor@yimaru.com', 'yaredyemane1@gmail.com',
NULL, NULL,
crypt('password@123', gen_salt('bf'))::bytea, crypt('password@123', gen_salt('bf'))::bytea,
'SUPER_ADMIN', 'SUPER_ADMIN',

View File

@ -1,3 +1,8 @@
-- =========================================
-- Notifications
-- =========================================
DROP TABLE IF EXISTS global_settings;
-- ========================================= -- =========================================
-- Notifications -- Notifications
-- ========================================= -- =========================================

View File

@ -1,17 +0,0 @@
DROP TABLE IF EXISTS referrals;
DROP TABLE IF EXISTS referral_settings;
DROP TYPE IF EXISTS ReferralStatus;
ALTER TABLE users
DROP COLUMN referral_code;
ALTER TABLE users
DROP COLUMN referred_by;
ALTER TABLE wallet
DROP COLUMN bonus_balance;
ALTER TABLE wallet
DROP COLUMN cash_balance;

View File

@ -1,45 +0,0 @@
CREATE TABLE IF NOT EXISTS referral_codes (
id BIGSERIAL PRIMARY KEY,
code VARCHAR(12) NOT NULL UNIQUE,
referrer_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
max_uses INT,
current_uses INT NOT NULL DEFAULT 0,
incentive_type TEXT NOT NULL CHECK (
incentive_type IN (
'course_access',
'discount',
'certificate_unlock',
'feature_unlock'
)
),
incentive_value TEXT, -- e.g. course_id, percentage, feature_key
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_referral_codes_referrer_id
ON referral_codes (referrer_id);
CREATE INDEX idx_referral_codes_code
ON referral_codes (code);
CREATE TABLE IF NOT EXISTS user_referrals (
id BIGSERIAL PRIMARY KEY,
referrer_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
referred_user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
referral_code_id BIGINT NOT NULL REFERENCES referral_codes(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (referred_user_id),
UNIQUE (referrer_id, referred_user_id)
);

View File

@ -1,2 +0,0 @@
DROP TABLE IF EXISTS reported_issues;

View File

@ -1,14 +0,0 @@
CREATE TABLE IF NOT EXISTS reported_issues (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
user_role VARCHAR(255) NOT NULL,
subject TEXT NOT NULL,
description TEXT NOT NULL,
issue_type TEXT NOT NULL,
-- e.g., "deposit", "withdrawal", "bet", "technical"
status TEXT NOT NULL DEFAULT 'pending',
-- pending, in_progress, resolved, rejected
metadata JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

View File

@ -1,14 +1,3 @@
-- name: GetUserByEmailPhone :one
SELECT *
FROM users
WHERE (
email = $1
OR phone_number = $2
)
AND (
organization_id = sqlc.narg('organization_id')
OR sqlc.narg('organization_id') IS NULL
);
-- name: CreateRefreshToken :exec -- name: CreateRefreshToken :exec
INSERT INTO refresh_tokens (user_id, token, expires_at, created_at, revoked) INSERT INTO refresh_tokens (user_id, token, expires_at, created_at, revoked)
VALUES ($1, $2, $3, $4, $5); VALUES ($1, $2, $3, $4, $5);

View File

@ -1,37 +1,37 @@
-- -- name: CreateReportedIssue :one -- name: CreateReportedIssue :one
-- INSERT INTO reported_issues ( INSERT INTO reported_issues (
-- user_id, user_id,
-- user_role, user_role,
-- subject, subject,
-- description, description,
-- issue_type, issue_type,
-- metadata metadata
-- ) )
-- VALUES ($1, $2, $3, $4, $5, $6) VALUES ($1, $2, $3, $4, $5, $6)
-- RETURNING *; RETURNING *;
-- -- name: ListReportedIssues :many -- name: ListReportedIssues :many
-- SELECT * SELECT *
-- FROM reported_issues FROM reported_issues
-- ORDER BY created_at DESC ORDER BY created_at DESC
-- LIMIT $1 OFFSET $2; LIMIT $1 OFFSET $2;
-- -- name: ListReportedIssuesByUser :many -- name: ListReportedIssuesByUser :many
-- SELECT * SELECT *
-- FROM reported_issues FROM reported_issues
-- WHERE user_id = $1 WHERE user_id = $1
-- ORDER BY created_at DESC ORDER BY created_at DESC
-- LIMIT $2 OFFSET $3; LIMIT $2 OFFSET $3;
-- -- name: CountReportedIssues :one -- name: CountReportedIssues :one
-- SELECT COUNT(*) SELECT COUNT(*)
-- FROM reported_issues; FROM reported_issues;
-- -- name: CountReportedIssuesByUser :one -- name: CountReportedIssuesByUser :one
-- SELECT COUNT(*) SELECT COUNT(*)
-- FROM reported_issues FROM reported_issues
-- WHERE user_id = $1; WHERE user_id = $1;
-- -- name: UpdateReportedIssueStatus :exec -- name: UpdateReportedIssueStatus :exec
-- UPDATE reported_issues UPDATE reported_issues
-- SET status = $2, SET status = $2,
-- updated_at = NOW() updated_at = NOW()
-- WHERE id = $1; WHERE id = $1;
-- -- name: DeleteReportedIssue :exec -- name: DeleteReportedIssue :exec
-- DELETE FROM reported_issues DELETE FROM reported_issues
-- WHERE id = $1; WHERE id = $1;

View File

@ -1,99 +1,72 @@
-- -- name: CreateNotification :one -- name: CreateNotification :one
-- INSERT INTO notifications ( INSERT INTO notifications (
-- id, user_id,
-- recipient_id, type,
-- type, level,
-- level, channel,
-- error_severity, title,
-- reciever, message,
-- is_read, payload
-- delivery_status, )
-- delivery_channel, VALUES (
-- payload, $1,
-- priority, $2,
-- timestamp, $3,
-- expires, $4,
-- img, $5,
-- metadata $6,
-- ) $7
-- VALUES ( )
-- $1, RETURNING *;
-- $2,
-- $3,
-- $4,
-- $5,
-- $6,
-- $7,
-- $8,
-- $9,
-- $10,
-- $11,
-- $12,
-- $13,
-- $14,
-- $15
-- )
-- RETURNING *;
-- -- name: GetNotification :one
-- SELECT *
-- FROM notifications
-- WHERE id = $1
-- LIMIT 1;
-- -- name: GetAllNotifications :many
-- SELECT *
-- FROM notifications
-- ORDER BY timestamp DESC
-- LIMIT $1 OFFSET $2;
-- -- name: GetTotalNotificationCount :one
-- SELECT COUNT(*)
-- FROM notifications;
-- -- name: GetUserNotifications :many
-- SELECT *
-- FROM notifications
-- WHERE recipient_id = $1
-- ORDER BY timestamp DESC
-- LIMIT $2 OFFSET $3;
-- -- name: GetUserNotificationCount :one
-- SELECT COUNT(*)
-- FROM notifications
-- WHERE recipient_id = $1;
-- -- name: CountUnreadNotifications :one
-- SELECT count(id)
-- FROM notifications
-- WHERE recipient_id = $1
-- AND is_read = false;
-- -- name: UpdateNotificationStatus :one
-- UPDATE notifications
-- SET delivery_status = $2,
-- is_read = $3,
-- metadata = $4
-- WHERE id = $1
-- RETURNING *;
-- -- name: ListFailedNotifications :many
-- SELECT *
-- FROM notifications
-- WHERE delivery_status = 'failed'
-- AND timestamp < NOW() - INTERVAL '1 hour'
-- ORDER BY timestamp ASC
-- LIMIT $1;
-- -- name: ListRecipientIDsByReceiver :many
-- SELECT recipient_id
-- FROM notifications
-- WHERE reciever = $1;
-- -- name: GetNotificationCounts :many
-- SELECT COUNT(*) as total,
-- COUNT(
-- CASE
-- WHEN is_read = true THEN 1
-- END
-- ) as read,
-- COUNT(
-- CASE
-- WHEN is_read = false THEN 1
-- END
-- ) as unread
-- FROM notifications;
-- -- name: DeleteOldNotifications :exec -- name: GetNotification :one
-- DELETE FROM notifications SELECT *
-- WHERE expires < now(); FROM notifications
WHERE id = $1
LIMIT 1;
-- name: GetAllNotifications :many
SELECT *
FROM notifications
ORDER BY created_at DESC
LIMIT $1 OFFSET $2;
-- name: GetTotalNotificationCount :one
SELECT COUNT(*)
FROM notifications;
-- name: GetUserNotifications :many
SELECT *
FROM notifications
WHERE user_id = $1
ORDER BY created_at DESC
LIMIT $2 OFFSET $3;
-- name: GetUserNotificationCount :one
SELECT COUNT(*)
FROM notifications
WHERE user_id = $1;
-- name: CountUnreadNotifications :one
SELECT COUNT(*)
FROM notifications
WHERE user_id = $1
AND is_read = FALSE;
-- name: MarkNotificationAsRead :one
UPDATE notifications
SET is_read = TRUE,
read_at = NOW()
WHERE id = $1
RETURNING *;
-- name: MarkAllUserNotificationsAsRead :exec
UPDATE notifications
SET is_read = TRUE,
read_at = NOW()
WHERE user_id = $1
AND is_read = FALSE;
-- name: DeleteUserNotifications :exec
DELETE FROM notifications
WHERE user_id = $1;

View File

@ -1,48 +1,17 @@
-- -- name: InsertGlobalSetting :exec -- name: InsertGlobalSetting :exec
-- INSERT INTO global_settings (key, value) INSERT INTO global_settings (key, value)
-- VALUES ($1, $2) ON CONFLICT (key) DO VALUES ($1, $2) ON CONFLICT (key) DO
-- UPDATE UPDATE
-- SET value = EXCLUDED.value; SET value = EXCLUDED.value;
-- -- name: GetGlobalSettings :many -- name: GetGlobalSettings :many
-- SELECT * SELECT *
-- FROM global_settings; FROM global_settings;
-- -- name: GetGlobalSetting :one -- name: GetGlobalSetting :one
-- SELECT * SELECT *
-- FROM global_settings FROM global_settings
-- WHERE key = $1; WHERE key = $1;
-- -- name: UpdateGlobalSetting :exec -- name: UpdateGlobalSetting :exec
-- UPDATE global_settings UPDATE global_settings
-- SET value = $2, SET value = $2,
-- updated_at = CURRENT_TIMESTAMP updated_at = CURRENT_TIMESTAMP
-- WHERE key = $1; WHERE key = $1;
-- -- name: InsertCompanySetting :exec
-- INSERT INTO company_settings (company_id, key, value)
-- VALUES ($1, $2, $3) ON CONFLICT (company_id, key) DO
-- UPDATE
-- SET value = EXCLUDED.value;
-- -- name: GetAllCompanySettings :many
-- SELECT *
-- FROM company_settings;
-- -- name: GetCompanySetting :many
-- SELECT *
-- FROM company_settings
-- WHERE company_id = $1;
-- -- name: GetCompanySettingsByKey :many
-- SELECT *
-- FROM company_settings
-- WHERE key = $1;
-- -- name: GetOverrideSettings :many
-- SELECT gs.key,
-- gs.created_at,
-- gs.updated_at,
-- COALESCE(cs.value, gs.value) AS value
-- FROM global_settings gs
-- LEFT JOIN company_settings cs ON cs.key = gs.key
-- AND cs.company_id = $1;
-- -- name: DeleteCompanySetting :exec
-- DELETE FROM company_settings
-- WHERE company_id = $1
-- AND key = $2;
-- -- name: DeleteAllCompanySetting :exec
-- DELETE FROM company_settings
-- WHERE company_id = $1;

View File

@ -62,7 +62,9 @@ SELECT *
FROM users FROM users
WHERE id = $1; WHERE id = $1;
-- name: GetAllUsers :many -- name: GetAllUsers :many
SELECT id, SELECT
COUNT(*) OVER () AS total_count,
id,
first_name, first_name,
last_name, last_name,
nick_name, nick_name,
@ -81,7 +83,7 @@ SELECT id,
suspended_at, suspended_at,
organization_id organization_id
FROM users FROM users
wHERE ( WHERE (
role = $1 role = $1
OR $1 IS NULL OR $1 IS NULL
) )
@ -103,7 +105,8 @@ wHERE (
created_at < sqlc.narg('created_after') created_at < sqlc.narg('created_after')
OR sqlc.narg('created_after') IS NULL OR sqlc.narg('created_after') IS NULL
) )
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset'); LIMIT sqlc.narg('limit')
OFFSET sqlc.narg('offset');
-- name: GetTotalUsers :one -- name: GetTotalUsers :one
SELECT COUNT(*) SELECT COUNT(*)
FROM users FROM users
@ -159,12 +162,6 @@ WHERE id = $4;
UPDATE users UPDATE users
SET organization_id = $1 SET organization_id = $1
WHERE id = $2; WHERE id = $2;
-- name: SuspendUser :exec
UPDATE users
SET suspended = $1,
suspended_at = $2,
updated_at = CURRENT_TIMESTAMP
WHERE id = $3;
-- name: DeleteUser :exec -- name: DeleteUser :exec
DELETE FROM users DELETE FROM users
WHERE id = $1; WHERE id = $1;
@ -183,14 +180,16 @@ SELECT EXISTS (
AND users.email IS NOT NULL AND users.email IS NOT NULL
AND users.organization_id = $2 AND users.organization_id = $2
) AS email_exists; ) AS email_exists;
-- name: GetUserByEmail :one -- name: GetUserByEmailPhone :one
SELECT id, SELECT
id,
first_name, first_name,
last_name, last_name,
nick_name, nick_name,
email, email,
phone_number, phone_number,
role, role,
password, -- added this line
age, age,
education_level, education_level,
country, country,
@ -203,30 +202,12 @@ SELECT id,
suspended_at, suspended_at,
organization_id organization_id
FROM users FROM users
WHERE email = $1 WHERE organization_id = $3
AND organization_id = $2; AND (
-- name: GetUserByPhone :one (email = $1 AND $1 IS NOT NULL)
SELECT id, OR (phone_number = $2 AND $2 IS NOT NULL)
first_name, )
last_name, LIMIT 1;
nick_name,
email,
phone_number,
role,
age,
education_level,
country,
region,
email_verified,
phone_verified,
created_at,
updated_at,
suspended,
suspended_at,
organization_id
FROM users
WHERE phone_number = $1
AND organization_id = $2;
-- name: UpdatePassword :exec -- name: UpdatePassword :exec
UPDATE users UPDATE users
SET password = $1, SET password = $1,
@ -240,3 +221,9 @@ SELECT users.*
FROM organizations FROM organizations
JOIN users ON organizations.owner_id = users.id JOIN users ON organizations.owner_id = users.id
WHERE organizations.id = $1; WHERE organizations.id = $1;
-- name: SuspendUser :exec
UPDATE users
SET suspended = $1,
suspended_at = $2,
updated_at = CURRENT_TIMESTAMP
WHERE id = $3;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -75,52 +75,6 @@ func (q *Queries) GetRefreshTokenByUserID(ctx context.Context, userID int64) (Re
return i, err return i, err
} }
const GetUserByEmailPhone = `-- name: GetUserByEmailPhone :one
SELECT id, first_name, last_name, nick_name, email, phone_number, role, password, age, education_level, country, region, email_verified, phone_verified, suspended, suspended_at, organization_id, created_at, updated_at
FROM users
WHERE (
email = $1
OR phone_number = $2
)
AND (
organization_id = $3
OR $3 IS NULL
)
`
type GetUserByEmailPhoneParams struct {
Email pgtype.Text `json:"email"`
PhoneNumber pgtype.Text `json:"phone_number"`
OrganizationID pgtype.Int8 `json:"organization_id"`
}
func (q *Queries) GetUserByEmailPhone(ctx context.Context, arg GetUserByEmailPhoneParams) (User, error) {
row := q.db.QueryRow(ctx, GetUserByEmailPhone, arg.Email, arg.PhoneNumber, arg.OrganizationID)
var i User
err := row.Scan(
&i.ID,
&i.FirstName,
&i.LastName,
&i.NickName,
&i.Email,
&i.PhoneNumber,
&i.Role,
&i.Password,
&i.Age,
&i.EducationLevel,
&i.Country,
&i.Region,
&i.EmailVerified,
&i.PhoneVerified,
&i.Suspended,
&i.SuspendedAt,
&i.OrganizationID,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const RevokeRefreshToken = `-- name: RevokeRefreshToken :exec const RevokeRefreshToken = `-- name: RevokeRefreshToken :exec
UPDATE refresh_tokens UPDATE refresh_tokens
SET revoked = TRUE SET revoked = TRUE

View File

@ -0,0 +1,197 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// source: issue_reporting.sql
package dbgen
import (
"context"
)
const CountReportedIssues = `-- name: CountReportedIssues :one
SELECT COUNT(*)
FROM reported_issues
`
func (q *Queries) CountReportedIssues(ctx context.Context) (int64, error) {
row := q.db.QueryRow(ctx, CountReportedIssues)
var count int64
err := row.Scan(&count)
return count, err
}
const CountReportedIssuesByUser = `-- name: CountReportedIssuesByUser :one
SELECT COUNT(*)
FROM reported_issues
WHERE user_id = $1
`
func (q *Queries) CountReportedIssuesByUser(ctx context.Context, userID int64) (int64, error) {
row := q.db.QueryRow(ctx, CountReportedIssuesByUser, userID)
var count int64
err := row.Scan(&count)
return count, err
}
const CreateReportedIssue = `-- name: CreateReportedIssue :one
INSERT INTO reported_issues (
user_id,
user_role,
subject,
description,
issue_type,
metadata
)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id, user_id, user_role, subject, description, issue_type, status, metadata, created_at, updated_at
`
type CreateReportedIssueParams struct {
UserID int64 `json:"user_id"`
UserRole string `json:"user_role"`
Subject string `json:"subject"`
Description string `json:"description"`
IssueType string `json:"issue_type"`
Metadata []byte `json:"metadata"`
}
func (q *Queries) CreateReportedIssue(ctx context.Context, arg CreateReportedIssueParams) (ReportedIssue, error) {
row := q.db.QueryRow(ctx, CreateReportedIssue,
arg.UserID,
arg.UserRole,
arg.Subject,
arg.Description,
arg.IssueType,
arg.Metadata,
)
var i ReportedIssue
err := row.Scan(
&i.ID,
&i.UserID,
&i.UserRole,
&i.Subject,
&i.Description,
&i.IssueType,
&i.Status,
&i.Metadata,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const DeleteReportedIssue = `-- name: DeleteReportedIssue :exec
DELETE FROM reported_issues
WHERE id = $1
`
func (q *Queries) DeleteReportedIssue(ctx context.Context, id int64) error {
_, err := q.db.Exec(ctx, DeleteReportedIssue, id)
return err
}
const ListReportedIssues = `-- name: ListReportedIssues :many
SELECT id, user_id, user_role, subject, description, issue_type, status, metadata, created_at, updated_at
FROM reported_issues
ORDER BY created_at DESC
LIMIT $1 OFFSET $2
`
type ListReportedIssuesParams struct {
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}
func (q *Queries) ListReportedIssues(ctx context.Context, arg ListReportedIssuesParams) ([]ReportedIssue, error) {
rows, err := q.db.Query(ctx, ListReportedIssues, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ReportedIssue
for rows.Next() {
var i ReportedIssue
if err := rows.Scan(
&i.ID,
&i.UserID,
&i.UserRole,
&i.Subject,
&i.Description,
&i.IssueType,
&i.Status,
&i.Metadata,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const ListReportedIssuesByUser = `-- name: ListReportedIssuesByUser :many
SELECT id, user_id, user_role, subject, description, issue_type, status, metadata, created_at, updated_at
FROM reported_issues
WHERE user_id = $1
ORDER BY created_at DESC
LIMIT $2 OFFSET $3
`
type ListReportedIssuesByUserParams struct {
UserID int64 `json:"user_id"`
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}
func (q *Queries) ListReportedIssuesByUser(ctx context.Context, arg ListReportedIssuesByUserParams) ([]ReportedIssue, error) {
rows, err := q.db.Query(ctx, ListReportedIssuesByUser, arg.UserID, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ReportedIssue
for rows.Next() {
var i ReportedIssue
if err := rows.Scan(
&i.ID,
&i.UserID,
&i.UserRole,
&i.Subject,
&i.Description,
&i.IssueType,
&i.Status,
&i.Metadata,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const UpdateReportedIssueStatus = `-- name: UpdateReportedIssueStatus :exec
UPDATE reported_issues
SET status = $2,
updated_at = NOW()
WHERE id = $1
`
type UpdateReportedIssueStatusParams struct {
ID int64 `json:"id"`
Status string `json:"status"`
}
func (q *Queries) UpdateReportedIssueStatus(ctx context.Context, arg UpdateReportedIssueStatusParams) error {
_, err := q.db.Exec(ctx, UpdateReportedIssueStatus, arg.ID, arg.Status)
return err
}

276
gen/db/notification.sql.go Normal file
View File

@ -0,0 +1,276 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// source: notification.sql
package dbgen
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const CountUnreadNotifications = `-- name: CountUnreadNotifications :one
SELECT COUNT(*)
FROM notifications
WHERE user_id = $1
AND is_read = FALSE
`
func (q *Queries) CountUnreadNotifications(ctx context.Context, userID int64) (int64, error) {
row := q.db.QueryRow(ctx, CountUnreadNotifications, userID)
var count int64
err := row.Scan(&count)
return count, err
}
const CreateNotification = `-- name: CreateNotification :one
INSERT INTO notifications (
user_id,
type,
level,
channel,
title,
message,
payload
)
VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7
)
RETURNING id, user_id, type, level, channel, title, message, payload, is_read, created_at, read_at
`
type CreateNotificationParams struct {
UserID int64 `json:"user_id"`
Type string `json:"type"`
Level string `json:"level"`
Channel pgtype.Text `json:"channel"`
Title string `json:"title"`
Message string `json:"message"`
Payload []byte `json:"payload"`
}
func (q *Queries) CreateNotification(ctx context.Context, arg CreateNotificationParams) (Notification, error) {
row := q.db.QueryRow(ctx, CreateNotification,
arg.UserID,
arg.Type,
arg.Level,
arg.Channel,
arg.Title,
arg.Message,
arg.Payload,
)
var i Notification
err := row.Scan(
&i.ID,
&i.UserID,
&i.Type,
&i.Level,
&i.Channel,
&i.Title,
&i.Message,
&i.Payload,
&i.IsRead,
&i.CreatedAt,
&i.ReadAt,
)
return i, err
}
const DeleteUserNotifications = `-- name: DeleteUserNotifications :exec
DELETE FROM notifications
WHERE user_id = $1
`
func (q *Queries) DeleteUserNotifications(ctx context.Context, userID int64) error {
_, err := q.db.Exec(ctx, DeleteUserNotifications, userID)
return err
}
const GetAllNotifications = `-- name: GetAllNotifications :many
SELECT id, user_id, type, level, channel, title, message, payload, is_read, created_at, read_at
FROM notifications
ORDER BY created_at DESC
LIMIT $1 OFFSET $2
`
type GetAllNotificationsParams struct {
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}
func (q *Queries) GetAllNotifications(ctx context.Context, arg GetAllNotificationsParams) ([]Notification, error) {
rows, err := q.db.Query(ctx, GetAllNotifications, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Notification
for rows.Next() {
var i Notification
if err := rows.Scan(
&i.ID,
&i.UserID,
&i.Type,
&i.Level,
&i.Channel,
&i.Title,
&i.Message,
&i.Payload,
&i.IsRead,
&i.CreatedAt,
&i.ReadAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const GetNotification = `-- name: GetNotification :one
SELECT id, user_id, type, level, channel, title, message, payload, is_read, created_at, read_at
FROM notifications
WHERE id = $1
LIMIT 1
`
func (q *Queries) GetNotification(ctx context.Context, id int64) (Notification, error) {
row := q.db.QueryRow(ctx, GetNotification, id)
var i Notification
err := row.Scan(
&i.ID,
&i.UserID,
&i.Type,
&i.Level,
&i.Channel,
&i.Title,
&i.Message,
&i.Payload,
&i.IsRead,
&i.CreatedAt,
&i.ReadAt,
)
return i, err
}
const GetTotalNotificationCount = `-- name: GetTotalNotificationCount :one
SELECT COUNT(*)
FROM notifications
`
func (q *Queries) GetTotalNotificationCount(ctx context.Context) (int64, error) {
row := q.db.QueryRow(ctx, GetTotalNotificationCount)
var count int64
err := row.Scan(&count)
return count, err
}
const GetUserNotificationCount = `-- name: GetUserNotificationCount :one
SELECT COUNT(*)
FROM notifications
WHERE user_id = $1
`
func (q *Queries) GetUserNotificationCount(ctx context.Context, userID int64) (int64, error) {
row := q.db.QueryRow(ctx, GetUserNotificationCount, userID)
var count int64
err := row.Scan(&count)
return count, err
}
const GetUserNotifications = `-- name: GetUserNotifications :many
SELECT id, user_id, type, level, channel, title, message, payload, is_read, created_at, read_at
FROM notifications
WHERE user_id = $1
ORDER BY created_at DESC
LIMIT $2 OFFSET $3
`
type GetUserNotificationsParams struct {
UserID int64 `json:"user_id"`
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}
func (q *Queries) GetUserNotifications(ctx context.Context, arg GetUserNotificationsParams) ([]Notification, error) {
rows, err := q.db.Query(ctx, GetUserNotifications, arg.UserID, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Notification
for rows.Next() {
var i Notification
if err := rows.Scan(
&i.ID,
&i.UserID,
&i.Type,
&i.Level,
&i.Channel,
&i.Title,
&i.Message,
&i.Payload,
&i.IsRead,
&i.CreatedAt,
&i.ReadAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const MarkAllUserNotificationsAsRead = `-- name: MarkAllUserNotificationsAsRead :exec
UPDATE notifications
SET is_read = TRUE,
read_at = NOW()
WHERE user_id = $1
AND is_read = FALSE
`
func (q *Queries) MarkAllUserNotificationsAsRead(ctx context.Context, userID int64) error {
_, err := q.db.Exec(ctx, MarkAllUserNotificationsAsRead, userID)
return err
}
const MarkNotificationAsRead = `-- name: MarkNotificationAsRead :one
UPDATE notifications
SET is_read = TRUE,
read_at = NOW()
WHERE id = $1
RETURNING id, user_id, type, level, channel, title, message, payload, is_read, created_at, read_at
`
func (q *Queries) MarkNotificationAsRead(ctx context.Context, id int64) (Notification, error) {
row := q.db.QueryRow(ctx, MarkNotificationAsRead, id)
var i Notification
err := row.Scan(
&i.ID,
&i.UserID,
&i.Type,
&i.Level,
&i.Channel,
&i.Title,
&i.Message,
&i.Payload,
&i.IsRead,
&i.CreatedAt,
&i.ReadAt,
)
return i, err
}

92
gen/db/settings.sql.go Normal file
View File

@ -0,0 +1,92 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// source: settings.sql
package dbgen
import (
"context"
)
const GetGlobalSetting = `-- name: GetGlobalSetting :one
SELECT key, value, created_at, updated_at
FROM global_settings
WHERE key = $1
`
func (q *Queries) GetGlobalSetting(ctx context.Context, key string) (GlobalSetting, error) {
row := q.db.QueryRow(ctx, GetGlobalSetting, key)
var i GlobalSetting
err := row.Scan(
&i.Key,
&i.Value,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const GetGlobalSettings = `-- name: GetGlobalSettings :many
SELECT key, value, created_at, updated_at
FROM global_settings
`
func (q *Queries) GetGlobalSettings(ctx context.Context) ([]GlobalSetting, error) {
rows, err := q.db.Query(ctx, GetGlobalSettings)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GlobalSetting
for rows.Next() {
var i GlobalSetting
if err := rows.Scan(
&i.Key,
&i.Value,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const InsertGlobalSetting = `-- name: InsertGlobalSetting :exec
INSERT INTO global_settings (key, value)
VALUES ($1, $2) ON CONFLICT (key) DO
UPDATE
SET value = EXCLUDED.value
`
type InsertGlobalSettingParams struct {
Key string `json:"key"`
Value string `json:"value"`
}
func (q *Queries) InsertGlobalSetting(ctx context.Context, arg InsertGlobalSettingParams) error {
_, err := q.db.Exec(ctx, InsertGlobalSetting, arg.Key, arg.Value)
return err
}
const UpdateGlobalSetting = `-- name: UpdateGlobalSetting :exec
UPDATE global_settings
SET value = $2,
updated_at = CURRENT_TIMESTAMP
WHERE key = $1
`
type UpdateGlobalSettingParams struct {
Key string `json:"key"`
Value string `json:"value"`
}
func (q *Queries) UpdateGlobalSetting(ctx context.Context, arg UpdateGlobalSettingParams) error {
_, err := q.db.Exec(ctx, UpdateGlobalSetting, arg.Key, arg.Value)
return err
}

View File

@ -205,7 +205,9 @@ func (q *Queries) DeleteUser(ctx context.Context, id int64) error {
} }
const GetAllUsers = `-- name: GetAllUsers :many const GetAllUsers = `-- name: GetAllUsers :many
SELECT id, SELECT
COUNT(*) OVER () AS total_count,
id,
first_name, first_name,
last_name, last_name,
nick_name, nick_name,
@ -224,7 +226,7 @@ SELECT id,
suspended_at, suspended_at,
organization_id organization_id
FROM users FROM users
wHERE ( WHERE (
role = $1 role = $1
OR $1 IS NULL OR $1 IS NULL
) )
@ -246,7 +248,8 @@ wHERE (
created_at < $5 created_at < $5
OR $5 IS NULL OR $5 IS NULL
) )
LIMIT $7 OFFSET $6 LIMIT $7
OFFSET $6
` `
type GetAllUsersParams struct { type GetAllUsersParams struct {
@ -260,6 +263,7 @@ type GetAllUsersParams struct {
} }
type GetAllUsersRow struct { type GetAllUsersRow struct {
TotalCount int64 `json:"total_count"`
ID int64 `json:"id"` ID int64 `json:"id"`
FirstName string `json:"first_name"` FirstName string `json:"first_name"`
LastName string `json:"last_name"` LastName string `json:"last_name"`
@ -298,6 +302,7 @@ func (q *Queries) GetAllUsers(ctx context.Context, arg GetAllUsersParams) ([]Get
for rows.Next() { for rows.Next() {
var i GetAllUsersRow var i GetAllUsersRow
if err := rows.Scan( if err := rows.Scan(
&i.TotalCount,
&i.ID, &i.ID,
&i.FirstName, &i.FirstName,
&i.LastName, &i.LastName,
@ -386,14 +391,16 @@ func (q *Queries) GetTotalUsers(ctx context.Context, arg GetTotalUsersParams) (i
return count, err return count, err
} }
const GetUserByEmail = `-- name: GetUserByEmail :one const GetUserByEmailPhone = `-- name: GetUserByEmailPhone :one
SELECT id, SELECT
id,
first_name, first_name,
last_name, last_name,
nick_name, nick_name,
email, email,
phone_number, phone_number,
role, role,
password, -- added this line
age, age,
education_level, education_level,
country, country,
@ -406,16 +413,21 @@ SELECT id,
suspended_at, suspended_at,
organization_id organization_id
FROM users FROM users
WHERE email = $1 WHERE organization_id = $3
AND organization_id = $2 AND (
(email = $1 AND $1 IS NOT NULL)
OR (phone_number = $2 AND $2 IS NOT NULL)
)
LIMIT 1
` `
type GetUserByEmailParams struct { type GetUserByEmailPhoneParams struct {
Email pgtype.Text `json:"email"` Email pgtype.Text `json:"email"`
PhoneNumber pgtype.Text `json:"phone_number"`
OrganizationID pgtype.Int8 `json:"organization_id"` OrganizationID pgtype.Int8 `json:"organization_id"`
} }
type GetUserByEmailRow struct { type GetUserByEmailPhoneRow struct {
ID int64 `json:"id"` ID int64 `json:"id"`
FirstName string `json:"first_name"` FirstName string `json:"first_name"`
LastName string `json:"last_name"` LastName string `json:"last_name"`
@ -423,6 +435,7 @@ type GetUserByEmailRow struct {
Email pgtype.Text `json:"email"` Email pgtype.Text `json:"email"`
PhoneNumber pgtype.Text `json:"phone_number"` PhoneNumber pgtype.Text `json:"phone_number"`
Role string `json:"role"` Role string `json:"role"`
Password []byte `json:"password"`
Age pgtype.Int4 `json:"age"` Age pgtype.Int4 `json:"age"`
EducationLevel pgtype.Text `json:"education_level"` EducationLevel pgtype.Text `json:"education_level"`
Country pgtype.Text `json:"country"` Country pgtype.Text `json:"country"`
@ -436,9 +449,9 @@ type GetUserByEmailRow struct {
OrganizationID pgtype.Int8 `json:"organization_id"` OrganizationID pgtype.Int8 `json:"organization_id"`
} }
func (q *Queries) GetUserByEmail(ctx context.Context, arg GetUserByEmailParams) (GetUserByEmailRow, error) { func (q *Queries) GetUserByEmailPhone(ctx context.Context, arg GetUserByEmailPhoneParams) (GetUserByEmailPhoneRow, error) {
row := q.db.QueryRow(ctx, GetUserByEmail, arg.Email, arg.OrganizationID) row := q.db.QueryRow(ctx, GetUserByEmailPhone, arg.Email, arg.PhoneNumber, arg.OrganizationID)
var i GetUserByEmailRow var i GetUserByEmailPhoneRow
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
&i.FirstName, &i.FirstName,
@ -447,6 +460,7 @@ func (q *Queries) GetUserByEmail(ctx context.Context, arg GetUserByEmailParams)
&i.Email, &i.Email,
&i.PhoneNumber, &i.PhoneNumber,
&i.Role, &i.Role,
&i.Password,
&i.Age, &i.Age,
&i.EducationLevel, &i.EducationLevel,
&i.Country, &i.Country,
@ -495,82 +509,6 @@ func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) {
return i, err return i, err
} }
const GetUserByPhone = `-- name: GetUserByPhone :one
SELECT id,
first_name,
last_name,
nick_name,
email,
phone_number,
role,
age,
education_level,
country,
region,
email_verified,
phone_verified,
created_at,
updated_at,
suspended,
suspended_at,
organization_id
FROM users
WHERE phone_number = $1
AND organization_id = $2
`
type GetUserByPhoneParams struct {
PhoneNumber pgtype.Text `json:"phone_number"`
OrganizationID pgtype.Int8 `json:"organization_id"`
}
type GetUserByPhoneRow struct {
ID int64 `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
NickName pgtype.Text `json:"nick_name"`
Email pgtype.Text `json:"email"`
PhoneNumber pgtype.Text `json:"phone_number"`
Role string `json:"role"`
Age pgtype.Int4 `json:"age"`
EducationLevel pgtype.Text `json:"education_level"`
Country pgtype.Text `json:"country"`
Region pgtype.Text `json:"region"`
EmailVerified bool `json:"email_verified"`
PhoneVerified bool `json:"phone_verified"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
Suspended bool `json:"suspended"`
SuspendedAt pgtype.Timestamptz `json:"suspended_at"`
OrganizationID pgtype.Int8 `json:"organization_id"`
}
func (q *Queries) GetUserByPhone(ctx context.Context, arg GetUserByPhoneParams) (GetUserByPhoneRow, error) {
row := q.db.QueryRow(ctx, GetUserByPhone, arg.PhoneNumber, arg.OrganizationID)
var i GetUserByPhoneRow
err := row.Scan(
&i.ID,
&i.FirstName,
&i.LastName,
&i.NickName,
&i.Email,
&i.PhoneNumber,
&i.Role,
&i.Age,
&i.EducationLevel,
&i.Country,
&i.Region,
&i.EmailVerified,
&i.PhoneVerified,
&i.CreatedAt,
&i.UpdatedAt,
&i.Suspended,
&i.SuspendedAt,
&i.OrganizationID,
)
return i, err
}
const SearchUserByNameOrPhone = `-- name: SearchUserByNameOrPhone :many const SearchUserByNameOrPhone = `-- name: SearchUserByNameOrPhone :many
SELECT id, SELECT id,
first_name, first_name,

9
go.mod
View File

@ -6,7 +6,6 @@ toolchain go1.24.11
require ( require (
github.com/amanuelabay/afrosms-go v1.0.6 github.com/amanuelabay/afrosms-go v1.0.6
github.com/go-co-op/gocron v1.37.0
github.com/go-playground/validator/v10 v10.29.0 github.com/go-playground/validator/v10 v10.29.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/resend/resend-go/v2 v2.28.0 github.com/resend/resend-go/v2 v2.28.0
@ -16,6 +15,8 @@ require (
golang.org/x/crypto v0.45.0 golang.org/x/crypto v0.45.0
) )
require github.com/rogpeppe/go-internal v1.8.1 // indirect
require ( require (
github.com/KyleBanks/depth v1.2.1 // indirect github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect
@ -31,7 +32,7 @@ require (
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/jackc/pgio v1.0.0 // indirect // github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect
@ -40,14 +41,12 @@ require (
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/mailru/easyjson v0.7.6 // indirect github.com/mailru/easyjson v0.7.6 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect github.com/montanaflynn/stats v0.7.1 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.10.0 // indirect go.uber.org/multierr v1.10.0 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/mod v0.29.0 // indirect golang.org/x/mod v0.29.0 // indirect
@ -66,7 +65,7 @@ require (
github.com/golang/mock v1.6.0 // indirect github.com/golang/mock v1.6.0 // indirect
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3
github.com/jackc/pgtype v1.14.4 // github.com/jackc/pgtype v1.14.4
github.com/jackc/pgx/v5 v5.7.6 github.com/jackc/pgx/v5 v5.7.6
github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/compress v1.17.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect

152
go.sum
View File

@ -1,7 +1,6 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
@ -21,11 +20,7 @@ github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2N
github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -33,10 +28,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik= github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik=
github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
@ -55,11 +46,9 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.29.0 h1:lQlF5VNJWNlRbRZNeOIkWElR+1LL/OuHcc0Kp14w1xk= github.com/go-playground/validator/v10 v10.29.0 h1:lQlF5VNJWNlRbRZNeOIkWElR+1LL/OuHcc0Kp14w1xk=
github.com/go-playground/validator/v10 v10.29.0/go.mod h1:D6QxqeMlgIPuT02L66f2ccrZ7AGgHkzKmmTMZhk/Kc4= github.com/go-playground/validator/v10 v10.29.0/go.mod h1:D6QxqeMlgIPuT02L66f2ccrZ7AGgHkzKmmTMZhk/Kc4=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofiber/fiber/v2 v2.32.0/go.mod h1:CMy5ZLiXkn6qwthrl03YMyW1NLfj0rhxz2LKl4t7ZTY= github.com/gofiber/fiber/v2 v2.32.0/go.mod h1:CMy5ZLiXkn6qwthrl03YMyW1NLfj0rhxz2LKl4t7ZTY=
github.com/gofiber/fiber/v2 v2.52.10 h1:jRHROi2BuNti6NYXmZ6gbNSfT3zj/8c0xy94GOU5elY= github.com/gofiber/fiber/v2 v2.52.10 h1:jRHROi2BuNti6NYXmZ6gbNSfT3zj/8c0xy94GOU5elY=
github.com/gofiber/fiber/v2 v2.52.10/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= github.com/gofiber/fiber/v2 v2.52.10/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
@ -69,60 +58,17 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgtype v1.14.4 h1:fKuNiCumbKTAIxQwXfB/nsrnkEI6bPJrrSiMKgbJ2j8=
github.com/jackc/pgtype v1.14.4/go.mod h1:aKeozOde08iifGosdJpz9MBZonJOUJxqNpPBcMJTlVA=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk= github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
@ -130,42 +76,28 @@ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275 h1:IZycmTpoUtQK3PD60UYBwjaCUHUP7cML494ao9/O8+Q= github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275 h1:IZycmTpoUtQK3PD60UYBwjaCUHUP7cML494ao9/O8+Q=
github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275/go.mod h1:zt6UU74K6Z6oMOYJbJzYpYucqdcQwSMPBEdSvGiaUMw= github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275/go.mod h1:zt6UU74K6Z6oMOYJbJzYpYucqdcQwSMPBEdSvGiaUMw=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
@ -180,7 +112,6 @@ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -189,40 +120,21 @@ github.com/resend/resend-go/v2 v2.28.0 h1:ttM1/VZR4fApBv3xI1TneSKi1pbfFsVrq7fXFl
github.com/resend/resend-go/v2 v2.28.0/go.mod h1:3YCb8c8+pLiqhtRFXTyFwlLvfjQtluxOr9HEh2BwCkQ= github.com/resend/resend-go/v2 v2.28.0/go.mod h1:3YCb8c8+pLiqhtRFXTyFwlLvfjQtluxOr9HEh2BwCkQ=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
@ -257,57 +169,29 @@ github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfS
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss= go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss=
go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
@ -315,27 +199,15 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -348,48 +220,26 @@ golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -399,7 +249,6 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
@ -408,4 +257,3 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=

View File

@ -381,7 +381,7 @@ func (c *Config) loadEnv() error {
c.AFRO_SMS_SENDER_NAME = os.Getenv("AFRO_SMS_SENDER_NAME") c.AFRO_SMS_SENDER_NAME = os.Getenv("AFRO_SMS_SENDER_NAME")
if c.AFRO_SMS_SENDER_NAME == "" { if c.AFRO_SMS_SENDER_NAME == "" {
c.AFRO_SMS_SENDER_NAME = "FortuneBet" c.AFRO_SMS_SENDER_NAME = "Yimaru"
} }
c.AFRO_SMS_RECEIVER_PHONE_NUMBER = os.Getenv("AFRO_SMS_RECEIVER_PHONE_NUMBER") c.AFRO_SMS_RECEIVER_PHONE_NUMBER = os.Getenv("AFRO_SMS_RECEIVER_PHONE_NUMBER")

View File

@ -1,390 +0,0 @@
package domain
import (
"errors"
"time"
)
var (
ErrInsufficientBalance = errors.New("insufficient balance")
ErrInvalidWithdrawalAmount = errors.New("invalid withdrawal amount")
ErrWithdrawalNotFound = errors.New("withdrawal not found")
ErrPaymentNotFound = errors.New("payment not found")
ErrPaymentAlreadyExists = errors.New("payment with this reference already exists")
ErrInvalidPaymentAmount = errors.New("invalid payment amount")
)
type PaymentStatus string
type WithdrawalStatus string
const (
WithdrawalStatusSuccessful WithdrawalStatus = "success"
WithdrawalStatusPending WithdrawalStatus = "pending"
WithdrawalStatusProcessing WithdrawalStatus = "processing"
WithdrawalStatusCompleted WithdrawalStatus = "completed"
WithdrawalStatusFailed WithdrawalStatus = "failed"
)
const (
PaymentStatusSuccessful PaymentStatus = "success"
PaymentStatusPending PaymentStatus = "pending"
PaymentStatusCompleted PaymentStatus = "completed"
PaymentStatusFailed PaymentStatus = "failed"
)
type ChapaInitDepositRequest struct {
Amount Currency `json:"amount"`
Currency string `json:"currency"`
Email string `json:"email"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
TxRef string `json:"tx_ref"`
CallbackURL string `json:"callback_url"`
ReturnURL string `json:"return_url"`
PhoneNumber string `json:"phone_number"`
// PhoneNumber string `json:"phone_number"`
}
type ChapaDepositRequestPayload struct {
Amount float64 `json:"amount" validate:"required,gt=0"`
}
type ChapaWebhookPayload struct {
TxRef string `json:"trx_ref"`
Amount Currency `json:"amount"`
// Currency string `json:"currency"`
Status PaymentStatus `json:"status"`
}
type ChapaPaymentWebhookRequest struct {
TxRef string `json:"trx_ref"`
RefId string `json:"ref_id"`
Status PaymentStatus `json:"status"`
}
// PaymentResponse contains the response from payment initialization
type ChapaDepositResponse struct {
CheckoutURL string
Reference string
}
// PaymentVerification contains payment verification details
type ChapaDepositVerification struct {
Status PaymentStatus
Amount Currency
Currency string
}
type ChapaPaymentVerificationResponse struct {
Message string `json:"message"`
Status string `json:"status"`
Data struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Email string `json:"email"`
Currency string `json:"currency"`
Amount float64 `json:"amount"`
Charge float64 `json:"charge"`
Mode string `json:"mode"`
Method string `json:"method"`
Type string `json:"type"`
Status string `json:"status"`
Reference string `json:"reference"`
TxRef string `json:"tx_ref"`
Customization struct {
Title string `json:"title"`
Description string `json:"description"`
Logo interface{} `json:"logo"`
} `json:"customization"`
Meta interface{} `json:"meta"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
} `json:"data"`
}
type ChapaTransferVerificationResponse struct {
Message string `json:"message"`
Status string `json:"status"`
Data struct {
AccountName string `json:"account_name"`
AccountNumber string `json:"account_number"`
Mobile interface{} `json:"mobile"`
Currency string `json:"currency"`
Amount float64 `json:"amount"`
Charge float64 `json:"charge"`
Mode string `json:"mode"`
TransferMethod string `json:"transfer_method"`
Narration interface{} `json:"narration"`
ChapaTransferID string `json:"chapa_transfer_id"`
BankCode int `json:"bank_code"`
BankName string `json:"bank_name"`
CrossPartyReference interface{} `json:"cross_party_reference"`
IPAddress string `json:"ip_address"`
Status string `json:"status"`
TxRef string `json:"tx_ref"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
} `json:"data"`
}
type ChapaAllTransactionsResponse struct {
Message string `json:"message"`
Status string `json:"status"`
Data struct {
Transactions []struct {
Status string `json:"status"`
RefID string `json:"ref_id"`
Type string `json:"type"`
CreatedAt string `json:"created_at"`
Currency string `json:"currency"`
Amount string `json:"amount"`
Charge string `json:"charge"`
TransID *string `json:"trans_id"`
PaymentMethod string `json:"payment_method"`
Customer struct {
ID int64 `json:"id"`
Email *string `json:"email"`
FirstName *string `json:"first_name"`
LastName *string `json:"last_name"`
Mobile *string `json:"mobile"`
} `json:"customer"`
} `json:"transactions"`
Pagination struct {
PerPage int `json:"per_page"`
CurrentPage int `json:"current_page"`
FirstPageURL string `json:"first_page_url"`
NextPageURL *string `json:"next_page_url"`
PrevPageURL *string `json:"prev_page_url"`
} `json:"pagination"`
} `json:"data"`
}
type ChapaTransactionEvent struct {
Item int64 `json:"item"`
Message string `json:"message"`
Type string `json:"type"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ChapaTransaction struct {
Status string `json:"status"`
RefID string `json:"ref_id"`
Type string `json:"type"`
CreatedAt string `json:"created_at"`
Currency string `json:"currency"`
Amount string `json:"amount"`
Charge string `json:"charge"`
TransID *string `json:"trans_id"`
PaymentMethod string `json:"payment_method"`
Customer ChapaCustomer `json:"customer"`
}
type ChapaCustomer struct {
ID int64 `json:"id"`
Email *string `json:"email"`
FirstName *string `json:"first_name"`
LastName *string `json:"last_name"`
Mobile *string `json:"mobile"`
}
// type Bank struct {
// ID int `json:"id"`
// Slug string `json:"slug"`
// Swift string `json:"swift"`
// Name string `json:"name"`
// AcctLength int `json:"acct_length"`
// CountryID int `json:"country_id"`
// IsMobileMoney int `json:"is_mobilemoney"` // nullable
// IsActive int `json:"is_active"`
// IsRTGS int `json:"is_rtgs"`
// Active int `json:"active"`
// Is24Hrs int `json:"is_24hrs"` // nullable
// CreatedAt time.Time `json:"created_at"`
// UpdatedAt time.Time `json:"updated_at"`
// Currency string `json:"currency"`
// BankLogo string `json:"bank_logo"` // URL or base64
// }
type SwapResponse struct {
Message string `json:"message"`
Status string `json:"status"`
Data struct {
Status string `json:"status"`
RefID string `json:"ref_id"`
FromCurrency string `json:"from_currency"`
ToCurrency string `json:"to_currency"`
Amount float64 `json:"amount"`
ExchangedAmount float64 `json:"exchanged_amount"`
Charge float64 `json:"charge"`
Rate float64 `json:"rate"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
} `json:"data"`
}
type ChapaTransfersListResponse struct {
Message string `json:"message"`
Status string `json:"status"`
Meta struct {
CurrentPage int `json:"current_page"`
FirstPageURL string `json:"first_page_url"`
LastPage int `json:"last_page"`
LastPageURL string `json:"last_page_url"`
NextPageURL string `json:"next_page_url"`
Path string `json:"path"`
PerPage int `json:"per_page"`
PrevPageURL interface{} `json:"prev_page_url"`
To int `json:"to"`
Total int `json:"total"`
Error []interface{} `json:"error"`
} `json:"meta"`
Data []struct {
AccountName string `json:"account_name"`
AccountNumber string `json:"account_number"`
Currency string `json:"currency"`
Amount float64 `json:"amount"`
Charge float64 `json:"charge"`
TransferType string `json:"transfer_type"`
ChapaReference string `json:"chapa_reference"`
BankCode int `json:"bank_code"`
BankName string `json:"bank_name"`
BankReference interface{} `json:"bank_reference"`
Status string `json:"status"`
Reference interface{} `json:"reference"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
} `json:"data"`
}
type BankResponse struct {
Message string `json:"message"`
Status string `json:"status"`
Data []BankData `json:"data"`
}
type BankData struct {
ID int `json:"id"`
Slug string `json:"slug"`
Swift string `json:"swift"`
Name string `json:"name"`
AcctLength int `json:"acct_length"`
CountryID int `json:"country_id"`
IsMobileMoney int `json:"is_mobilemoney"` // nullable
IsActive int `json:"is_active"`
IsRTGS int `json:"is_rtgs"`
Active int `json:"active"`
Is24Hrs int `json:"is_24hrs"` // nullable
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Currency string `json:"currency"`
}
type ChapaWithdrawal struct {
ID string
UserID int64
Amount Currency
AccountNumber string
BankCode string
Status WithdrawalStatus
Reference string
CreatedAt time.Time
UpdatedAt time.Time
}
type ChapaWithdrawalRequest struct {
AccountName string `json:"account_name"`
AccountNumber string `json:"account_number"`
Amount string `json:"amount"` // string because Chapa API uses string for monetary values
Currency string `json:"currency"`
Reference string `json:"reference"`
BankCode int `json:"bank_code"`
}
// type ChapaWithdrawalRequest struct {
// AccountName string `json:"account_name"`
// AccountNumber string `json:"account_number"`
// Amount Currency `json:"amount"`
// Currency string `json:"currency"`
// BeneficiaryName string `json:"beneficiary_name"`
// BankCode string `json:"bank_code"`
// PhoneNumber string `json:"phone_number"`
// }
type ChapaWithdrawalResponse struct {
Message string `json:"message"`
Status string `json:"status"`
Data string `json:"data"` // Accepts string instead of struct
}
type ChapaTransactionType struct {
Type string `json:"type"`
}
type ChapaWebhookTransfer struct {
Event string `json:"event"`
Type string `json:"type"`
AccountName string `json:"account_name"`
AccountNumber string `json:"account_number"`
BankID int `json:"bank_id"`
BankName string `json:"bank_name"`
Amount string `json:"amount"`
Charge string `json:"charge"`
Currency string `json:"currency"`
Status string `json:"status"`
Reference string `json:"reference"`
ChapaReference string `json:"chapa_reference"`
BankReference string `json:"bank_reference"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
type ChapaWebhookPayment struct {
Event string `json:"event"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Email *string `json:"email,omitempty"`
Mobile string `json:"mobile"`
Currency string `json:"currency"`
Amount string `json:"amount"`
Charge string `json:"charge"`
Status string `json:"status"`
Mode string `json:"mode"`
Reference string `json:"reference"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
Type string `json:"type"`
TxRef string `json:"tx_ref"`
PaymentMethod string `json:"payment_method"`
Customization ChapaWebhookCustomization `json:"customization"`
Meta interface{} `json:"meta"` // may vary in structure, so kept flexible
}
type ChapaWebhookCustomization struct {
Title *string `json:"title,omitempty"`
Description *string `json:"description,omitempty"`
Logo *string `json:"logo,omitempty"`
}
type Balance struct {
Currency string `json:"currency"`
AvailableBalance float64 `json:"available_balance"`
LedgerBalance float64 `json:"ledger_balance"`
}
type SwapRequest struct {
From string `json:"from"`
To string `json:"to"`
Amount float64 `json:"amount"`
}
type ChapaCancelResponse struct {
Message string `json:"message"`
Status string `json:"status"`
TxRef string `json:"tx_ref"`
Amount float64 `json:"amount"`
Currency string `json:"currency"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}

View File

@ -1,39 +0,0 @@
package domain
import "time"
type DirectDepositStatus string
const (
DepositStatusPending DirectDepositStatus = "PENDING"
DepositStatusCompleted DirectDepositStatus = "COMPLETED"
DepositStatusRejected DirectDepositStatus = "REJECTED"
)
type DirectDeposit struct {
ID int
CustomerID int
WalletID int
BankName string
AccountNumber string
AccountHolder string
Amount float64
ReferenceNumber string
TransferScreenshot string
Status string
CreatedAt time.Time
ApprovedBy *int
ApprovedAt *time.Time
RejectionReason *string
}
type CreateDirectDeposit struct {
CustomerID int
WalletID int
BankName string
AccountNumber string
AccountHolder string
Amount float64
ReferenceNumber string
TransferScreenshot string
}

View File

@ -1,28 +0,0 @@
package domain
import "time"
type Bank struct {
ID int `json:"id"`
Slug string `json:"slug"`
Swift string `json:"swift"`
Name string `json:"name"`
AcctLength int `json:"acct_length"`
CountryID int `json:"country_id"`
IsMobileMoney int `json:"is_mobilemoney"` // nullable
IsActive int `json:"is_active"`
IsRTGS int `json:"is_rtgs"`
Active int `json:"active"`
Is24Hrs int `json:"is_24hrs"` // nullable
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Currency string `json:"currency"`
BankLogo string `json:"bank_logo"` // URL or base64
}
type InstResponse struct {
Message string `json:"message"`
Status string `json:"status"`
Data interface{} `json:"data"` // Changed to interface{} for flexibility
Pagination *Pagination `json:"pagination,omitempty"` // Made pointer and optional
}

View File

@ -123,11 +123,11 @@ func ReceiverFromRole(role Role) NotificationRecieverSide {
switch role { switch role {
case RoleAdmin: case RoleAdmin:
return NotificationRecieverSideAdmin return NotificationRecieverSideAdmin
case RoleCashier: case RoleSuperAdmin:
return NotificationRecieverSideCashier return NotificationRecieverSideAdmin
case RoleBranchManager: case RoleStudent:
return NotificationRecieverSideBranchManager return NotificationRecieverSideCustomer
case RoleCustomer: case RoleInstructor:
return NotificationRecieverSideCustomer return NotificationRecieverSideCustomer
default: default:
return "" return ""

View File

@ -1,186 +0,0 @@
package domain
// import (
// "time"
// dbgen "Yimaru-Backend/gen/db"
// )
// type ReferralCode struct {
// ID int64
// ReferrerID int64
// ReferralCode string
// CompanyID int64
// NumberOfReferrals int64
// RewardAmount Currency
// CreatedAt time.Time
// UpdatedAt time.Time
// }
// type ReferralCodeRes struct {
// ID int64 `json:"id"`
// ReferrerID int64 `json:"referrer_id"`
// ReferralCode string `json:"referral_code"`
// CompanyID int64 `json:"company_id"`
// NumberOfReferrals int64 `json:"number_of_referrals"`
// RewardAmount float32 `json:"reward_amount"`
// CreatedAt time.Time `json:"created_at"`
// UpdatedAt time.Time `json:"updated_at"`
// }
// type CreateReferralCode struct {
// ReferrerID int64
// ReferralCode string
// CompanyID int64
// NumberOfReferrals int64
// RewardAmount Currency
// }
// type UserReferral struct {
// ReferredID int64
// ReferralCodeID int64
// }
// type CreateUserReferrals struct {
// ReferredID int64
// ReferralCodeID int64
// }
// type UpdateReferralCode struct {
// ID int64
// IsActive bool
// ReferralCode string
// RewardAmount Currency
// NumberOfReferrals int64
// }
// type ReferralStats struct {
// TotalReferrals int64
// TotalRewardEarned Currency
// }
// type ReferralStatsRes struct {
// TotalReferrals int64 `json:"total_referrals"`
// TotalRewardEarned float32 `json:"total_reward_earned"`
// }
// // type ReferralSettings struct {
// // ID int64
// // ReferralRewardAmount float64
// // CashbackPercentage float64
// // BetReferralBonusPercentage float64
// // MaxReferrals int32
// // ExpiresAfterDays int32
// // UpdatedBy string
// // CreatedAt time.Time
// // UpdatedAt time.Time
// // Version int32
// // }
// // type ReferralSettingsReq struct {
// // ReferralRewardAmount float64 `json:"referral_reward_amount" validate:"required"`
// // CashbackPercentage float64 `json:"cashback_percentage" validate:"required"`
// // MaxReferrals int32 `json:"max_referrals" validate:"required"`
// // UpdatedBy string `json:"updated_by" validate:"required"`
// // }
// func ConvertCreateReferralCode(code CreateReferralCode) dbgen.CreateReferralCodeParams {
// return dbgen.CreateReferralCodeParams{
// ReferralCode: code.ReferralCode,
// ReferrerID: code.ReferrerID,
// CompanyID: code.CompanyID,
// NumberOfReferrals: code.NumberOfReferrals,
// RewardAmount: int64(code.RewardAmount),
// }
// }
// func ConvertDBReferralCode(code dbgen.ReferralCode) ReferralCode {
// return ReferralCode{
// ID: code.ID,
// ReferrerID: code.ReferrerID,
// ReferralCode: code.ReferralCode,
// NumberOfReferrals: code.NumberOfReferrals,
// RewardAmount: Currency(code.RewardAmount),
// CompanyID: code.CompanyID,
// CreatedAt: code.CreatedAt.Time,
// UpdatedAt: code.UpdatedAt.Time,
// }
// }
// func ConvertDBReferralCodes(codes []dbgen.ReferralCode) []ReferralCode {
// result := make([]ReferralCode, len(codes))
// for i, code := range codes {
// result[i] = ConvertDBReferralCode(code)
// }
// return result
// }
// func ConvertCreateUserReferral(referral CreateUserReferrals) dbgen.CreateUserReferralParams {
// return dbgen.CreateUserReferralParams{
// ReferredID: referral.ReferredID,
// ReferralCodeID: referral.ReferralCodeID,
// }
// }
// func ConvertDBUserReferral(referral dbgen.UserReferral) UserReferral {
// return UserReferral{
// ReferredID: referral.ReferredID,
// ReferralCodeID: referral.ReferralCodeID,
// }
// }
// func ConvertDBUserReferrals(referrals []dbgen.UserReferral) []UserReferral {
// result := make([]UserReferral, len(referrals))
// for i, referral := range referrals {
// result[i] = ConvertDBUserReferral(referral)
// }
// return result
// }
// func ConvertUpdateReferralCode(referralCode UpdateReferralCode) dbgen.UpdateReferralCodeParams {
// return dbgen.UpdateReferralCodeParams{
// ID: referralCode.ID,
// IsActive: referralCode.IsActive,
// ReferralCode: referralCode.ReferralCode,
// NumberOfReferrals: referralCode.NumberOfReferrals,
// RewardAmount: int64(referralCode.RewardAmount),
// }
// }
// func ConvertDBReferralStats(stats dbgen.GetReferralStatsRow) ReferralStats {
// return ReferralStats{
// TotalReferrals: stats.TotalReferrals,
// TotalRewardEarned: Currency(stats.TotalRewardEarned),
// }
// }
// func ConvertReferralCodeRes(referral ReferralCode) ReferralCodeRes {
// return ReferralCodeRes{
// ID: referral.ID,
// ReferrerID: referral.ReferrerID,
// ReferralCode: referral.ReferralCode,
// CompanyID: referral.CompanyID,
// NumberOfReferrals: referral.NumberOfReferrals,
// RewardAmount: referral.RewardAmount.Float32(),
// CreatedAt: referral.CreatedAt,
// UpdatedAt: referral.UpdatedAt,
// }
// }
// func ConvertReferralCodeResList(referrals []ReferralCode) []ReferralCodeRes {
// result := make([]ReferralCodeRes, len(referrals))
// for i, referral := range referrals {
// result[i] = ConvertReferralCodeRes(referral)
// }
// return result
// }
// func ConvertReferralStatsRes(stats ReferralStats) ReferralStatsRes {
// return ReferralStatsRes{
// TotalReferrals: stats.TotalReferrals,
// TotalRewardEarned: stats.TotalRewardEarned.Float32(),
// }
// }

View File

@ -5,17 +5,20 @@ type Role string
const ( const (
RoleSuperAdmin Role = "super_admin" RoleSuperAdmin Role = "super_admin"
RoleAdmin Role = "admin" RoleAdmin Role = "admin"
RoleBranchManager Role = "branch_manager" RoleStudent Role = "student"
RoleCustomer Role = "customer" RoleInstructor Role = "instructor"
RoleCashier Role = "cashier" RoleSupport Role = "support"
RoleTransactionApprover Role = "transaction_approver"
) )
func (r Role) IsValid() bool { func (r Role) IsValid() bool {
switch r { switch r {
case RoleSuperAdmin, RoleAdmin, RoleBranchManager, RoleCustomer, RoleCashier, RoleTransactionApprover: case RoleSuperAdmin, RoleAdmin, RoleStudent, RoleInstructor, RoleSupport:
return true return true
default: default:
return false return false
} }
} }
func (r Role) Value() string {
return string(r)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,46 +1,44 @@
package domain package domain
// import ( import (
// "time" "time"
)
// dbgen "Yimaru-Backend/gen/db" type Setting struct {
// ) Key string
Value string
UpdatedAt time.Time
}
// type Setting struct { type CreateSetting struct {
// Key string Key string
// Value string Value string
// UpdatedAt time.Time }
// }
// type CreateSetting struct { type SettingRes struct {
// Key string Key string `json:"key"`
// Value string Value string `json:"value"`
// } UpdatedAt time.Time `json:"updated_at"`
}
type CompanySetting struct {
Key string
Value string
CompanyID int64
UpdatedAt time.Time
CreatedAt time.Time
}
// type SettingRes struct { type CompanySettingRes struct {
// Key string `json:"key"` Key string `json:"key"`
// Value string `json:"value"` Value string `json:"value"`
// UpdatedAt time.Time `json:"updated_at"` CompanyID int64 `json:"company_id"`
// } UpdatedAt time.Time `json:"updated_at"`
// type CompanySetting struct { CreatedAt time.Time `json:"created_at"`
// Key string }
// Value string
// CompanyID int64
// UpdatedAt time.Time
// CreatedAt time.Time
// }
// type CompanySettingRes struct { func ConvertSetting(setting Setting) SettingRes {
// Key string `json:"key"` return SettingRes(setting)
// Value string `json:"value"` }
// CompanyID int64 `json:"company_id"`
// UpdatedAt time.Time `json:"updated_at"`
// CreatedAt time.Time `json:"created_at"`
// }
// func ConvertSetting(setting Setting) SettingRes {
// return SettingRes(setting)
// }
// func ConvertCompanySetting(companySetting dbgen.CompanySetting) CompanySetting { // func ConvertCompanySetting(companySetting dbgen.CompanySetting) CompanySetting {
// return CompanySetting{ // return CompanySetting{

View File

@ -8,13 +8,13 @@ import (
type NotificationStore interface { type NotificationStore interface {
GetUserNotifications(ctx context.Context, recipientID int64, limit, offset int) ([]domain.Notification, int64, error) GetUserNotifications(ctx context.Context, recipientID int64, limit, offset int) ([]domain.Notification, int64, error)
ListRecipientIDs(ctx context.Context, receiver domain.NotificationRecieverSide) ([]int64, error) // New method // ListRecipientIDs(ctx context.Context, receiver domain.NotificationRecieverSide) ([]int64, error) // New method
CountUnreadNotifications(ctx context.Context, recipient_id int64) (int64, error) CountUnreadNotifications(ctx context.Context, recipient_id int64) (int64, error)
GetAllNotifications(ctx context.Context, limit, offset int) ([]domain.Notification, error) GetAllNotifications(ctx context.Context, limit, offset int) ([]domain.Notification, error)
GetNotificationCounts(ctx context.Context, filter domain.ReportFilter) (total, read, unread int64, err error) // GetNotificationCounts(ctx context.Context, filter domain.ReportFilter) (total, read, unread int64, err error)
CreateNotification(ctx context.Context, notification *domain.Notification) (*domain.Notification, error) CreateNotification(ctx context.Context, notification *domain.Notification) (*domain.Notification, error)
UpdateNotificationStatus(ctx context.Context, id, status string, isRead bool, metadata []byte) (*domain.Notification, error) // UpdateNotificationStatus(ctx context.Context, id, status string, isRead bool, metadata []byte) (*domain.Notification, error)
ListFailedNotifications(ctx context.Context, limit int) ([]domain.Notification, error) // ListFailedNotifications(ctx context.Context, limit int) ([]domain.Notification, error)
DeleteOldNotifications(ctx context.Context) error // DeleteOldNotifications(ctx context.Context) error
} }

View File

@ -1,15 +1,16 @@
package ports package ports
import ( import (
"Yimaru-Backend/internal/domain"
"context" "context"
) )
type SettingStore interface { type SettingStore interface {
// GetGlobalSettingList(ctx context.Context) (domain.SettingList, error) GetGlobalSettingList(ctx context.Context) (domain.SettingList, error)
// GetGlobalSettings(ctx context.Context) ([]domain.Setting, error) GetGlobalSettings(ctx context.Context) ([]domain.Setting, error)
// GetGlobalSetting(ctx context.Context, key string) (domain.Setting, error) GetGlobalSetting(ctx context.Context, key string) (domain.Setting, error)
// UpdateGlobalSetting(ctx context.Context, key, value string) error UpdateGlobalSetting(ctx context.Context, key, value string) error
// UpdateGlobalSettingList(ctx context.Context, settingList domain.ValidSettingList) error UpdateGlobalSettingList(ctx context.Context, settingList domain.ValidSettingList) error
// InsertCompanySetting(ctx context.Context, key, value string, companyID int64) error // InsertCompanySetting(ctx context.Context, key, value string, companyID int64) error
// InsertCompanySettingList(ctx context.Context, settingList domain.ValidSettingList, companyID int64) error // InsertCompanySettingList(ctx context.Context, settingList domain.ValidSettingList, companyID int64) error
@ -18,6 +19,6 @@ type SettingStore interface {
// GetOverrideSettings(ctx context.Context, companyID int64) ([]domain.Setting, error) // GetOverrideSettings(ctx context.Context, companyID int64) ([]domain.Setting, error)
// GetOverrideSettingsList(ctx context.Context, companyID int64) (domain.SettingList, error) // GetOverrideSettingsList(ctx context.Context, companyID int64) (domain.SettingList, error)
// DeleteCompanySetting(ctx context.Context, companyID int64, key string) error // DeleteCompanySetting(ctx context.Context, companyID int64, key string) error
DeleteAllCompanySetting(ctx context.Context, companyID int64) error // DeleteAllCompanySetting(ctx context.Context, companyID int64) error
EnsureAllSettingsExist(ctx context.Context) error EnsureAllSettingsExist(ctx context.Context) error
} }

View File

@ -2,34 +2,53 @@ package ports
import ( import (
"context" "context"
"time"
"Yimaru-Backend/internal/domain" "Yimaru-Backend/internal/domain"
) )
type UserStore interface { type UserStore interface {
CreateUser(ctx context.Context, user domain.User, usedOtpId int64, is_company bool) (domain.User, error) CreateUser(ctx context.Context, user domain.User, usedOtpId int64) (domain.User, error)
CreateUserWithoutOtp(ctx context.Context, user domain.User, is_company bool) (domain.User, error) CreateUserWithoutOtp(ctx context.Context, user domain.User) (domain.User, error)
GetUserByID(ctx context.Context, id int64) (domain.User, error) GetUserByID(ctx context.Context, id int64) (domain.User, error)
GetAllUsers(ctx context.Context, filter domain.UserFilter) ([]domain.User, int64, error) GetAllUsers(
// GetAllCashiers(ctx context.Context, filter domain.UserFilter) ([]domain.GetCashier, int64, error) ctx context.Context,
// GetCashierByID(ctx context.Context, cashierID int64) (domain.GetCashier, error) role *string,
GetCashiersByBranch(ctx context.Context, branchID int64) ([]domain.User, error) organizationID *int64,
GetAdminByCompanyID(ctx context.Context, companyID int64) (domain.User, error) query *string,
UpdateUser(ctx context.Context, user domain.UpdateUserReq) error createdBefore, createdAfter *time.Time,
UpdateUserCompany(ctx context.Context, id int64, companyID int64) error limit, offset int32,
) ([]domain.User, int64, error)
GetTotalUsers(ctx context.Context, role *string, organizationID *int64) (int64, error)
SearchUserByNameOrPhone(ctx context.Context, search string, organizationID *int64, role *string) ([]domain.User, error)
UpdateUser(ctx context.Context, user domain.User) error
UpdateUserOrganization(ctx context.Context, userID, organizationID int64) error
SuspendUser(ctx context.Context, userID int64, suspended bool, suspendedAt time.Time) error
DeleteUser(ctx context.Context, userID int64) error
CheckPhoneEmailExist(ctx context.Context, phone, email string, organizationID domain.ValidInt64) (phoneExists, emailExists bool, err error)
GetUserByEmailPhone(
ctx context.Context,
email string,
phone string,
organizationID domain.ValidInt64,
) (domain.User, error)
UpdatePassword(ctx context.Context, password, email, phone string, organizationID int64, updatedAt time.Time) error
GetOwnerByOrganizationID(ctx context.Context, organizationID int64) (domain.User, error)
UpdateUserSuspend(ctx context.Context, id int64, status bool) error UpdateUserSuspend(ctx context.Context, id int64, status bool) error
DeleteUser(ctx context.Context, id int64) error
CheckPhoneEmailExist(ctx context.Context, phoneNum, email string, companyID domain.ValidInt64) (bool, bool, error)
GetUserByEmail(ctx context.Context, email string, companyID domain.ValidInt64) (domain.User, error)
GetUserByPhone(ctx context.Context, phoneNum string, companyID domain.ValidInt64) (domain.User, error)
SearchUserByNameOrPhone(ctx context.Context, searchString string, role *domain.Role, companyID domain.ValidInt64) ([]domain.User, error)
UpdatePassword(ctx context.Context, identifier string, password []byte, usedOtpId int64, companyId int64) error
GetUserByEmailPhone(ctx context.Context, email, phone string, companyID domain.ValidInt64) (domain.User, error)
GetCustomerCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error) // UpdateUser(ctx context.Context, user domain.UpdateUserReq) error
GetCustomerDetails(ctx context.Context, filter domain.ReportFilter) (map[int64]domain.CustomerDetail, error) // UpdateUserSuspend(ctx context.Context, id int64, status bool) error
GetBranchCustomerCounts(ctx context.Context, filter domain.ReportFilter) (map[int64]int64, error) // DeleteUser(ctx context.Context, id int64) error
GetRoleCounts(ctx context.Context, role string, filter domain.ReportFilter) (total, active, inactive int64, err error) // CheckPhoneEmailExist(ctx context.Context, phoneNum, email string, companyID domain.ValidInt64) (bool, bool, error)
// GetUserByEmail(ctx context.Context, email string, companyID domain.ValidInt64) (domain.User, error)
// GetUserByPhone(ctx context.Context, phoneNum string, companyID domain.ValidInt64) (domain.User, error)
// SearchUserByNameOrPhone(ctx context.Context, searchString string, role *domain.Role, companyID domain.ValidInt64) ([]domain.User, error)
// UpdatePassword(ctx context.Context, identifier string, password []byte, usedOtpId int64, companyId int64) error
// GetUserByEmailPhone(ctx context.Context, email, phone string, companyID domain.ValidInt64) (domain.User, error)
// GetCustomerCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error)
// GetCustomerDetails(ctx context.Context, filter domain.ReportFilter) (map[int64]domain.CustomerDetail, error)
// GetRoleCounts(ctx context.Context, role string, filter domain.ReportFilter) (total, active, inactive int64, err error)
} }
type SmsGateway interface { type SmsGateway interface {
SendSMSOTP(ctx context.Context, phoneNumber, otp string) error SendSMSOTP(ctx context.Context, phoneNumber, otp string) error

View File

@ -0,0 +1,64 @@
package repository
import (
dbgen "Yimaru-Backend/gen/db"
"context"
)
type ReportedIssueRepository interface {
CreateReportedIssue(ctx context.Context, arg dbgen.CreateReportedIssueParams) (dbgen.ReportedIssue, error)
ListReportedIssues(ctx context.Context, limit, offset int32) ([]dbgen.ReportedIssue, error)
ListReportedIssuesByUser(ctx context.Context, userID int64, limit, offset int32) ([]dbgen.ReportedIssue, error)
CountReportedIssues(ctx context.Context) (int64, error)
CountReportedIssuesByUser(ctx context.Context, userID int64) (int64, error)
UpdateReportedIssueStatus(ctx context.Context, id int64, status string) error
DeleteReportedIssue(ctx context.Context, id int64) error
}
type ReportedIssueRepo struct {
store *Store
}
func NewReportedIssueRepository(store *Store) ReportedIssueRepository {
return &ReportedIssueRepo{store: store}
}
func (s *ReportedIssueRepo) CreateReportedIssue(ctx context.Context, arg dbgen.CreateReportedIssueParams) (dbgen.ReportedIssue, error) {
return s.store.queries.CreateReportedIssue(ctx, arg)
}
func (s *ReportedIssueRepo) ListReportedIssues(ctx context.Context, limit, offset int32) ([]dbgen.ReportedIssue, error) {
params := dbgen.ListReportedIssuesParams{
Limit: limit,
Offset: offset,
}
return s.store.queries.ListReportedIssues(ctx, params)
}
func (s *ReportedIssueRepo) ListReportedIssuesByUser(ctx context.Context, userID int64, limit, offset int32) ([]dbgen.ReportedIssue, error) {
params := dbgen.ListReportedIssuesByUserParams{
UserID: userID,
Limit: limit,
Offset: offset,
}
return s.store.queries.ListReportedIssuesByUser(ctx, params)
}
func (s *ReportedIssueRepo) CountReportedIssues(ctx context.Context) (int64, error) {
return s.store.queries.CountReportedIssues(ctx)
}
func (s *ReportedIssueRepo) CountReportedIssuesByUser(ctx context.Context, userID int64) (int64, error) {
return s.store.queries.CountReportedIssuesByUser(ctx, userID)
}
func (s *ReportedIssueRepo) UpdateReportedIssueStatus(ctx context.Context, id int64, status string) error {
return s.store.queries.UpdateReportedIssueStatus(ctx, dbgen.UpdateReportedIssueStatusParams{
ID: id,
Status: status,
})
}
func (s *ReportedIssueRepo) DeleteReportedIssue(ctx context.Context, id int64) error {
return s.store.queries.DeleteReportedIssue(ctx, id)
}

View File

@ -1,356 +1,192 @@
package repository package repository
// import ( import (
// "context" "context"
// "encoding/json" "encoding/json"
// "fmt"
// dbgen "Yimaru-Backend/gen/db" dbgen "Yimaru-Backend/gen/db"
// "Yimaru-Backend/internal/domain" "Yimaru-Backend/internal/domain"
// "Yimaru-Backend/internal/ports" "Yimaru-Backend/internal/ports"
// "github.com/jackc/pgx/v5/pgtype"
// )
// func NewNotificationStore(s *Store) ports.NotificationStore { return s } "github.com/jackc/pgx/v5/pgtype"
)
// func (r *Store) CreateNotification(ctx context.Context, notification *domain.Notification) (*domain.Notification, error) { func NewNotificationStore(s *Store) ports.NotificationStore {
// var errorSeverity pgtype.Text return s
// if notification.ErrorSeverity != "" { }
// errorSeverity.String = string(notification.ErrorSeverity)
// errorSeverity.Valid = true
// }
// var deliveryChannel pgtype.Text /* =========================
// if notification.DeliveryChannel != "" { Create
// deliveryChannel.String = string(notification.DeliveryChannel) ========================= */
// deliveryChannel.Valid = true
// }
// var priority pgtype.Int4 func (r *Store) CreateNotification(
// if notification.Priority != 0 { ctx context.Context,
// priority.Int32 = int32(notification.Priority) n *domain.Notification,
// priority.Valid = true ) (*domain.Notification, error) {
// }
// params := dbgen.CreateNotificationParams{ params := dbgen.CreateNotificationParams{
// ID: notification.ID, UserID: n.RecipientID,
// RecipientID: notification.RecipientID, Type: string(n.Type),
// Type: string(notification.Type), Level: string(n.Level),
// Level: string(notification.Level), Channel: pgtype.Text{String: string(n.DeliveryChannel)},
// ErrorSeverity: errorSeverity, Title: n.Payload.Headline,
// Reciever: string(notification.Reciever), Message: n.Payload.Message,
// IsRead: notification.IsRead, Payload: marshalPayload(n.Payload),
// DeliveryStatus: string(notification.DeliveryStatus), }
// DeliveryChannel: deliveryChannel,
// Payload: marshalPayload(notification.Payload),
// Priority: priority,
// Timestamp: pgtype.Timestamptz{Time: notification.Timestamp, Valid: true},
// Expires: pgtype.Timestamptz{Time: notification.Expires, Valid: true},
// Img: pgtype.Text{String: notification.Image, Valid: notification.Image != ""},
// Metadata: notification.Metadata,
// }
// dbNotification, err := r.queries.CreateNotification(ctx, params) dbNotif, err := r.queries.CreateNotification(ctx, params)
// if err != nil { if err != nil {
// return nil, err return nil, err
// } }
// return r.mapDBToDomain(&dbNotification), nil return mapDBToDomain(&dbNotif), nil
// } }
// func (r *Store) UpdateNotificationStatus(ctx context.Context, id, status string, isRead bool, metadata []byte) (*domain.Notification, error) { /* =========================
// params := dbgen.UpdateNotificationStatusParams{ Read
// ID: id, ========================= */
// DeliveryStatus: status,
// IsRead: isRead,
// Metadata: metadata,
// }
// dbNotification, err := r.queries.UpdateNotificationStatus(ctx, params) func (r *Store) GetUserNotifications(
// if err != nil { ctx context.Context,
// return nil, err userID int64,
// } limit, offset int,
) ([]domain.Notification, int64, error) {
// return r.mapDBToDomain(&dbNotification), nil params := dbgen.GetUserNotificationsParams{
// } UserID: userID,
Limit: int32(limit),
Offset: int32(offset),
}
// func (r *Store) GetUserNotifications(ctx context.Context, recipientID int64, limit, offset int) ([]domain.Notification, int64, error) { rows, err := r.queries.GetUserNotifications(ctx, params)
// params := dbgen.GetUserNotificationsParams{ if err != nil {
// RecipientID: recipientID, return nil, 0, err
// Limit: int32(limit), }
// Offset: int32(offset),
// }
// dbNotifications, err := r.queries.GetUserNotifications(ctx, params) total, err := r.queries.GetUserNotificationCount(ctx, userID)
// if err != nil { if err != nil {
// return nil, 0, err return nil, 0, err
// } }
// total, err := r.queries.GetUserNotificationCount(ctx, recipientID) result := make([]domain.Notification, 0, len(rows))
for _, row := range rows {
result = append(result, *mapDBToDomain(&row))
}
// if err != nil { return result, total, nil
// return nil, 0, err }
// }
// var result []domain.Notification = make([]domain.Notification, 0, len(dbNotifications)) func (r *Store) GetAllNotifications(
// for _, dbNotif := range dbNotifications { ctx context.Context,
// domainNotif := r.mapDBToDomain(&dbNotif) limit, offset int,
// result = append(result, *domainNotif) ) ([]domain.Notification, error) {
// }
// return result, total, nil rows, err := r.queries.GetAllNotifications(ctx, dbgen.GetAllNotificationsParams{
// } Limit: int32(limit),
Offset: int32(offset),
})
if err != nil {
return nil, err
}
// func (r *Store) GetAllNotifications(ctx context.Context, limit, offset int) ([]domain.Notification, error) { result := make([]domain.Notification, 0, len(rows))
for _, row := range rows {
result = append(result, *mapDBToDomain(&row))
}
// dbNotifications, err := r.queries.GetAllNotifications(ctx, dbgen.GetAllNotificationsParams{ return result, nil
// Limit: int32(limit), }
// Offset: int32(offset),
// })
// if err != nil {
// return nil, err
// }
// var result []domain.Notification = make([]domain.Notification, 0, len(dbNotifications)) func (r *Store) CountUnreadNotifications(
// for _, dbNotif := range dbNotifications { ctx context.Context,
// domainNotif := r.mapDBToDomain(&dbNotif) userID int64,
// result = append(result, *domainNotif) ) (int64, error) {
// } return r.queries.CountUnreadNotifications(ctx, userID)
// return result, nil }
// }
// func (r *Store) ListFailedNotifications(ctx context.Context, limit int) ([]domain.Notification, error) { /* =========================
// dbNotifications, err := r.queries.ListFailedNotifications(ctx, int32(limit)) Update
// if err != nil { ========================= */
// return nil, err
// }
// var result []domain.Notification func (r *Store) MarkNotificationAsRead(
// for _, dbNotif := range dbNotifications { ctx context.Context,
// domainNotif := r.mapDBToDomain(&dbNotif) id int64,
// result = append(result, *domainNotif) ) (*domain.Notification, error) {
// }
// return result, nil dbNotif, err := r.queries.MarkNotificationAsRead(ctx, id)
// } if err != nil {
return nil, err
}
// func (r *Store) ListRecipientIDs(ctx context.Context, receiver domain.NotificationRecieverSide) ([]int64, error) { return mapDBToDomain(&dbNotif), nil
// return r.queries.ListRecipientIDsByReceiver(ctx, string(receiver)) }
// }
// func (s *Store) DeleteOldNotifications(ctx context.Context) error { func (r *Store) MarkAllUserNotificationsAsRead(
// return s.queries.DeleteOldNotifications(ctx) ctx context.Context,
// } userID int64,
) error {
return r.queries.MarkAllUserNotificationsAsRead(ctx, userID)
}
// func (r *Store) mapDBToDomain(dbNotif *dbgen.Notification) *domain.Notification { /* =========================
// var errorSeverity domain.NotificationErrorSeverity Delete
// if dbNotif.ErrorSeverity.Valid { ========================= */
// errorSeverity = domain.NotificationErrorSeverity(dbNotif.ErrorSeverity.String)
// } else { func (r *Store) DeleteUserNotifications(
// errorSeverity = "" ctx context.Context,
// } userID int64,
) error {
return r.queries.DeleteUserNotifications(ctx, userID)
}
// var deliveryChannel domain.DeliveryChannel /* =========================
// if dbNotif.DeliveryChannel.Valid { Mapping
// deliveryChannel = domain.DeliveryChannel(dbNotif.DeliveryChannel.String) ========================= */
// } else {
// deliveryChannel = ""
// }
// var priority int func mapDBToDomain(db *dbgen.Notification) *domain.Notification {
// if dbNotif.Priority.Valid { payload, err := unmarshalPayload(db.Payload)
// priority = int(dbNotif.Priority.Int32) if err != nil {
// } payload = domain.NotificationPayload{}
}
// payload, err := unmarshalPayload(dbNotif.Payload) var channel domain.DeliveryChannel
// if err != nil { if db.Channel.Valid {
// payload = domain.NotificationPayload{} channel = domain.DeliveryChannel(db.Channel.String)
// } }
// return &domain.Notification{ return &domain.Notification{
// ID: dbNotif.ID, ID: string(db.ID),
// RecipientID: dbNotif.RecipientID, RecipientID: db.UserID,
// Type: domain.NotificationType(dbNotif.Type), Type: domain.NotificationType(db.Type),
// Level: domain.NotificationLevel(dbNotif.Level), Level: domain.NotificationLevel(db.Level),
// ErrorSeverity: errorSeverity, DeliveryChannel: channel,
// Reciever: domain.NotificationRecieverSide(dbNotif.Reciever), DeliveryStatus: "PENDING",
// IsRead: dbNotif.IsRead, Payload: domain.NotificationPayload{
// DeliveryStatus: domain.NotificationDeliveryStatus(dbNotif.DeliveryStatus), Headline: payload.Headline,
// DeliveryChannel: deliveryChannel, Message: payload.Message,
// Payload: payload, },
// Priority: priority, IsRead: db.IsRead,
// Timestamp: dbNotif.Timestamp.Time, Timestamp: db.CreatedAt.Time,
// Expires: dbNotif.Expires.Time, // ReadAt: db.ReadAt.Time,
// Image: dbNotif.Img.String, }
// Metadata: dbNotif.Metadata, }
// }
// }
// func marshalPayload(payload domain.NotificationPayload) []byte { /* =========================
// data, _ := json.Marshal(payload) JSON Helpers
// return data ========================= */
// }
// func unmarshalPayload(data []byte) (domain.NotificationPayload, error) { func marshalPayload(p domain.NotificationPayload) []byte {
// var payload domain.NotificationPayload b, _ := json.Marshal(p)
// if err := json.Unmarshal(data, &payload); err != nil { return b
// return domain.NotificationPayload{}, err }
// }
// return payload, nil
// }
// func (r *Store) CountUnreadNotifications(ctx context.Context, recipient_id int64) (int64, error) { func unmarshalPayload(b []byte) (domain.NotificationPayload, error) {
// return r.queries.CountUnreadNotifications(ctx, recipient_id) var p domain.NotificationPayload
// } if len(b) == 0 {
return p, nil
// func (r *Store) GetNotificationCounts(ctx context.Context, filter domain.ReportFilter) (total, read, unread int64, err error) { }
// rows, err := r.queries.GetNotificationCounts(ctx) if err := json.Unmarshal(b, &p); err != nil {
// if err != nil { return p, err
// return 0, 0, 0, fmt.Errorf("failed to get notification counts: %w", err) }
// } return p, nil
}
// // var total, read, unread int64
// for _, row := range rows {
// total += row.Total
// read += row.Read
// unread += row.Unread
// }
// return total, read, unread, nil
// }
// func (s *Store) GetMostActiveNotificationRecipients(ctx context.Context, filter domain.ReportFilter, limit int) ([]domain.ActiveNotificationRecipient, error) {
// query := `SELECT
// n.recipient_id,
// u.first_name || ' ' || u.last_name as recipient_name,
// COUNT(*) as notification_count,
// MAX(n.timestamp) as last_notification_time
// FROM notifications n
// JOIN users u ON n.recipient_id = u.id
// WHERE n.timestamp BETWEEN $1 AND $2
// GROUP BY n.recipient_id, u.first_name, u.last_name
// ORDER BY notification_count DESC
// LIMIT $3`
// var recipients []domain.ActiveNotificationRecipient
// rows, err := s.conn.Query(ctx, query, filter.StartTime.Value, filter.EndTime.Value, limit)
// if err != nil {
// return nil, fmt.Errorf("failed to get active notification recipients: %w", err)
// }
// defer rows.Close()
// for rows.Next() {
// var r domain.ActiveNotificationRecipient
// if err := rows.Scan(&r.RecipientID, &r.RecipientName, &r.NotificationCount, &r.LastNotificationTime); err != nil {
// return nil, err
// }
// recipients = append(recipients, r)
// }
// return recipients, nil
// }
// // GetNotificationDeliveryStats
// func (s *Store) GetNotificationDeliveryStats(ctx context.Context, filter domain.ReportFilter) (domain.NotificationDeliveryStats, error) {
// query := `SELECT
// COUNT(*) as total_sent,
// COUNT(CASE WHEN delivery_status = 'failed' THEN 1 END) as failed_deliveries,
// (COUNT(CASE WHEN delivery_status = 'sent' THEN 1 END) * 100.0 / NULLIF(COUNT(*), 0)) as success_rate,
// MODE() WITHIN GROUP (ORDER BY delivery_channel) as most_used_channel
// FROM notifications
// WHERE timestamp BETWEEN $1 AND $2`
// var stats domain.NotificationDeliveryStats
// row := s.conn.QueryRow(ctx, query, filter.StartTime.Value, filter.EndTime.Value)
// err := row.Scan(&stats.TotalSent, &stats.FailedDeliveries, &stats.SuccessRate, &stats.MostUsedChannel)
// if err != nil {
// return domain.NotificationDeliveryStats{}, fmt.Errorf("failed to get notification delivery stats: %w", err)
// }
// return stats, nil
// }
// // GetNotificationCountsByType
// func (s *Store) GetNotificationCountsByType(ctx context.Context, filter domain.ReportFilter) (map[string]domain.NotificationTypeCount, error) {
// query := `SELECT
// type,
// COUNT(*) as total,
// COUNT(CASE WHEN is_read = true THEN 1 END) as read,
// COUNT(CASE WHEN is_read = false THEN 1 END) as unread
// FROM notifications
// WHERE timestamp BETWEEN $1 AND $2
// GROUP BY type`
// counts := make(map[string]domain.NotificationTypeCount)
// rows, err := s.conn.Query(ctx, query, filter.StartTime.Value, filter.EndTime.Value)
// if err != nil {
// return nil, fmt.Errorf("failed to get notification counts by type: %w", err)
// }
// defer rows.Close()
// for rows.Next() {
// var nt domain.NotificationTypeCount
// var typ string
// if err := rows.Scan(&typ, &nt.Total, &nt.Read, &nt.Unread); err != nil {
// return nil, err
// }
// counts[typ] = nt
// }
// return counts, nil
// }
// // func (s *Store) GetAllNotifications(ctx context.Context, limit, offset int) ([]domain.Notification, error) {
// // dbNotifications, err := s.queries.GetAllNotifications(ctx, dbgen.GetAllNotificationsParams{
// // Limit: int32(limit),
// // Offset: int32(offset),
// // })
// // if err != nil {
// // return nil, err
// // }
// // result := make([]domain.Notification, 0, len(dbNotifications))
// // for _, dbNotif := range dbNotifications {
// // // You may want to move this mapping logic to a shared function if not already present
// // var errorSeverity *domain.NotificationErrorSeverity
// // if dbNotif.ErrorSeverity.Valid {
// // s := domain.NotificationErrorSeverity(dbNotif.ErrorSeverity.String)
// // errorSeverity = &s
// // }
// // var deliveryChannel domain.DeliveryChannel
// // if dbNotif.DeliveryChannel.Valid {
// // deliveryChannel = domain.DeliveryChannel(dbNotif.DeliveryChannel.String)
// // } else {
// // deliveryChannel = ""
// // }
// // var priority int
// // if dbNotif.Priority.Valid {
// // priority = int(dbNotif.Priority.Int32)
// // }
// // payload, err := unmarshalPayload(dbNotif.Payload)
// // if err != nil {
// // payload = domain.NotificationPayload{}
// // }
// // result = append(result, domain.Notification{
// // ID: dbNotif.ID,
// // RecipientID: dbNotif.RecipientID,
// // Type: domain.NotificationType(dbNotif.Type),
// // Level: domain.NotificationLevel(dbNotif.Level),
// // ErrorSeverity: errorSeverity,
// // Reciever: domain.NotificationRecieverSide(dbNotif.Reciever),
// // IsRead: dbNotif.IsRead,
// // DeliveryStatus: domain.NotificationDeliveryStatus(dbNotif.DeliveryStatus),
// // DeliveryChannel: deliveryChannel,
// // Payload: payload,
// // Priority: priority,
// // Timestamp: dbNotif.Timestamp.Time,
// // Metadata: dbNotif.Metadata,
// // })
// // }
// // return result, nil
// // }

View File

@ -0,0 +1,168 @@
package repository
import (
dbgen "Yimaru-Backend/gen/db"
"Yimaru-Backend/internal/domain"
"Yimaru-Backend/internal/ports"
"context"
"fmt"
"go.uber.org/zap"
)
// Interface for creating new setting store
func NewSettingStore(s *Store) ports.SettingStore { return s }
func (s *Store) InsertGlobalSetting(ctx context.Context, setting domain.CreateSetting) error {
err := s.queries.InsertGlobalSetting(ctx, dbgen.InsertGlobalSettingParams{
Key: setting.Key,
Value: setting.Value,
})
if err != nil {
return err
}
return nil
}
func (s *Store) GetGlobalSettingList(ctx context.Context) (domain.SettingList, error) {
settings, err := s.queries.GetGlobalSettings(ctx)
if err != nil {
domain.MongoDBLogger.Error("failed to get all settings", zap.Error(err))
return domain.SettingList{}, err
}
return domain.ConvertDBGlobalSettingList(settings)
}
func (s *Store) GetGlobalSettings(ctx context.Context) ([]domain.Setting, error) {
settings, err := s.queries.GetGlobalSettings(ctx)
if err != nil {
domain.MongoDBLogger.Error("failed to get all settings", zap.Error(err))
}
var result []domain.Setting = make([]domain.Setting, 0, len(settings))
for _, setting := range settings {
result = append(result, domain.Setting{
Key: setting.Key,
Value: setting.Value,
UpdatedAt: setting.UpdatedAt.Time,
})
}
return result, nil
}
func (s *Store) GetGlobalSetting(ctx context.Context, key string) (domain.Setting, error) {
dbSetting, err := s.queries.GetGlobalSetting(ctx, key)
if err != nil {
domain.MongoDBLogger.Error("failed to get all settings", zap.Error(err))
}
result := domain.Setting{
Key: dbSetting.Key,
Value: dbSetting.Value,
UpdatedAt: dbSetting.UpdatedAt.Time,
}
return result, nil
}
func (s *Store) UpdateGlobalSetting(ctx context.Context, key, value string) error {
err := s.queries.UpdateGlobalSetting(ctx, dbgen.UpdateGlobalSettingParams{
Key: key,
Value: value,
})
if err != nil {
domain.MongoDBLogger.Error("failed to update setting",
zap.String("key", key),
zap.String("value", value),
zap.Error(err),
)
return err
}
return err
}
func (s *Store) UpdateGlobalSettingList(ctx context.Context, settingList domain.ValidSettingList) error {
convertedSettings := settingList.ConvertAllSettings()
for _, setting := range convertedSettings {
err := s.UpdateGlobalSetting(ctx, setting.Key, setting.Value)
if err != nil {
domain.MongoDBLogger.Warn("failed to update setting list", zap.String("key", setting.Key), zap.Error(err))
return err
}
}
return nil
}
// func (s *Store) GetOverrideSettings(ctx context.Context, companyID int64) ([]domain.Setting, error) {
// settings, err := s.queries.GetOverrideSettings(ctx, companyID)
// if err != nil {
// return nil, err
// }
// result := make([]domain.Setting, 0, len(settings))
// for _, setting := range settings {
// result = append(result, domain.Setting{
// Key: setting.Key,
// Value: setting.Value,
// UpdatedAt: setting.UpdatedAt.Time,
// })
// }
// return result, nil
// }
// func (s *Store) GetOverrideSettingsList(ctx context.Context, companyID int64) (domain.SettingList, error) {
// settings, err := s.queries.GetOverrideSettings(ctx, companyID)
// if err != nil {
// return domain.SettingList{}, err
// }
// return domain.ConvertDBOverrideSettingList(settings)
// }
// func (s *Store) DeleteCompanySetting(ctx context.Context, companyID int64, key string) error {
// return s.queries.DeleteCompanySetting(ctx, dbgen.DeleteCompanySettingParams{
// CompanyID: companyID,
// Key: key,
// })
// }
// func (s *Store) DeleteAllCompanySetting(ctx context.Context, companyID int64) error {
// return s.queries.DeleteAllCompanySetting(ctx, companyID)
// }
func (s *Store) EnsureAllSettingsExist(ctx context.Context) error {
defaultSettings := domain.NewDefaultSettingList().ToSettingArray() // returns []domain.Setting from your typed struct
dbSettings, err := s.GetGlobalSettings(ctx)
if err != nil {
return fmt.Errorf("failed to fetch settings: %w", err)
}
existing := map[string]struct{}{}
for _, s := range dbSettings {
existing[s.Key] = struct{}{}
}
for _, setting := range defaultSettings {
if _, found := existing[setting.Key]; !found {
if err := s.InsertGlobalSetting(ctx, domain.CreateSetting{
Key: setting.Key,
Value: setting.Value,
}); err != nil {
return fmt.Errorf("failed to create missing setting %q: %w", setting.Key, err)
}
}
}
return nil
}

View File

@ -3,7 +3,10 @@ package repository
import ( import (
dbgen "Yimaru-Backend/gen/db" dbgen "Yimaru-Backend/gen/db"
"Yimaru-Backend/internal/domain" "Yimaru-Backend/internal/domain"
"Yimaru-Backend/internal/ports"
"Yimaru-Backend/internal/services/authentication"
"context" "context"
"database/sql"
"errors" "errors"
"time" "time"
@ -23,6 +26,54 @@ import (
// } // }
// } // }
func NewUserStore(s *Store) ports.UserStore { return s }
func (s *Store) CreateUserWithoutOtp(ctx context.Context, user domain.User) (domain.User, error) {
userRes, err := s.queries.CreateUser(ctx, dbgen.CreateUserParams{
FirstName: user.FirstName,
LastName: user.LastName,
NickName: pgtype.Text{String: user.NickName},
Email: pgtype.Text{String: user.Email, Valid: user.Email != ""},
PhoneNumber: pgtype.Text{String: user.PhoneNumber, Valid: user.PhoneNumber != ""},
Role: string(user.Role),
Password: user.Password,
Age: pgtype.Int4{Int32: int32(user.Age), Valid: user.Age > 0},
EducationLevel: pgtype.Text{String: user.EducationLevel, Valid: user.EducationLevel != ""},
Country: pgtype.Text{String: user.Country, Valid: user.Country != ""},
Region: pgtype.Text{String: user.Region, Valid: user.Region != ""},
EmailVerified: user.EmailVerified,
PhoneVerified: user.PhoneVerified,
Suspended: user.Suspended,
SuspendedAt: pgtype.Timestamptz{Time: user.SuspendedAt, Valid: !user.SuspendedAt.IsZero()},
OrganizationID: pgtype.Int8{Int64: user.OrganizationID.Value, Valid: user.OrganizationID.Valid},
CreatedAt: pgtype.Timestamptz{Time: time.Now(), Valid: true},
UpdatedAt: pgtype.Timestamptz{Time: time.Now(), Valid: true},
})
if err != nil {
return domain.User{}, err
}
return domain.User{
ID: userRes.ID,
FirstName: userRes.FirstName,
LastName: userRes.LastName,
NickName: userRes.NickName.String,
Email: userRes.Email.String,
PhoneNumber: userRes.PhoneNumber.String,
Role: domain.Role(userRes.Role),
Age: int(userRes.Age.Int32),
EducationLevel: userRes.EducationLevel.String,
Country: userRes.Country.String,
Region: userRes.Region.String,
EmailVerified: userRes.EmailVerified,
PhoneVerified: userRes.PhoneVerified,
Suspended: userRes.Suspended,
SuspendedAt: userRes.SuspendedAt.Time,
OrganizationID: domain.ValidInt64{Value: userRes.OrganizationID.Int64, Valid: userRes.OrganizationID.Valid},
CreatedAt: userRes.CreatedAt.Time,
UpdatedAt: userRes.UpdatedAt.Time,
}, nil
}
// CreateUser inserts a new user into the database // CreateUser inserts a new user into the database
func (s *Store) CreateUser(ctx context.Context, user domain.User, usedOtpId int64) (domain.User, error) { func (s *Store) CreateUser(ctx context.Context, user domain.User, usedOtpId int64) (domain.User, error) {
// Optional: mark OTP as used // Optional: mark OTP as used
@ -117,23 +168,54 @@ func (s *Store) GetUserByID(ctx context.Context, id int64) (domain.User, error)
} }
// GetAllUsers retrieves users with optional filters // GetAllUsers retrieves users with optional filters
func (s *Store) GetAllUsers(ctx context.Context, role *string, organizationID *int64, query *string, createdBefore, createdAfter *time.Time, limit, offset int32) ([]domain.User, error) { func (s *Store) GetAllUsers(
rows, err := s.queries.GetAllUsers(ctx, dbgen.GetAllUsersParams{ ctx context.Context,
Role: *role, role *string,
OrganizationID: pgtype.Int8{Int64: *organizationID}, organizationID *int64,
Query: pgtype.Text{String: *query}, query *string,
CreatedBefore: pgtype.Timestamptz{Time: *createdBefore}, createdBefore, createdAfter *time.Time,
CreatedAfter: pgtype.Timestamptz{Time: *createdAfter}, limit, offset int32,
Limit: pgtype.Int4{Int32: limit}, ) ([]domain.User, int64, error) {
Offset: pgtype.Int4{Int32: offset},
}) params := dbgen.GetAllUsersParams{
if err != nil { Limit: pgtype.Int4{Int32: limit, Valid: true},
return nil, err Offset: pgtype.Int4{Int32: offset, Valid: true},
} }
users := make([]domain.User, len(rows)) if role != nil {
for i, u := range rows { params.Role = *role
users[i] = domain.User{ }
if organizationID != nil {
params.OrganizationID = pgtype.Int8{Int64: *organizationID, Valid: true}
}
if query != nil {
params.Query = pgtype.Text{String: *query, Valid: true}
}
if createdBefore != nil {
params.CreatedBefore = pgtype.Timestamptz{Time: *createdBefore, Valid: true}
}
if createdAfter != nil {
params.CreatedAfter = pgtype.Timestamptz{Time: *createdAfter, Valid: true}
}
rows, err := s.queries.GetAllUsers(ctx, params)
if err != nil {
return nil, 0, err
}
if len(rows) == 0 {
return []domain.User{}, 0, nil
}
totalCount := rows[0].TotalCount
users := make([]domain.User, 0, len(rows))
for _, u := range rows {
users = append(users, domain.User{
ID: u.ID, ID: u.ID,
FirstName: u.FirstName, FirstName: u.FirstName,
LastName: u.LastName, LastName: u.LastName,
@ -149,13 +231,16 @@ func (s *Store) GetAllUsers(ctx context.Context, role *string, organizationID *i
PhoneVerified: u.PhoneVerified, PhoneVerified: u.PhoneVerified,
Suspended: u.Suspended, Suspended: u.Suspended,
SuspendedAt: u.SuspendedAt.Time, SuspendedAt: u.SuspendedAt.Time,
OrganizationID: domain.ValidInt64{Value: u.OrganizationID.Int64, Valid: u.OrganizationID.Valid}, OrganizationID: domain.ValidInt64{
Value: u.OrganizationID.Int64,
Valid: u.OrganizationID.Valid,
},
CreatedAt: u.CreatedAt.Time, CreatedAt: u.CreatedAt.Time,
UpdatedAt: u.UpdatedAt.Time, UpdatedAt: u.UpdatedAt.Time,
} })
} }
return users, nil return users, totalCount, nil
} }
// GetTotalUsers counts users with optional filters // GetTotalUsers counts users with optional filters
@ -241,11 +326,11 @@ func (s *Store) DeleteUser(ctx context.Context, userID int64) error {
} }
// CheckPhoneEmailExist checks if phone or email exists in an organization // CheckPhoneEmailExist checks if phone or email exists in an organization
func (s *Store) CheckPhoneEmailExist(ctx context.Context, phone, email string, organizationID int64) (phoneExists, emailExists bool, err error) { func (s *Store) CheckPhoneEmailExist(ctx context.Context, phone, email string, organizationID domain.ValidInt64) (phoneExists, emailExists bool, err error) {
res, err := s.queries.CheckPhoneEmailExist(ctx, dbgen.CheckPhoneEmailExistParams{ res, err := s.queries.CheckPhoneEmailExist(ctx, dbgen.CheckPhoneEmailExistParams{
PhoneNumber: pgtype.Text{String: phone}, PhoneNumber: pgtype.Text{String: phone},
Email: pgtype.Text{String: email}, Email: pgtype.Text{String: email},
OrganizationID: pgtype.Int8{Int64: organizationID}, OrganizationID: pgtype.Int8{Int64: organizationID.Value},
}) })
if err != nil { if err != nil {
return false, false, err return false, false, err
@ -255,64 +340,54 @@ func (s *Store) CheckPhoneEmailExist(ctx context.Context, phone, email string, o
} }
// GetUserByEmail retrieves a user by email and organization // GetUserByEmail retrieves a user by email and organization
func (s *Store) GetUserByEmail(ctx context.Context, email string, organizationID int64) (domain.User, error) { func (s *Store) GetUserByEmailPhone(
userRes, err := s.queries.GetUserByEmail(ctx, dbgen.GetUserByEmailParams{ ctx context.Context,
Email: pgtype.Text{String: email}, email string,
OrganizationID: pgtype.Int8{Int64: organizationID}, phone string,
organizationID domain.ValidInt64,
) (domain.User, error) {
user, err := s.queries.GetUserByEmailPhone(ctx, dbgen.GetUserByEmailPhoneParams{
Email: pgtype.Text{
String: email,
Valid: email != "",
},
PhoneNumber: pgtype.Text{
String: phone,
Valid: phone != "",
},
OrganizationID: organizationID.ToPG(),
}) })
if err != nil { if err != nil {
return domain.User{}, err if errors.Is(err, sql.ErrNoRows) {
return domain.User{}, authentication.ErrUserNotFound
} }
return domain.User{ return domain.User{}, err
ID: userRes.ID,
FirstName: userRes.FirstName,
LastName: userRes.LastName,
NickName: userRes.NickName.String,
Email: userRes.Email.String,
PhoneNumber: userRes.PhoneNumber.String,
Role: domain.Role(userRes.Role),
Age: int(userRes.Age.Int32),
EducationLevel: userRes.EducationLevel.String,
Country: userRes.Country.String,
Region: userRes.Region.String,
EmailVerified: userRes.EmailVerified,
PhoneVerified: userRes.PhoneVerified,
Suspended: userRes.Suspended,
SuspendedAt: userRes.SuspendedAt.Time,
OrganizationID: domain.ValidInt64{Value: userRes.OrganizationID.Int64, Valid: userRes.OrganizationID.Valid},
CreatedAt: userRes.CreatedAt.Time,
UpdatedAt: userRes.UpdatedAt.Time,
}, nil
} }
// GetUserByPhone retrieves a user by phone and organization
func (s *Store) GetUserByPhone(ctx context.Context, phone string, organizationID int64) (domain.User, error) {
userRes, err := s.queries.GetUserByPhone(ctx, dbgen.GetUserByPhoneParams{
PhoneNumber: pgtype.Text{String: phone},
OrganizationID: pgtype.Int8{Int64: organizationID},
})
if err != nil {
return domain.User{}, err
}
return domain.User{ return domain.User{
ID: userRes.ID, ID: user.ID,
FirstName: userRes.FirstName, FirstName: user.FirstName,
LastName: userRes.LastName, LastName: user.LastName,
NickName: userRes.NickName.String, NickName: user.NickName.String,
Email: userRes.Email.String, Email: user.Email.String,
PhoneNumber: userRes.PhoneNumber.String, PhoneNumber: user.PhoneNumber.String,
Role: domain.Role(userRes.Role), Password: user.Password,
Age: int(userRes.Age.Int32), Role: domain.Role(user.Role),
EducationLevel: userRes.EducationLevel.String, Age: int(user.Age.Int32),
Country: userRes.Country.String, EducationLevel: user.EducationLevel.String,
Region: userRes.Region.String, Country: user.Country.String,
EmailVerified: userRes.EmailVerified, Region: user.Region.String,
PhoneVerified: userRes.PhoneVerified, EmailVerified: user.EmailVerified,
Suspended: userRes.Suspended, PhoneVerified: user.PhoneVerified,
SuspendedAt: userRes.SuspendedAt.Time, Suspended: user.Suspended,
OrganizationID: domain.ValidInt64{Value: userRes.OrganizationID.Int64, Valid: userRes.OrganizationID.Valid}, SuspendedAt: user.SuspendedAt.Time,
CreatedAt: userRes.CreatedAt.Time, OrganizationID: domain.ValidInt64{
UpdatedAt: userRes.UpdatedAt.Time, Value: user.OrganizationID.Int64,
Valid: user.OrganizationID.Valid,
},
CreatedAt: user.CreatedAt.Time,
UpdatedAt: user.UpdatedAt.Time,
}, nil }, nil
} }
@ -336,6 +411,21 @@ func (s *Store) GetOwnerByOrganizationID(ctx context.Context, organizationID int
return mapUser(userRes), nil return mapUser(userRes), nil
} }
func (s *Store) UpdateUserSuspend(ctx context.Context, id int64, status bool) error {
err := s.queries.SuspendUser(ctx, dbgen.SuspendUserParams{
ID: id,
Suspended: status,
SuspendedAt: pgtype.Timestamptz{
Time: time.Now(),
Valid: true,
},
})
if err != nil {
return err
}
return nil
}
// mapUser converts dbgen.User to domain.User // mapUser converts dbgen.User to domain.User
func mapUser(u dbgen.User) domain.User { func mapUser(u dbgen.User) domain.User {
return domain.User{ return domain.User{

View File

@ -1,69 +0,0 @@
package currency
import (
"Yimaru-Backend/internal/domain"
"context"
"encoding/json"
"fmt"
"net/http"
"time"
)
type FixerFetcher struct {
apiKey string
baseURL string
httpClient *http.Client
}
func NewFixerFetcher(apiKey string, baseURL string) *FixerFetcher {
return &FixerFetcher{
apiKey: apiKey,
baseURL: baseURL,
httpClient: &http.Client{Timeout: 10 * time.Second},
}
}
type fixerResponse struct {
Success bool `json:"success"`
Base string `json:"base"`
Date string `json:"date"`
Rates map[string]float64 `json:"rates"`
}
func (f *FixerFetcher) FetchLatestRates(ctx context.Context, baseCurrency domain.IntCurrency) (map[domain.IntCurrency]float64, error) {
url := fmt.Sprintf("%s/latest?base=%s", f.baseURL, baseCurrency)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("apikey", f.apiKey)
resp, err := f.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to fetch rates: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
var result fixerResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
if !result.Success {
return nil, fmt.Errorf("api returned unsuccessful response")
}
rates := make(map[domain.IntCurrency]float64)
for currency, rate := range result.Rates {
rates[domain.IntCurrency(currency)] = rate
}
return rates, nil
}

View File

@ -1,125 +0,0 @@
package currency
import (
"Yimaru-Backend/internal/domain"
"Yimaru-Backend/internal/repository"
"context"
"fmt"
"time"
)
type Service struct {
repo repository.CurrencyRepository
baseCurrency domain.IntCurrency
fixerFetcher *FixerFetcher
}
func NewService(repo repository.CurrencyRepository, baseCurrency domain.IntCurrency, fixerFetcher *FixerFetcher) *Service {
return &Service{repo: repo}
}
func (s *Service) Convert(ctx context.Context, amount float64, from, to domain.IntCurrency) (float64, error) {
if from == to {
return amount, nil
}
rate, err := s.repo.GetExchangeRate(ctx, from, to)
if err != nil {
return 0, err
}
return rate.Convert(amount)
}
func (s *Service) GetSupportedCurrencies(ctx context.Context) ([]domain.IntCurrency, error) {
return s.repo.GetSupportedCurrencies(ctx)
}
func (s *Service) UpdateRates(ctx context.Context) error {
// Implement fetching from external API (e.g., Fixer, Open Exchange Rates)
rates := map[domain.IntCurrency]map[domain.IntCurrency]float64{
domain.ETB: {
domain.USD: 0.018,
domain.EUR: 0.016,
domain.GBP: 0.014,
},
// Add other currencies...
}
for from, toRates := range rates {
for to, rate := range toRates {
err := s.repo.StoreExchangeRate(ctx, domain.IntCurrencyRate{
From: from,
To: to,
Rate: rate,
ValidUntil: time.Now().Add(24 * time.Hour), // Refresh daily
})
if err != nil {
return err
}
}
}
return nil
}
func (s *Service) FetchAndStoreRates(ctx context.Context) error {
// s.logger.Info("Starting exchange rate update")
rates, err := s.fixerFetcher.FetchLatestRates(ctx, s.baseCurrency)
if err != nil {
// s.logger.Error("Failed to fetch rates", "error", err)
return fmt.Errorf("failed to fetch rates: %w", err)
}
// Convert to integer rates with precision
const precision = 6 // 1.000000
for currency, rate := range rates {
if currency == s.baseCurrency {
continue
}
intRate := domain.IntCurrencyRate{
From: s.baseCurrency,
To: currency,
Rate: rate * float64(pow10(precision)),
ValidUntil: time.Now().Add(24 * time.Hour), // Rates valid for 24 hours
}
if err := s.repo.StoreExchangeRate(ctx, intRate); err != nil {
// s.logger.Error("Failed to store rate",
// "from", s.baseCurrency,
// "to", currency,
// "error", err)
continue // Try to store other rates even if one fails
}
// Also store the inverse rate
inverseRate := domain.IntCurrencyRate{
From: currency,
To: s.baseCurrency,
Rate: (1 / rate) * float64(pow10(precision)),
ValidUntil: time.Now().Add(24 * time.Hour),
}
if err := s.repo.StoreExchangeRate(ctx, inverseRate); err != nil {
// s.logger.Error("Failed to store inverse rate",
// "from", currency,
// "to", s.baseCurrency,
// "error", err)
return fmt.Errorf("Error storing exchange rates")
}
}
// s.logger.Info("Exchange rates updated successfully")
return nil
}
func pow10(n int) int64 {
result := int64(1)
for i := 0; i < n; i++ {
result *= 10
}
return result
}

View File

@ -1,55 +0,0 @@
package currency
import (
"Yimaru-Backend/internal/config"
"context"
"fmt"
"log/slog"
"time"
"github.com/go-co-op/gocron"
)
type ExchangeRateWorker struct {
fetcherService *FixerFetcher
scheduler *gocron.Scheduler
logger *slog.Logger
cfg *config.Config
}
func NewExchangeRateWorker(
fetcherService *FixerFetcher, logger *slog.Logger, cfg *config.Config,
) *ExchangeRateWorker {
return &ExchangeRateWorker{
fetcherService: fetcherService,
scheduler: gocron.NewScheduler(time.UTC),
logger: logger,
cfg: cfg,
}
}
func (w *ExchangeRateWorker) Start(ctx context.Context) {
_, err := w.scheduler.Every(6).Hours().Do(w.RunUpdate)
if err != nil {
return
}
// Run immediately on startup
go w.RunUpdate()
w.scheduler.StartAsync()
}
func (w *ExchangeRateWorker) RunUpdate() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if _, err := w.fetcherService.FetchLatestRates(ctx, w.cfg.BASE_CURRENCY); err != nil {
fmt.Println("Exchange rate update failed", "error", err)
}
}
func (w *ExchangeRateWorker) Stop() {
w.scheduler.Stop()
w.logger.Info("Exchange rate worker stopped")
}

View File

@ -0,0 +1,92 @@
package issuereporting
import (
"context"
"errors"
dbgen "Yimaru-Backend/gen/db"
"Yimaru-Backend/internal/domain"
"Yimaru-Backend/internal/repository"
)
type Service struct {
repo repository.ReportedIssueRepository
}
func New(repo repository.ReportedIssueRepository) *Service {
return &Service{repo: repo}
}
func (s *Service) CreateReportedIssue(ctx context.Context, issue domain.ReportedIssueReq, userID int64, role domain.Role) (domain.ReportedIssue, error) {
// metadata, err := json.Marshal(issue.Metadata)
// if err != nil {
// return domain.ReportedIssue{}, err
// }
params := dbgen.CreateReportedIssueParams{
UserID: userID,
UserRole: string(role),
Subject: issue.Subject,
Description: issue.Description,
IssueType: string(issue.IssueType),
// Metadata: metadata,
}
dbIssue, err := s.repo.CreateReportedIssue(ctx, params)
if err != nil {
return domain.ReportedIssue{}, err
}
// Map dbgen.ReportedIssue to domain.ReportedIssue
reportedIssue := domain.ReportedIssue{
ID: dbIssue.ID,
Subject: dbIssue.Subject,
Description: dbIssue.Description,
UserID: dbIssue.UserID,
UserRole: domain.Role(dbIssue.UserRole),
Status: domain.ReportedIssueStatus(dbIssue.Status),
IssueType: domain.ReportedIssueType(dbIssue.IssueType),
CreatedAt: dbIssue.CreatedAt.Time,
UpdatedAt: dbIssue.UpdatedAt.Time,
// Add other fields as necessary
}
return reportedIssue, nil
}
func (s *Service) GetIssuesForUser(ctx context.Context, userID int64, limit, offset int) ([]domain.ReportedIssue, error) {
dbIssues, err := s.repo.ListReportedIssuesByUser(ctx, userID, int32(limit), int32(offset))
if err != nil {
return nil, err
}
reportedIssues := make([]domain.ReportedIssue, len(dbIssues))
for i, dbIssue := range dbIssues {
reportedIssues[i] = domain.ReportedIssue{
ID: dbIssue.ID,
Subject: dbIssue.Subject,
Description: dbIssue.Description,
UserID: dbIssue.UserID,
UserRole: domain.Role(dbIssue.UserRole),
Status: domain.ReportedIssueStatus(dbIssue.Status),
IssueType: domain.ReportedIssueType(dbIssue.IssueType),
CreatedAt: dbIssue.CreatedAt.Time,
UpdatedAt: dbIssue.UpdatedAt.Time,
// Add other fields as necessary
}
}
return reportedIssues, nil
}
func (s *Service) GetAllIssues(ctx context.Context, limit, offset int) ([]dbgen.ReportedIssue, error) {
return s.repo.ListReportedIssues(ctx, int32(limit), int32(offset))
}
func (s *Service) UpdateIssueStatus(ctx context.Context, issueID int64, status string) error {
validStatuses := map[string]bool{"pending": true, "in_progress": true, "resolved": true, "rejected": true}
if !validStatuses[status] {
return errors.New("invalid status")
}
return s.repo.UpdateReportedIssueStatus(ctx, issueID, status)
}
func (s *Service) DeleteIssue(ctx context.Context, issueID int64) error {
return s.repo.DeleteReportedIssue(ctx, issueID)
}

View File

@ -8,7 +8,7 @@ import (
func (s *Service) SendEmail(ctx context.Context, receiverEmail, message string, messageHTML string, subject string) error { func (s *Service) SendEmail(ctx context.Context, receiverEmail, message string, messageHTML string, subject string) error {
apiKey := s.config.ResendApiKey apiKey := s.config.ResendApiKey
client := resend.NewClient(apiKey) client := resend.NewClient(apiKey)
formattedSenderEmail := "FortuneBets <" + s.config.ResendSenderEmail + ">" formattedSenderEmail := "Y <" + s.config.ResendSenderEmail + ">"
params := &resend.SendEmailRequest{ params := &resend.SendEmailRequest{
From: formattedSenderEmail, From: formattedSenderEmail,
To: []string{receiverEmail}, To: []string{receiverEmail},

View File

@ -19,21 +19,21 @@ var (
func (s *Service) SendSMS(ctx context.Context, receiverPhone, message string, companyID domain.ValidInt64) error { func (s *Service) SendSMS(ctx context.Context, receiverPhone, message string, companyID domain.ValidInt64) error {
var settingsList domain.SettingList var settingsList domain.SettingList
var err error // var err error
if companyID.Valid { // if companyID.Valid {
settingsList, err = s.settingSvc.GetOverrideSettingsList(ctx, companyID.Value) // settingsList, err = s.settingSvc.GetOverrideSettingsList(ctx, companyID.Value)
if err != nil { // if err != nil {
// TODO: Send a log about the error // // TODO: Send a log about the error
return err // return err
} // }
} else { // } else {
settingsList, err = s.settingSvc.GetGlobalSettingList(ctx) // settingsList, err = s.settingSvc.GetGlobalSettingList(ctx)
if err != nil { // if err != nil {
// TODO: Send a log about the error // // TODO: Send a log about the error
return err // return err
} // }
} // }
switch settingsList.SMSProvider { switch settingsList.SMSProvider {
case domain.AfroMessage: case domain.AfroMessage:

View File

@ -60,7 +60,7 @@ func New(
go hub.Run() go hub.Run()
go svc.startWorker() go svc.startWorker()
go svc.startRetryWorker() // go svc.startRetryWorker()
// go svc.RunRedisSubscriber(context.Background()) // go svc.RunRedisSubscriber(context.Background())
// go svc.StartKafkaConsumer(context.Background()) // go svc.StartKafkaConsumer(context.Background())
@ -123,42 +123,42 @@ func (s *Service) SendNotification(ctx context.Context, notification *domain.Not
return nil return nil
} }
func (s *Service) MarkAsRead(ctx context.Context, notificationIDs []string, recipientID int64) error { // func (s *Service) MarkAsRead(ctx context.Context, notificationIDs []string, recipientID int64) error {
for _, notificationID := range notificationIDs { // for _, notificationID := range notificationIDs {
_, err := s.store.UpdateNotificationStatus(ctx, notificationID, string(domain.DeliveryStatusSent), true, nil) // _, err := s.store.UpdateNotificationStatus(ctx, notificationID, string(domain.DeliveryStatusSent), true, nil)
if err != nil {
s.mongoLogger.Error("[NotificationSvc.MarkAsRead] Failed to mark notification as read",
zap.String("notificationID", notificationID),
zap.Int64("recipientID", recipientID),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return err
}
// count, err := s.store.CountUnreadNotifications(ctx, recipientID)
// if err != nil { // if err != nil {
// s.logger.Error("[NotificationSvc.MarkAsRead] Failed to count unread notifications", "recipientID", recipientID, "error", err) // s.mongoLogger.Error("[NotificationSvc.MarkAsRead] Failed to mark notification as read",
// zap.String("notificationID", notificationID),
// zap.Int64("recipientID", recipientID),
// zap.Error(err),
// zap.Time("timestamp", time.Now()),
// )
// return err // return err
// } // }
// s.Hub.Broadcast <- map[string]interface{}{ // // count, err := s.store.CountUnreadNotifications(ctx, recipientID)
// "type": "COUNT_NOT_OPENED_NOTIFICATION", // // if err != nil {
// "recipient_id": recipientID, // // s.logger.Error("[NotificationSvc.MarkAsRead] Failed to count unread notifications", "recipientID", recipientID, "error", err)
// "payload": map[string]int{ // // return err
// "not_opened_notifications_count": int(count), // // }
// },
// // s.Hub.Broadcast <- map[string]interface{}{
// // "type": "COUNT_NOT_OPENED_NOTIFICATION",
// // "recipient_id": recipientID,
// // "payload": map[string]int{
// // "not_opened_notifications_count": int(count),
// // },
// // }
// s.mongoLogger.Info("[NotificationSvc.MarkAsRead] Notification marked as read",
// zap.String("notificationID", notificationID),
// zap.Int64("recipientID", recipientID),
// zap.Time("timestamp", time.Now()),
// )
// } // }
s.mongoLogger.Info("[NotificationSvc.MarkAsRead] Notification marked as read", // return nil
zap.String("notificationID", notificationID), // }
zap.Int64("recipientID", recipientID),
zap.Time("timestamp", time.Now()),
)
}
return nil
}
func (s *Service) GetUserNotifications(ctx context.Context, recipientID int64, limit, offset int) ([]domain.Notification, int64, error) { func (s *Service) GetUserNotifications(ctx context.Context, recipientID int64, limit, offset int) ([]domain.Notification, int64, error) {
notifications, total, err := s.store.GetUserNotifications(ctx, recipientID, limit, offset) notifications, total, err := s.store.GetUserNotifications(ctx, recipientID, limit, offset)
@ -277,9 +277,9 @@ func (s *Service) startWorker() {
} }
} }
func (s *Service) ListRecipientIDs(ctx context.Context, receiver domain.NotificationRecieverSide) ([]int64, error) { // func (s *Service) ListRecipientIDs(ctx context.Context, receiver domain.NotificationRecieverSide) ([]int64, error) {
return s.store.ListRecipientIDs(ctx, receiver) // return s.store.ListRecipientIDs(ctx, receiver)
} // }
func (s *Service) handleNotification(notification *domain.Notification) { func (s *Service) handleNotification(notification *domain.Notification) {
ctx := context.Background() ctx := context.Background()
@ -310,13 +310,13 @@ func (s *Service) handleNotification(notification *domain.Notification) {
} }
} }
if _, err := s.store.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil { // if _, err := s.store.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil {
s.mongoLogger.Error("[NotificationSvc.HandleNotification] Failed to update notification status", // s.mongoLogger.Error("[NotificationSvc.HandleNotification] Failed to update notification status",
zap.String("id", notification.ID), // zap.String("id", notification.ID),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
} // }
} }
func (s *Service) SendNotificationSMS(ctx context.Context, recipientID int64, message string) error { func (s *Service) SendNotificationSMS(ctx context.Context, recipientID int64, message string) error {
@ -381,94 +381,94 @@ func (s *Service) SendNotificationEmail(ctx context.Context, recipientID int64,
return nil return nil
} }
func (s *Service) startRetryWorker() { // func (s *Service) startRetryWorker() {
ticker := time.NewTicker(1 * time.Minute) // ticker := time.NewTicker(1 * time.Minute)
defer ticker.Stop() // defer ticker.Stop()
for { // for {
select { // select {
case <-ticker.C: // case <-ticker.C:
s.retryFailedNotifications() // s.retryFailedNotifications()
case <-s.stopCh: // case <-s.stopCh:
s.mongoLogger.Info("[NotificationSvc.StartRetryWorker] Retry worker stopped", // s.mongoLogger.Info("[NotificationSvc.StartRetryWorker] Retry worker stopped",
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return // return
} // }
} // }
} // }
func (s *Service) retryFailedNotifications() { // func (s *Service) retryFailedNotifications() {
ctx := context.Background() // ctx := context.Background()
failedNotifications, err := s.store.ListFailedNotifications(ctx, 100) // failedNotifications, err := s.store.ListFailedNotifications(ctx, 100)
if err != nil { // if err != nil {
s.mongoLogger.Error("[NotificationSvc.RetryFailedNotifications] Failed to list failed notifications", // s.mongoLogger.Error("[NotificationSvc.RetryFailedNotifications] Failed to list failed notifications",
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return // return
} // }
for _, n := range failedNotifications { // for _, n := range failedNotifications {
notification := &n // notification := &n
go func(notification *domain.Notification) { // go func(notification *domain.Notification) {
for attempt := 0; attempt < 3; attempt++ { // for attempt := 0; attempt < 3; attempt++ {
time.Sleep(time.Duration(attempt) * time.Second) // time.Sleep(time.Duration(attempt) * time.Second)
switch notification.DeliveryChannel { // switch notification.DeliveryChannel {
case domain.DeliveryChannelSMS: // case domain.DeliveryChannelSMS:
if err := s.SendNotificationSMS(ctx, notification.RecipientID, notification.Payload.Message); err == nil { // if err := s.SendNotificationSMS(ctx, notification.RecipientID, notification.Payload.Message); err == nil {
notification.DeliveryStatus = domain.DeliveryStatusSent // notification.DeliveryStatus = domain.DeliveryStatusSent
if _, err := s.store.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil { // if _, err := s.store.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil {
s.mongoLogger.Error("[NotificationSvc.RetryFailedNotifications] Failed to update after retry", // s.mongoLogger.Error("[NotificationSvc.RetryFailedNotifications] Failed to update after retry",
zap.String("id", notification.ID), // zap.String("id", notification.ID),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
} else { // } else {
s.mongoLogger.Info("[NotificationSvc.RetryFailedNotifications] Successfully retried notification", // s.mongoLogger.Info("[NotificationSvc.RetryFailedNotifications] Successfully retried notification",
zap.String("id", notification.ID), // zap.String("id", notification.ID),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
} // }
return // return
} // }
case domain.DeliveryChannelEmail: // case domain.DeliveryChannelEmail:
if err := s.SendNotificationEmail(ctx, notification.RecipientID, notification.Payload.Message, notification.Payload.Headline); err == nil { // if err := s.SendNotificationEmail(ctx, notification.RecipientID, notification.Payload.Message, notification.Payload.Headline); err == nil {
notification.DeliveryStatus = domain.DeliveryStatusSent // notification.DeliveryStatus = domain.DeliveryStatusSent
if _, err := s.store.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil { // if _, err := s.store.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil {
s.mongoLogger.Error("[NotificationSvc.RetryFailedNotifications] Failed to update after retry", // s.mongoLogger.Error("[NotificationSvc.RetryFailedNotifications] Failed to update after retry",
zap.String("id", notification.ID), // zap.String("id", notification.ID),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
} else { // } else {
s.mongoLogger.Info("[NotificationSvc.RetryFailedNotifications] Successfully retried notification", // s.mongoLogger.Info("[NotificationSvc.RetryFailedNotifications] Successfully retried notification",
zap.String("id", notification.ID), // zap.String("id", notification.ID),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
} // }
return // return
} // }
} // }
} // }
s.mongoLogger.Error("[NotificationSvc.RetryFailedNotifications] Max retries reached for notification", // s.mongoLogger.Error("[NotificationSvc.RetryFailedNotifications] Max retries reached for notification",
zap.String("id", notification.ID), // zap.String("id", notification.ID),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
}(notification) // }(notification)
} // }
} // }
func (s *Service) CountUnreadNotifications(ctx context.Context, recipient_id int64) (int64, error) { func (s *Service) CountUnreadNotifications(ctx context.Context, recipient_id int64) (int64, error) {
return s.store.CountUnreadNotifications(ctx, recipient_id) return s.store.CountUnreadNotifications(ctx, recipient_id)
} }
func (s *Service) DeleteOldNotifications(ctx context.Context) error { // func (s *Service) DeleteOldNotifications(ctx context.Context) error {
return s.store.DeleteOldNotifications(ctx) // return s.store.DeleteOldNotifications(ctx)
} // }
// func (s *Service) GetNotificationCounts(ctx context.Context, filter domain.ReportFilter) (total, read, unread int64, err error){ // func (s *Service) GetNotificationCounts(ctx context.Context, filter domain.ReportFilter) (total, read, unread int64, err error){
// return s.store.Get(ctx, filter) // return s.store.Get(ctx, filter)

View File

@ -1,16 +0,0 @@
package referralservice
// import (
// "context"
// )
// type ReferralStore interface {
// GenerateReferralCode() (string, error)
// CreateReferral(ctx context.Context, userID int64, companyID int64) error
// ProcessReferral(ctx context.Context, referredPhone, referralCode string, companyID int64) error
// ProcessDepositBonus(ctx context.Context, userPhone string, amount float64) error
// ProcessBetReferral(ctx context.Context, userId int64, betAmount float64) error
// GetReferralStats(ctx context.Context, userID int64, companyID int64) (*domain.ReferralStats, error)
// GetReferralCountByID(ctx context.Context, referrerID int64) (int64, error)
// }

View File

@ -1,260 +0,0 @@
package referralservice
import (
"Yimaru-Backend/internal/config"
"Yimaru-Backend/internal/domain"
"Yimaru-Backend/internal/ports"
"Yimaru-Backend/internal/services/settings"
"context"
"crypto/rand"
"encoding/base32"
"errors"
"log/slog"
"go.uber.org/zap"
)
type Service struct {
repo ports.ReferralStore
settingSvc settings.Service
config *config.Config
logger *slog.Logger
mongoLogger *zap.Logger
}
func New(
repo ports.ReferralStore,
settingSvc settings.Service,
cfg *config.Config,
logger *slog.Logger,
mongoLogger *zap.Logger,
) *Service {
return &Service{
repo: repo,
settingSvc: settingSvc,
config: cfg,
logger: logger,
mongoLogger: mongoLogger,
}
}
var (
ErrInvalidReferral = errors.New("invalid or expired referral")
ErrUserNotFound = errors.New("user not found")
ErrNoReferralFound = errors.New("no referral found for this user")
ErrUserAlreadyHasReferralCode = errors.New("user already has an active referral code")
ErrMaxReferralCountLimitReached = errors.New("referral count limit has been reached")
)
func (s *Service) GenerateReferralCode() (string, error) {
b := make([]byte, 8)
if _, err := rand.Read(b); err != nil {
s.mongoLogger.Error("Failed to generate random bytes for referral code", zap.Error(err))
return "", err
}
code := base32.StdEncoding.EncodeToString(b)[:10]
s.mongoLogger.Debug("Generated referral code", zap.String("code", code))
return code, nil
}
func (s *Service) CreateReferralCode(ctx context.Context, userID int64, companyID int64) (domain.ReferralCode, error) {
settingsList, err := s.settingSvc.GetOverrideSettingsList(ctx, companyID)
if err != nil {
s.mongoLogger.Error("Failed to fetch settings", zap.Error(err))
return domain.ReferralCode{}, err
}
// check if user already has an active referral code
referralCodes, err := s.repo.GetReferralCodesByUser(ctx, userID)
if err != nil {
s.mongoLogger.Error("Failed to check if user already has active referral code", zap.Int64("userID", userID), zap.Error(err))
return domain.ReferralCode{}, err
}
if len(referralCodes) != 0 {
s.mongoLogger.Error("user already has an active referral code", zap.Int64("userID", userID), zap.Any("codes", referralCodes), zap.Error(err))
return domain.ReferralCode{}, ErrUserAlreadyHasReferralCode
}
code, err := s.GenerateReferralCode()
if err != nil {
return domain.ReferralCode{}, err
}
newReferralCode, err := s.repo.CreateReferralCode(ctx, domain.CreateReferralCode{
ReferrerID: userID,
ReferralCode: code,
CompanyID: companyID,
NumberOfReferrals: settingsList.DefaultMaxReferrals,
RewardAmount: settingsList.ReferralRewardAmount,
})
if err != nil {
return domain.ReferralCode{}, err
}
return newReferralCode, nil
}
func (s *Service) ProcessReferral(ctx context.Context, referredID int64, referralCode string, companyID int64) error {
paramLogger := s.mongoLogger.With(
zap.Int64("referredID", referredID),
zap.String("referralCode", referralCode),
zap.Int64("companyID", companyID),
)
referral, err := s.repo.GetReferralCode(ctx, referralCode)
if err != nil {
paramLogger.Error("Failed to get referral by code", zap.Error(err))
return err
}
// wallets, err := s.walletSvc.GetCustomerWallet(ctx, referral.ReferrerID)
// if err != nil {
// paramLogger.Error("Failed to get referrer wallets", zap.Error(err))
// return err
// }
// _, err = s.walletSvc.AddToWallet(ctx, wallets.StaticID,
// referral.RewardAmount, domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{},
// fmt.Sprintf("Added %v to static wallet due to %v referral code being used", referral.RewardAmount, referral.ReferralCode),
// )
// if err != nil {
// paramLogger.Error("Failed to add referral reward to static wallet", zap.Int64("static_wallet_id", wallets.StaticID), zap.Error(err))
// return err
// }
_, err = s.repo.CreateUserReferral(ctx, domain.CreateUserReferrals{
ReferredID: referredID,
ReferralCodeID: referral.ID,
})
if err != nil {
paramLogger.Error("Failed to create user referral", zap.Error(err))
return err
}
paramLogger.Info("Referral processed successfully", zap.String("rewardAmount", referral.ReferralCode))
return nil
}
func (s *Service) GetReferralStats(ctx context.Context, userID int64, companyID int64) (domain.ReferralStats, error) {
paramLogger := s.mongoLogger.With(zap.Int64("userID", userID), zap.Int64("companyID", companyID))
stats, err := s.repo.GetReferralStats(ctx, userID, companyID)
if err != nil {
paramLogger.Error("Failed to get referral stats", zap.Error(err))
return domain.ReferralStats{}, err
}
return stats, nil
}
func (s *Service) GetUserReferralCount(ctx context.Context, referrerID int64) (int64, error) {
count, err := s.repo.GetUserReferralCount(ctx, referrerID)
if err != nil {
s.mongoLogger.Error("Failed to get referral count", zap.Int64("referrerID", referrerID), zap.Error(err))
return 0, err
}
return count, nil
}
func (s *Service) GetReferralCodesByUser(ctx context.Context, userID int64) ([]domain.ReferralCode, error) {
return s.repo.GetReferralCodesByUser(ctx, userID)
}
func (s *Service) GetReferralCode(ctx context.Context, code string) (domain.ReferralCode, error) {
return s.repo.GetReferralCode(ctx, code)
}
func (s *Service) UpdateReferralCode(ctx context.Context, referral domain.UpdateReferralCode) error {
return s.repo.UpdateReferralCode(ctx, referral)
}
func (s *Service) GetUserReferral(ctx context.Context, referrerID int64) (domain.UserReferral, error) {
return s.repo.GetUserReferral(ctx, referrerID)
}
func (s *Service) GetUserReferralsByCode(ctx context.Context, code string) ([]domain.UserReferral, error) {
return s.repo.GetUserReferralsByCode(ctx, code)
}
// func (s *Service) ProcessDepositBonus(ctx context.Context, userID int64, amount float32, companyID int64) error {
// settingsList, err := s.settingSvc.GetOverrideSettingsList(ctx, companyID)
// if err != nil {
// s.logger.Error("Failed to fetch settings", "error", err)
// return err
// }
// s.logger.Info("Processing deposit bonus", "amount", amount)
// customerWallet, err := s.walletSvc.GetCustomerWallet(ctx, userID)
// if err != nil {
// s.logger.Error("Failed to get wallets for user", "userID", userID, "error", err)
// return err
// }
// bonus := amount * settingsList.CashbackPercentage
// _, err = s.walletSvc.AddToWallet(ctx, customerWallet.StaticID, domain.ToCurrency(bonus), domain.ValidInt64{},
// domain.TRANSFER_DIRECT, domain.PaymentDetails{},
// fmt.Sprintf("Added to bonus wallet because of Deposit Cashback Bonus %d", bonus))
// if err != nil {
// s.logger.Error("Failed to add deposit bonus to wallet", "staticWalletID", customerWallet.StaticID, "userID", userID, "bonus", bonus, "error", err)
// return err
// }
// s.logger.Info("Deposit bonus processed successfully", "bonus", bonus)
// return nil
// }
// func (s *Service) ProcessBetReferral(ctx context.Context, userId int64, betAmount float64) error {
// s.logger.Info("Processing bet referral", "userID", userId, "betAmount", betAmount)
// settings, err := s.repo.GetSettings(ctx)
// if err != nil {
// s.logger.Error("Failed to get referral settings", "error", err)
// return err
// }
// referral, err := s.repo.GetReferralByReferredID(ctx, userId)
// if err != nil {
// s.logger.Error("Failed to get referral by referred ID", "userId", userId, "error", err)
// return err
// }
// if referral == nil || referral.Status != domain.ReferralCompleted {
// s.logger.Warn("No valid referral found", "userId", userId, "status", referral.Status)
// return ErrNoReferralFound
// }
// wallets, err := s.walletSvc.GetWalletsByUser(ctx, referral.ReferrerID)
// if err != nil {
// s.logger.Error("Failed to get wallets for referrer", "referrerID", referral.ReferrerID, "error", err)
// return err
// }
// if len(wallets) == 0 {
// s.logger.Error("Referrer has no wallet", "referrerID", referral.ReferrerID)
// return errors.New("referrer has no wallet")
// }
// bonusPercentage := settings.BetReferralBonusPercentage
// if bonusPercentage == 0 {
// bonusPercentage = 5.0
// s.logger.Debug("Using default bet referral bonus percentage", "percentage", bonusPercentage)
// }
// bonus := betAmount * (bonusPercentage / 100)
// walletID := wallets[0].ID
// currentBalance := float64(wallets[0].Balance)
// _, err = s.walletSvc.AddToWallet(ctx, walletID, domain.ToCurrency(float32(currentBalance+bonus)), domain.ValidInt64{},
// domain.TRANSFER_DIRECT, domain.PaymentDetails{},
// fmt.Sprintf("Added %v to static wallet because of bet referral", referral.RewardAmount))
// if err != nil {
// s.logger.Error("Failed to add bet referral bonus to wallet", "walletID", walletID, "referrerID", referral.ReferrerID, "bonus", bonus, "error", err)
// return err
// }
// s.logger.Info("Bet referral processed successfully", "referrer ID", referral.ReferrerID, "referrerID", referral.ReferrerID, "bonus", bonus)
// return nil
// }

View File

@ -35,29 +35,29 @@ func (s *Service) UpdateGlobalSettingList(ctx context.Context, settingList domai
return s.settingStore.UpdateGlobalSettingList(ctx, settingList) return s.settingStore.UpdateGlobalSettingList(ctx, settingList)
} }
func (s *Service) InsertCompanySetting(ctx context.Context, key, value string, companyID int64) error { // func (s *Service) InsertCompanySetting(ctx context.Context, key, value string, companyID int64) error {
return s.settingStore.InsertCompanySetting(ctx, key, value, companyID) // return s.settingStore.InsertCompanySetting(ctx, key, value, companyID)
} // }
func (s *Service) InsertCompanySettingList(ctx context.Context, settingList domain.ValidSettingList, companyID int64) error { // func (s *Service) InsertCompanySettingList(ctx context.Context, settingList domain.ValidSettingList, companyID int64) error {
return s.settingStore.InsertCompanySettingList(ctx, settingList, companyID) // return s.settingStore.InsertCompanySettingList(ctx, settingList, companyID)
} // }
func (s *Service) GetAllCompanySettings(ctx context.Context) ([]domain.CompanySetting, error) { // func (s *Service) GetAllCompanySettings(ctx context.Context) ([]domain.CompanySetting, error) {
return s.settingStore.GetAllCompanySettings(ctx) // return s.settingStore.GetAllCompanySettings(ctx)
} // }
func (s *Service) GetCompanySettingsByKey(ctx context.Context, key string) ([]domain.CompanySetting, error) { // func (s *Service) GetCompanySettingsByKey(ctx context.Context, key string) ([]domain.CompanySetting, error) {
return s.settingStore.GetCompanySettingsByKey(ctx, key) // return s.settingStore.GetCompanySettingsByKey(ctx, key)
} // }
func (s *Service) GetOverrideSettings(ctx context.Context, companyID int64) ([]domain.Setting, error) { // func (s *Service) GetOverrideSettings(ctx context.Context, companyID int64) ([]domain.Setting, error) {
return s.settingStore.GetOverrideSettings(ctx, companyID) // return s.settingStore.GetOverrideSettings(ctx, companyID)
} // }
func (s *Service) GetOverrideSettingsList(ctx context.Context, companyID int64) (domain.SettingList, error) { // func (s *Service) GetOverrideSettingsList(ctx context.Context, companyID int64) (domain.SettingList, error) {
return s.settingStore.GetOverrideSettingsList(ctx, companyID) // return s.settingStore.GetOverrideSettingsList(ctx, companyID)
} // }
func (s *Service) DeleteCompanySetting(ctx context.Context, companyID int64, key string) error { // func (s *Service) DeleteCompanySetting(ctx context.Context, companyID int64, key string) error {
return s.settingStore.DeleteCompanySetting(ctx, companyID, key) // return s.settingStore.DeleteCompanySetting(ctx, companyID, key)
} // }
func (s *Service) DeleteAllCompanySetting(ctx context.Context, companyID int64) error { // func (s *Service) DeleteAllCompanySetting(ctx context.Context, companyID int64) error {
return s.settingStore.DeleteAllCompanySetting(ctx, companyID) // return s.settingStore.DeleteAllCompanySetting(ctx, companyID)
} // }

View File

@ -134,39 +134,41 @@ func (s *Service) GetBranchByRole(ctx context.Context, branchID *int64, role dom
// var companyID int64 // var companyID int64
switch role { switch role {
case domain.RoleAdmin, domain.RoleBranchManager, domain.RoleSuperAdmin: case domain.RoleAdmin, domain.RoleSuperAdmin:
if branchID == nil { if branchID == nil {
// h.logger.Error("CashoutReq Branch ID is required for this user role") // h.logger.Error("CashoutReq Branch ID is required for this user role")
return nil, nil, ErrBranchRequiredForRole return nil, nil, ErrBranchRequiredForRole
} }
branch, err := s.branchSvc.GetBranchByID(ctx, *branchID) // branch, err := s.branchSvc.GetBranchByID(ctx, *branchID)
if err != nil { // if err != nil {
// h.logger.Error("CashoutReq no branches") // // h.logger.Error("CashoutReq no branches")
return nil, nil, err // return nil, nil, err
} // }
// Check if the user has access to the company // Check if the user has access to the company
if role != domain.RoleSuperAdmin { if role != domain.RoleSuperAdmin {
if userCompanyID.Valid && userCompanyID.Value != branch.CompanyID { // if userCompanyID.Valid && userCompanyID.Value != branch.CompanyID {
return nil, nil, ErrUnauthorizedCompanyID // return nil, nil, ErrUnauthorizedCompanyID
} // }
} }
if role == domain.RoleBranchManager { // if role == domain.RoleBranchManager {
if branch.BranchManagerID != userID { // // if branch.BranchManagerID != userID {
return nil, nil, ErrUnauthorizedBranchManager // // return nil, nil, ErrUnauthorizedBranchManager
} // // }
} // }
return &branch.ID, &branch.CompanyID, nil // return &branch.ID, &branch.CompanyID, nil
case domain.RoleCashier: case domain.RoleInstructor:
branch, err := s.branchSvc.GetBranchByCashier(ctx, userID) // branch, err := s.branchSvc.GetBranchByCashier(ctx, userID)
if err != nil { // if err != nil {
// h.logger.Error("CashoutReq failed, branch id invalid") // // h.logger.Error("CashoutReq failed, branch id invalid")
return nil, nil, ErrInvalidBranchID // return nil, nil, ErrInvalidBranchID
} // }
return &branch.ID, &branch.CompanyID, nil // return &branch.ID, &branch.CompanyID, nil
default: default:
return nil, nil, ErrCustomerRoleNotAuthorized return nil, nil, ErrCustomerRoleNotAuthorized
} }
return nil, nil, ErrCustomerRoleNotAuthorized
} }

View File

@ -13,7 +13,7 @@ import (
func (s *Service) SendOtp(ctx context.Context, sentTo string, otpFor domain.OtpFor, medium domain.OtpMedium, provider domain.SMSProvider) error { func (s *Service) SendOtp(ctx context.Context, sentTo string, otpFor domain.OtpFor, medium domain.OtpMedium, provider domain.SMSProvider) error {
otpCode := helpers.GenerateOTP() otpCode := helpers.GenerateOTP()
message := fmt.Sprintf("Welcome to Fortune bets, your OTP is %s please don't share with anyone.", otpCode) message := fmt.Sprintf("Welcome to Yimaru Online Learning Platform, your OTP is %s please don't share with anyone.", otpCode)
switch medium { switch medium {
case domain.OtpMediumSms: case domain.OtpMediumSms:
@ -31,7 +31,7 @@ func (s *Service) SendOtp(ctx context.Context, sentTo string, otpFor domain.OtpF
return fmt.Errorf("invalid sms provider: %s", provider) return fmt.Errorf("invalid sms provider: %s", provider)
} }
case domain.OtpMediumEmail: case domain.OtpMediumEmail:
if err := s.messengerSvc.SendEmail(ctx, sentTo, message, message, "FortuneBets - One Time Password"); err != nil { if err := s.messengerSvc.SendEmail(ctx, sentTo, message, message, "Yimaru - One Time Password"); err != nil {
return err return err
} }
} }

View File

@ -34,8 +34,8 @@ func (s *Service) CreateUser(ctx context.Context, User domain.CreateUserReq, is_
EmailVerified: true, EmailVerified: true,
PhoneVerified: true, PhoneVerified: true,
Suspended: User.Suspended, Suspended: User.Suspended,
CompanyID: User.CompanyID, OrganizationID: User.OrganizationID,
}, is_company) })
} }
func (s *Service) DeleteUser(ctx context.Context, id int64) error { func (s *Service) DeleteUser(ctx context.Context, id int64) error {
@ -45,24 +45,9 @@ func (s *Service) DeleteUser(ctx context.Context, id int64) error {
func (s *Service) GetAllUsers(ctx context.Context, filter domain.UserFilter) ([]domain.User, int64, error) { func (s *Service) GetAllUsers(ctx context.Context, filter domain.UserFilter) ([]domain.User, int64, error) {
// Get all Users // Get all Users
return s.userStore.GetAllUsers(ctx, filter) return s.userStore.GetAllUsers(ctx, &filter.Role, &filter.OrganizationID.Value, &filter.Query.Value, &filter.CreatedBefore.Value, &filter.CreatedAfter.Value, int32(filter.PageSize.Value), int32(filter.Page.Value))
} }
func (s *Service) GetUserById(ctx context.Context, id int64) (domain.User, error) { func (s *Service) GetUserById(ctx context.Context, id int64) (domain.User, error) {
return s.userStore.GetUserByID(ctx, id) return s.userStore.GetUserByID(ctx, id)
} }
func (s *Service) GetCashiersByBranch(ctx context.Context, branchID int64) ([]domain.User, error) {
return s.userStore.GetCashiersByBranch(ctx, branchID)
}
func (s *Service) GetAdminByCompanyID(ctx context.Context, companyID int64) (domain.User, error) {
return s.userStore.GetAdminByCompanyID(ctx, companyID)
}
func (s *Service) GetAllCashiers(ctx context.Context, filter domain.UserFilter) ([]domain.GetCashier, int64, error) {
return s.userStore.GetAllCashiers(ctx, filter)
}
func (s *Service) GetCashierByID(ctx context.Context, cashierID int64) (domain.GetCashier, error) {
return s.userStore.GetCashierByID(ctx, cashierID)
}

View File

@ -9,14 +9,15 @@ import (
func (s *Service) CheckPhoneEmailExist(ctx context.Context, phoneNum, email string, companyID domain.ValidInt64) (bool, bool, error) { // email,phone,error func (s *Service) CheckPhoneEmailExist(ctx context.Context, phoneNum, email string, companyID domain.ValidInt64) (bool, bool, error) { // email,phone,error
return s.userStore.CheckPhoneEmailExist(ctx, phoneNum, email, companyID) return s.userStore.CheckPhoneEmailExist(ctx, phoneNum, email, companyID)
} }
func (s *Service) SendRegisterCode(ctx context.Context, medium domain.OtpMedium, sentTo string, provider domain.SMSProvider, companyID domain.ValidInt64) error { func (s *Service) SendRegisterCode(ctx context.Context, medium domain.OtpMedium, sentTo string, provider domain.SMSProvider, companyID domain.ValidInt64) error {
var err error var err error
// check if user exists // check if user exists
switch medium { switch medium {
case domain.OtpMediumEmail: case domain.OtpMediumEmail:
_, err = s.userStore.GetUserByEmail(ctx, sentTo, companyID) _, err = s.userStore.GetUserByEmailPhone(ctx, sentTo, "", companyID)
case domain.OtpMediumSms: case domain.OtpMediumSms:
_, err = s.userStore.GetUserByPhone(ctx, sentTo, companyID) _, err = s.userStore.GetUserByEmailPhone(ctx, "", sentTo, companyID)
} }
if err != nil && err != domain.ErrUserNotFound { if err != nil && err != domain.ErrUserNotFound {
@ -27,6 +28,7 @@ func (s *Service) SendRegisterCode(ctx context.Context, medium domain.OtpMedium,
// send otp based on the medium // send otp based on the medium
return s.SendOtp(ctx, sentTo, domain.OtpRegister, medium, provider) return s.SendOtp(ctx, sentTo, domain.OtpRegister, medium, provider)
} }
func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterUserReq) (domain.User, error) { // normal func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterUserReq) (domain.User, error) { // normal
// get otp // get otp
@ -64,13 +66,13 @@ func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterU
Email: registerReq.Email, Email: registerReq.Email,
PhoneNumber: registerReq.PhoneNumber, PhoneNumber: registerReq.PhoneNumber,
Password: hashedPassword, Password: hashedPassword,
Role: domain.RoleCustomer, Role: domain.RoleStudent,
EmailVerified: registerReq.OtpMedium == domain.OtpMediumEmail, EmailVerified: registerReq.OtpMedium == domain.OtpMediumEmail,
PhoneVerified: registerReq.OtpMedium == domain.OtpMediumSms, PhoneVerified: registerReq.OtpMedium == domain.OtpMediumSms,
CompanyID: registerReq.CompanyID, OrganizationID: registerReq.OrganizationID,
} }
// create the user and mark otp as used // create the user and mark otp as used
user, err := s.userStore.CreateUser(ctx, userR, otp.ID, false) user, err := s.userStore.CreateUser(ctx, userR, otp.ID)
if err != nil { if err != nil {
return domain.User{}, err return domain.User{}, err
} }

View File

@ -13,9 +13,9 @@ func (s *Service) SendResetCode(ctx context.Context, medium domain.OtpMedium, se
// check if user exists // check if user exists
switch medium { switch medium {
case domain.OtpMediumEmail: case domain.OtpMediumEmail:
_, err = s.userStore.GetUserByEmail(ctx, sentTo, companyID) _, err = s.userStore.GetUserByEmailPhone(ctx, sentTo, "", companyID)
case domain.OtpMediumSms: case domain.OtpMediumSms:
_, err = s.userStore.GetUserByPhone(ctx, sentTo, companyID) _, err = s.userStore.GetUserByEmailPhone(ctx, "", sentTo, companyID)
} }
if err != nil { if err != nil {
@ -51,13 +51,13 @@ func (s *Service) ResetPassword(ctx context.Context, resetReq domain.ResetPasswo
return domain.ErrInvalidOtp return domain.ErrInvalidOtp
} }
// hash password // hash password
hashedPassword, err := hashPassword(resetReq.Password) // hashedPassword, err := hashPassword(resetReq.Password)
if err != nil { // if err != nil {
return err // return err
} // }
// reset pass and mark otp as used // reset pass and mark otp as used
err = s.userStore.UpdatePassword(ctx, sentTo, hashedPassword, otp.ID, resetReq.CompanyID) err = s.userStore.UpdatePassword(ctx, resetReq.Password, resetReq.Email, resetReq.PhoneNumber, resetReq.OrganizationID, time.Now())
if err != nil { if err != nil {
return err return err
} }

View File

@ -5,21 +5,34 @@ import (
"context" "context"
) )
func (s *Service) SearchUserByNameOrPhone(ctx context.Context, searchString string, role *domain.Role, companyID domain.ValidInt64) ([]domain.User, error) { func (s *Service) SearchUserByNameOrPhone(ctx context.Context, searchString string, role *int64, companyID *string) ([]domain.User, error) {
// Search user // Search user
return s.userStore.SearchUserByNameOrPhone(ctx, searchString, role, companyID) return s.userStore.SearchUserByNameOrPhone(ctx, searchString, role, companyID)
} }
func (s *Service) UpdateUser(ctx context.Context, user domain.UpdateUserReq) error { func (s *Service) UpdateUser(ctx context.Context, user domain.UpdateUserReq) error {
// update user // update user
return s.userStore.UpdateUser(ctx, user)
newUser := domain.User{
ID: user.UserID,
FirstName: user.FirstName.Value,
LastName: user.LastName.Value,
NickName: user.NickName.Value,
Age: user.Age.Value,
EducationLevel: user.EducationLevel.Value,
Country: user.Country.Value,
Region: user.Region.Value,
Suspended: user.Suspended.Value,
OrganizationID: user.OrganizationID,
}
return s.userStore.UpdateUser(ctx, newUser)
} }
func (s *Service) UpdateUserCompany(ctx context.Context, id int64, companyID int64) error { // func (s *Service) UpdateUserCompany(ctx context.Context, id int64, companyID int64) error {
// update user // // update user
return s.userStore.UpdateUserCompany(ctx, id, companyID) // return s.userStore.UpdateUserCompany(ctx, id, companyID)
} // }
func (s *Service) UpdateUserSuspend(ctx context.Context, id int64, status bool) error { func (s *Service) UpdateUserSuspend(ctx context.Context, id int64, status bool) error {
// update user // update user

View File

@ -4,9 +4,10 @@ import (
"Yimaru-Backend/internal/config" "Yimaru-Backend/internal/config"
"Yimaru-Backend/internal/services/arifpay" "Yimaru-Backend/internal/services/arifpay"
"Yimaru-Backend/internal/services/authentication" "Yimaru-Backend/internal/services/authentication"
issuereporting "Yimaru-Backend/internal/services/issue_reporting"
notificationservice "Yimaru-Backend/internal/services/notification" notificationservice "Yimaru-Backend/internal/services/notification"
"Yimaru-Backend/internal/services/recommendation" "Yimaru-Backend/internal/services/recommendation"
referralservice "Yimaru-Backend/internal/services/referal"
"Yimaru-Backend/internal/services/settings" "Yimaru-Backend/internal/services/settings"
"Yimaru-Backend/internal/services/transaction" "Yimaru-Backend/internal/services/transaction"
"Yimaru-Backend/internal/services/user" "Yimaru-Backend/internal/services/user"
@ -23,60 +24,36 @@ import (
) )
type App struct { type App struct {
// directDepositSvc *directdeposit.Service
// telebirrSvc *telebirr.TelebirrService
arifpaySvc *arifpay.ArifpayService arifpaySvc *arifpay.ArifpayService
// santimpaySvc *santimpay.SantimPayService issueReportingSvc *issuereporting.Service
// issueReportingSvc *issuereporting.Service
// instSvc *institutions.Service
// currSvc *currency.Service
fiber *fiber.App fiber *fiber.App
recommendationSvc recommendation.RecommendationService recommendationSvc recommendation.RecommendationService
cfg *config.Config cfg *config.Config
logger *slog.Logger logger *slog.Logger
NotidicationStore *notificationservice.Service NotidicationStore *notificationservice.Service
referralSvc *referralservice.Service
// raffleSvc *raffle.Service
// bonusSvc *bonus.Service
port int port int
settingSvc *settings.Service settingSvc *settings.Service
authSvc *authentication.Service authSvc *authentication.Service
userSvc *user.Service userSvc *user.Service
// chapaSvc *chapa.Service
transactionSvc *transaction.Service transactionSvc *transaction.Service
// branchSvc *branch.Service
// companySvc *company.Service
validator *customvalidator.CustomValidator validator *customvalidator.CustomValidator
JwtConfig jwtutil.JwtConfig JwtConfig jwtutil.JwtConfig
Logger *slog.Logger Logger *slog.Logger
// statSvc *stats.Service
mongoLoggerSvc *zap.Logger mongoLoggerSvc *zap.Logger
} }
func NewApp( func NewApp(
// directDepositSvc *directdeposit.Service,
// telebirrSvc *telebirr.TelebirrService,
arifpaySvc *arifpay.ArifpayService, arifpaySvc *arifpay.ArifpayService,
// santimpaySvc *santimpay.SantimPayService, issueReportingSvc *issuereporting.Service,
// issueReportingSvc *issuereporting.Service,
// instSvc *institutions.Service,
// currSvc *currency.Service,
port int, validator *customvalidator.CustomValidator, port int, validator *customvalidator.CustomValidator,
settingSvc *settings.Service, settingSvc *settings.Service,
authSvc *authentication.Service, authSvc *authentication.Service,
logger *slog.Logger, logger *slog.Logger,
JwtConfig jwtutil.JwtConfig, JwtConfig jwtutil.JwtConfig,
userSvc *user.Service, userSvc *user.Service,
// chapaSvc *chapa.Service,
transactionSvc *transaction.Service, transactionSvc *transaction.Service,
// branchSvc *branch.Service,
// companySvc *company.Service,
notidicationStore *notificationservice.Service, notidicationStore *notificationservice.Service,
referralSvc *referralservice.Service,
// raffleSvc *raffle.Service,
// bonusSvc *bonus.Service,
recommendationSvc recommendation.RecommendationService, recommendationSvc recommendation.RecommendationService,
// statSvc *stats.Service,
cfg *config.Config, cfg *config.Config,
mongoLoggerSvc *zap.Logger, mongoLoggerSvc *zap.Logger,
) *App { ) *App {
@ -97,13 +74,8 @@ func NewApp(
app.Static("/static", "./static") app.Static("/static", "./static")
s := &App{ s := &App{
// directDepositSvc: directDepositSvc,
// telebirrSvc: telebirrSvc,
arifpaySvc: arifpaySvc, arifpaySvc: arifpaySvc,
// santimpaySvc: santimpaySvc,
// issueReportingSvc: issueReportingSvc, // issueReportingSvc: issueReportingSvc,
// instSvc: instSvc,
// currSvc: currSvc,
fiber: app, fiber: app,
port: port, port: port,
settingSvc: settingSvc, settingSvc: settingSvc,
@ -112,18 +84,10 @@ func NewApp(
logger: logger, logger: logger,
JwtConfig: JwtConfig, JwtConfig: JwtConfig,
userSvc: userSvc, userSvc: userSvc,
// ticketSvc: ticketSvc,
// chapaSvc: chapaSvc,
transactionSvc: transactionSvc, transactionSvc: transactionSvc,
// branchSvc: branchSvc,
// companySvc: companySvc,
NotidicationStore: notidicationStore, NotidicationStore: notidicationStore,
referralSvc: referralSvc,
// raffleSvc: raffleSvc,
// bonusSvc: bonusSvc,
Logger: logger, Logger: logger,
recommendationSvc: recommendationSvc, recommendationSvc: recommendationSvc,
// statSvc: statSvc,
cfg: cfg, cfg: cfg,
mongoLoggerSvc: mongoLoggerSvc, mongoLoggerSvc: mongoLoggerSvc,
} }

View File

@ -18,7 +18,7 @@ type CreateAdminReq struct {
Email string `json:"email" example:"john.doe@example.com"` Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"` PhoneNumber string `json:"phone_number" example:"1234567890"`
Password string `json:"password" example:"password123"` Password string `json:"password" example:"password123"`
CompanyID *int64 `json:"company_id,omitempty" example:"1"` OrganizationID *int64 `json:"company_id,omitempty" example:"1"`
} }
// CreateAdmin godoc // CreateAdmin godoc
@ -34,7 +34,7 @@ type CreateAdminReq struct {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /api/v1/admin [post] // @Router /api/v1/admin [post]
func (h *Handler) CreateAdmin(c *fiber.Ctx) error { func (h *Handler) CreateAdmin(c *fiber.Ctx) error {
var companyID domain.ValidInt64 var OrganizationID domain.ValidInt64
var req CreateAdminReq var req CreateAdminReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
@ -60,24 +60,24 @@ func (h *Handler) CreateAdmin(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, errMsg) return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
if req.CompanyID == nil { if req.OrganizationID == nil {
companyID = domain.ValidInt64{ OrganizationID = domain.ValidInt64{
Value: 0, Value: 0,
Valid: false, Valid: false,
} }
} else { } else {
// _, err := h.companySvc.GetCompanyByID(c.Context(), *req.CompanyID) // _, err := h.companySvc.GetCompanyByID(c.Context(), *req.OrganizationID)
// if err != nil { // if err != nil {
// h.mongoLoggerSvc.Error("invalid company ID for CreateAdmin", // h.mongoLoggerSvc.Error("invalid company ID for CreateAdmin",
// zap.Int64("status_code", fiber.StatusInternalServerError), // zap.Int64("status_code", fiber.StatusInternalServerError),
// zap.Int64("company_id", *req.CompanyID), // zap.Int64("company_id", *req.OrganizationID),
// zap.Error(err), // zap.Error(err),
// zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
// ) // )
// return fiber.NewError(fiber.StatusInternalServerError, "Company ID is invalid:"+err.Error()) // return fiber.NewError(fiber.StatusInternalServerError, "Company ID is invalid:"+err.Error())
// } // }
companyID = domain.ValidInt64{ OrganizationID = domain.ValidInt64{
Value: *req.CompanyID, Value: *req.OrganizationID,
Valid: true, Valid: true,
} }
} }
@ -89,7 +89,7 @@ func (h *Handler) CreateAdmin(c *fiber.Ctx) error {
PhoneNumber: req.PhoneNumber, PhoneNumber: req.PhoneNumber,
Password: req.Password, Password: req.Password,
Role: string(domain.RoleAdmin), Role: string(domain.RoleAdmin),
CompanyID: companyID, OrganizationID: OrganizationID,
} }
newUser, err := h.userSvc.CreateUser(c.Context(), user, true) newUser, err := h.userSvc.CreateUser(c.Context(), user, true)
@ -103,9 +103,9 @@ func (h *Handler) CreateAdmin(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create admin:"+err.Error()) return fiber.NewError(fiber.StatusInternalServerError, "Failed to create admin:"+err.Error())
} }
// if req.CompanyID != nil { // if req.OrganizationID != nil {
// _, err := h.companySvc.UpdateCompany(c.Context(), domain.UpdateCompany{ // _, err := h.companySvc.UpdateCompany(c.Context(), domain.UpdateCompany{
// ID: *req.CompanyID, // ID: *req.OrganizationID,
// AdminID: domain.ValidInt64{ // AdminID: domain.ValidInt64{
// Value: newUser.ID, // Value: newUser.ID,
// Valid: true, // Valid: true,
@ -114,7 +114,7 @@ func (h *Handler) CreateAdmin(c *fiber.Ctx) error {
// if err != nil { // if err != nil {
// h.mongoLoggerSvc.Error("failed to update company with new admin", // h.mongoLoggerSvc.Error("failed to update company with new admin",
// zap.Int64("status_code", fiber.StatusInternalServerError), // zap.Int64("status_code", fiber.StatusInternalServerError),
// zap.Int64("company_id", *req.CompanyID), // zap.Int64("company_id", *req.OrganizationID),
// zap.Int64("admin_id", newUser.ID), // zap.Int64("admin_id", newUser.ID),
// zap.Error(err), // zap.Error(err),
// zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
@ -200,7 +200,7 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error {
companyFilter := int64(c.QueryInt("company_id")) companyFilter := int64(c.QueryInt("company_id"))
filter := domain.UserFilter{ filter := domain.UserFilter{
Role: string(domain.RoleAdmin), Role: string(domain.RoleAdmin),
CompanyID: domain.ValidInt64{ OrganizationID: domain.ValidInt64{
Value: companyFilter, Value: companyFilter,
Valid: companyFilter != 0, Valid: companyFilter != 0,
}, },
@ -365,7 +365,7 @@ type updateAdminReq struct {
FirstName string `json:"first_name" example:"John"` FirstName string `json:"first_name" example:"John"`
LastName string `json:"last_name" example:"Doe"` LastName string `json:"last_name" example:"Doe"`
Suspended bool `json:"suspended" example:"false"` Suspended bool `json:"suspended" example:"false"`
CompanyID *int64 `json:"company_id,omitempty" example:"1"` OrganizationID *int64 `json:"company_id,omitempty" example:"1"`
} }
// UpdateAdmin godoc // UpdateAdmin godoc
@ -417,16 +417,16 @@ func (h *Handler) UpdateAdmin(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "Invalid Admin ID") return fiber.NewError(fiber.StatusBadRequest, "Invalid Admin ID")
} }
var companyID domain.ValidInt64 var OrganizationID domain.ValidInt64
if req.CompanyID != nil { if req.OrganizationID != nil {
companyID = domain.ValidInt64{ OrganizationID = domain.ValidInt64{
Value: *req.CompanyID, Value: *req.OrganizationID,
Valid: true, Valid: true,
} }
} }
err = h.userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{ err = h.userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{
UserId: AdminID, UserID: AdminID,
FirstName: domain.ValidString{ FirstName: domain.ValidString{
Value: req.FirstName, Value: req.FirstName,
Valid: req.FirstName != "", Valid: req.FirstName != "",
@ -439,7 +439,7 @@ func (h *Handler) UpdateAdmin(c *fiber.Ctx) error {
Value: req.Suspended, Value: req.Suspended,
Valid: true, Valid: true,
}, },
CompanyID: companyID, OrganizationID: OrganizationID,
}) })
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("UpdateAdmin failed - user service error", h.mongoLoggerSvc.Error("UpdateAdmin failed - user service error",
@ -451,9 +451,9 @@ func (h *Handler) UpdateAdmin(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update admin:"+err.Error()) return fiber.NewError(fiber.StatusInternalServerError, "Failed to update admin:"+err.Error())
} }
// if req.CompanyID != nil { // if req.OrganizationID != nil {
// _, err := h.companySvc.UpdateCompany(c.Context(), domain.UpdateCompany{ // _, err := h.companySvc.UpdateCompany(c.Context(), domain.UpdateCompany{
// ID: *req.CompanyID, // ID: *req.OrganizationID,
// AdminID: domain.ValidInt64{ // AdminID: domain.ValidInt64{
// Value: AdminID, // Value: AdminID,
// Valid: true, // Valid: true,

View File

@ -40,8 +40,8 @@ type loginCustomerRes struct {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /api/v1/{tenant_slug}/customer-login [post] // @Router /api/v1/{tenant_slug}/customer-login [post]
func (h *Handler) LoginCustomer(c *fiber.Ctx) error { func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64) OrganizationID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid { if !OrganizationID.Valid {
h.BadRequestLogger().Error("invalid company id") h.BadRequestLogger().Error("invalid company id")
return fiber.NewError(fiber.StatusBadRequest, "invalid company id") return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
} }
@ -63,7 +63,7 @@ func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, errMsg) return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
successRes, err := h.authSvc.Login(c.Context(), req.Email, req.PhoneNumber, req.Password, companyID) successRes, err := h.authSvc.Login(c.Context(), req.Email, req.PhoneNumber, req.Password, OrganizationID)
if err != nil { if err != nil {
switch { switch {
@ -95,7 +95,7 @@ func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
} }
} }
if successRes.Role != domain.RoleCustomer { if successRes.Role != domain.RoleStudent {
h.mongoLoggerSvc.Info("Login attempt: customer login of other role", h.mongoLoggerSvc.Info("Login attempt: customer login of other role",
zap.Int("status_code", fiber.StatusForbidden), zap.Int("status_code", fiber.StatusForbidden),
zap.String("role", string(successRes.Role)), zap.String("role", string(successRes.Role)),
@ -167,8 +167,8 @@ type LoginAdminRes struct {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /api/v1/{tenant_slug}/admin-login [post] // @Router /api/v1/{tenant_slug}/admin-login [post]
func (h *Handler) LoginAdmin(c *fiber.Ctx) error { func (h *Handler) LoginAdmin(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64) OrganizationID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid { if !OrganizationID.Valid {
h.BadRequestLogger().Error("invalid company id") h.BadRequestLogger().Error("invalid company id")
return fiber.NewError(fiber.StatusBadRequest, "invalid company id") return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
} }
@ -190,7 +190,7 @@ func (h *Handler) LoginAdmin(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, errMsg) return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
successRes, err := h.authSvc.Login(c.Context(), req.Email, req.PhoneNumber, req.Password, companyID) successRes, err := h.authSvc.Login(c.Context(), req.Email, req.PhoneNumber, req.Password, OrganizationID)
if err != nil { if err != nil {
switch { switch {
case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound): case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound):
@ -221,7 +221,7 @@ func (h *Handler) LoginAdmin(c *fiber.Ctx) error {
} }
} }
if successRes.Role == domain.RoleCustomer { if successRes.Role == domain.RoleStudent || successRes.Role == domain.RoleInstructor {
h.mongoLoggerSvc.Warn("Login attempt: admin login of customer", h.mongoLoggerSvc.Warn("Login attempt: admin login of customer",
zap.Int("status_code", fiber.StatusForbidden), zap.Int("status_code", fiber.StatusForbidden),
zap.String("role", string(successRes.Role)), zap.String("role", string(successRes.Role)),
@ -451,7 +451,7 @@ func (h *Handler) RefreshToken(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user information:"+err.Error()) return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user information:"+err.Error())
} }
accessToken, err := jwtutil.CreateJwt(user.ID, user.Role, user.CompanyID, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry) accessToken, err := jwtutil.CreateJwt(user.ID, user.Role, user.OrganizationID, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry)
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("Failed to create new access token", h.mongoLoggerSvc.Error("Failed to create new access token",
zap.Int("status_code", fiber.StatusInternalServerError), zap.Int("status_code", fiber.StatusInternalServerError),

View File

@ -1,456 +0,0 @@
package handlers
import (
"Yimaru-Backend/internal/domain"
"Yimaru-Backend/internal/services/authentication"
"Yimaru-Backend/internal/web_server/response"
"fmt"
"strconv"
"time"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
type CreateCashierReq struct {
FirstName string `json:"first_name" example:"John"`
LastName string `json:"last_name" example:"Doe"`
Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"`
Password string `json:"password" example:"password123"`
BranchID int64 `json:"branch_id" example:"1"`
Suspended bool `json:"suspended" example:"false"`
}
// CreateCashier godoc
// @Summary Create cashier
// @Description Create cashier
// @Tags cashier
// @Accept json
// @Produce json
// @Param cashier body CreateCashierReq true "Create cashier"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/cashiers [post]
func (h *Handler) CreateCashier(c *fiber.Ctx) error {
// Get user_id from middleware
// companyID := c.Locals("company_id").(domain.ValidInt64)
var req CreateCashierReq
if err := c.BodyParser(&req); err != nil {
h.mongoLoggerSvc.Info("failed to parse CreateCashier request body",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
}
valErrs, ok := h.validator.Validate(c, req)
if !ok {
var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Info("Failed to validate CreateCashier",
zap.Any("request", req),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg)
}
// Cashiers inherit the company id from the branch id
// TODO add a check here to make sure that the admin/manager if from same company
// branch, err := h.branchSvc.GetBranchByID(c.Context(), req.BranchID)
// if err != nil {
// return fiber.NewError(fiber.StatusBadRequest, "Branch ID is invalid")
// }
// userRequest := domain.CreateUserReq{
// FirstName: req.FirstName,
// LastName: req.LastName,
// Email: req.Email,
// PhoneNumber: req.PhoneNumber,
// Password: req.Password,
// Role: string(domain.RoleCashier),
// Suspended: req.Suspended,
// CompanyID: domain.ValidInt64{
// Value: 0,
// Valid: true,
// },
// }
// fmt.Print(req.Suspended)
// newUser, err := h.userSvc.CreateUser(c.Context(), userRequest, true)
// if err != nil {
// h.mongoLoggerSvc.Error("Failed to create cashier user",
// zap.Any("userRequest", userRequest),
// zap.Int("status_code", fiber.StatusInternalServerError),
// zap.Error(err),
// zap.Time("timestamp", time.Now()),
// )
// return fiber.NewError(fiber.StatusInternalServerError, "Failed to create cashier user:"+err.Error())
// }
// err = h.branchSvc.CreateBranchCashier(c.Context(), req.BranchID, newUser.ID)
// if err != nil {
// h.mongoLoggerSvc.Error("failed to create branch cashier",
// zap.Int64("branchID", req.BranchID),
// zap.Int64("userID", newUser.ID),
// zap.Int("status_code", fiber.StatusInternalServerError),
// zap.Error(err),
// zap.Time("timestamp", time.Now()),
// )
// return fiber.NewError(fiber.StatusInternalServerError, "Failed to create branch cashier:"+err.Error())
// }
return response.WriteJSON(c, fiber.StatusOK, "Cashier created successfully", nil, nil)
}
type GetCashierRes struct {
ID int64 `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Email string `json:"email"`
PhoneNumber string `json:"phone_number"`
Role domain.Role `json:"role"`
EmailVerified bool `json:"email_verified"`
PhoneVerified bool `json:"phone_verified"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
SuspendedAt time.Time `json:"suspended_at"`
Suspended bool `json:"suspended"`
LastLogin time.Time `json:"last_login"`
BranchID int64 `json:"branch_id"`
BranchName string `json:"branch_name"`
BranchWallet int64 `json:"branch_wallet"`
BranchLocation string `json:"branch_location"`
}
// GetAllCashiers godoc
// @Summary Get all cashiers
// @Description Get all cashiers
// @Tags cashier
// @Accept json
// @Produce json
// @Param page query int false "Page number"
// @Param page_size query int false "Page size"
// @Success 200 {array} GetCashierRes
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/cashiers [get]
func (h *Handler) GetAllCashiers(c *fiber.Ctx) error {
role := c.Locals("role").(domain.Role)
companyId := c.Locals("company_id").(domain.ValidInt64)
if role != domain.RoleSuperAdmin && !companyId.Valid {
h.mongoLoggerSvc.Error("Cannot get company ID in context",
zap.String("role", string(role)),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Cannot get company ID in context")
}
searchQuery := c.Query("query")
searchString := domain.ValidString{
Value: searchQuery,
Valid: searchQuery != "",
}
createdBeforeQuery := c.Query("created_before")
var createdBefore domain.ValidTime
if createdBeforeQuery != "" {
createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
if err != nil {
h.mongoLoggerSvc.Info("invalid created_before format",
zap.String("createdBefore", createdBeforeQuery),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid created_before format")
}
createdBefore = domain.ValidTime{
Value: createdBeforeParsed,
Valid: true,
}
}
createdAfterQuery := c.Query("created_after")
var createdAfter domain.ValidTime
if createdAfterQuery != "" {
createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
if err != nil {
h.mongoLoggerSvc.Info("invalid created_after format",
zap.String("created_after", createdAfterQuery),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid created_after format")
}
createdAfter = domain.ValidTime{
Value: createdAfterParsed,
Valid: true,
}
}
filter := domain.UserFilter{
Role: string(domain.RoleCashier),
CompanyID: companyId,
Page: domain.ValidInt{
Value: c.QueryInt("page", 1) - 1,
Valid: true,
},
PageSize: domain.ValidInt{
Value: c.QueryInt("page_size", 10),
Valid: true,
},
Query: searchString,
CreatedBefore: createdBefore,
CreatedAfter: createdAfter,
}
valErrs, ok := h.validator.Validate(c, filter)
if !ok {
var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Info("Failed to validate filters for GetAllCashier",
zap.Any("filter", filter),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg)
}
cashiers, total, err := h.userSvc.GetAllCashiers(c.Context(), filter)
if err != nil {
h.mongoLoggerSvc.Error("failed to get all cashiers",
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get cashiers:"+err.Error())
}
var result []GetCashierRes = make([]GetCashierRes, 0, len(cashiers))
for _, cashier := range cashiers {
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), cashier.ID)
if err != nil {
if err == authentication.ErrRefreshTokenNotFound {
lastLogin = &cashier.CreatedAt
} else {
h.mongoLoggerSvc.Error("Failed to get user last login",
zap.Int64("userID", cashier.ID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login:"+err.Error())
}
}
result = append(result, GetCashierRes{
ID: cashier.ID,
FirstName: cashier.FirstName,
LastName: cashier.LastName,
Email: cashier.Email,
PhoneNumber: cashier.PhoneNumber,
Role: cashier.Role,
EmailVerified: cashier.EmailVerified,
PhoneVerified: cashier.PhoneVerified,
CreatedAt: cashier.CreatedAt,
UpdatedAt: cashier.UpdatedAt,
SuspendedAt: cashier.SuspendedAt,
Suspended: cashier.Suspended,
LastLogin: *lastLogin,
BranchID: cashier.BranchID,
BranchName: cashier.BranchName,
BranchWallet: cashier.BranchWallet,
BranchLocation: cashier.BranchLocation,
})
}
return response.WritePaginatedJSON(c, fiber.StatusOK, "Cashiers retrieved successfully", result, nil, filter.Page.Value, int(total))
}
// GetCashierByID godoc
// @Summary Get cashier by id
// @Description Get a single cashier by id
// @Tags cashier
// @Accept json
// @Produce json
// @Param id path int true "User ID"
// @Success 200 {object} UserProfileRes
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/cashier/{id} [get]
func (h *Handler) GetCashierByID(c *fiber.Ctx) error {
// branchId := int64(12) //c.Locals("branch_id").(int64)
// filter := user.Filter{
// Role: string(domain.RoleUser),
// BranchId: user.ValidBranchId{
// Value: branchId,
// Valid: true,
// },
// Page: c.QueryInt("page", 1),
// PageSize: c.QueryInt("page_size", 10),
// }
// valErrs, ok := validator.Validate(c, filter)
// if !ok {
// return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
// }
stringID := c.Params("id")
cashierID, err := strconv.ParseInt(stringID, 10, 64)
if err != nil {
h.mongoLoggerSvc.Info("failed to parse user_id",
zap.String("stringID", stringID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid cashier ID")
}
user, err := h.userSvc.GetCashierByID(c.Context(), cashierID)
if err != nil {
h.mongoLoggerSvc.Error("Get User By ID failed",
zap.Int64("cashierID", cashierID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get cashiers:"+err.Error())
}
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
if err != nil {
if err != authentication.ErrRefreshTokenNotFound {
h.mongoLoggerSvc.Error("Failed to get user last login",
zap.Int64("userID", user.ID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login:"+err.Error())
}
lastLogin = &user.CreatedAt
}
res := GetCashierRes{
ID: user.ID,
FirstName: user.FirstName,
LastName: user.LastName,
Email: user.Email,
PhoneNumber: user.PhoneNumber,
Role: user.Role,
EmailVerified: user.EmailVerified,
PhoneVerified: user.PhoneVerified,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
SuspendedAt: user.SuspendedAt,
Suspended: user.Suspended,
LastLogin: *lastLogin,
BranchID: user.BranchID,
BranchName: user.BranchName,
BranchWallet: user.BranchWallet,
BranchLocation: user.BranchLocation,
}
return response.WriteJSON(c, fiber.StatusOK, "User retrieved successfully", res, nil)
}
type updateCashierReq struct {
FirstName string `json:"first_name" example:"John"`
LastName string `json:"last_name" example:"Doe"`
Suspended bool `json:"suspended" example:"false"`
}
// UpdateCashier godoc
// @Summary Update cashier
// @Description Update cashier
// @Tags cashier
// @Accept json
// @Produce json
// @Param id path int true "Cashier ID"
// @Param cashier body updateCashierReq true "Update cashier"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/cashiers/{id} [put]
func (h *Handler) UpdateCashier(c *fiber.Ctx) error {
cashierIdStr := c.Params("id")
cashierId, err := strconv.ParseInt(cashierIdStr, 10, 64)
if err != nil {
h.mongoLoggerSvc.Info("UpdateCashier invalid cashier ID",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid cashier ID")
}
var req updateCashierReq
if err := c.BodyParser(&req); err != nil {
h.mongoLoggerSvc.Info("UpdateCashier failed to parse request body",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
}
valErrs, ok := h.validator.Validate(c, req)
if !ok {
var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Info("Failed to validate update cashier request",
zap.Any("request", req),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg)
}
err = h.userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{
UserId: cashierId,
FirstName: domain.ValidString{
Value: req.FirstName,
Valid: req.FirstName != "",
},
LastName: domain.ValidString{
Value: req.LastName,
Valid: req.LastName != "",
},
Suspended: domain.ValidBool{
Value: req.Suspended,
Valid: true,
},
},
)
if err != nil {
h.mongoLoggerSvc.Error("failed to update cashier",
zap.Int64("userID", cashierId),
zap.Any("request", req),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update cashier"+err.Error())
}
return response.WriteJSON(c, fiber.StatusOK, "Cashier updated successfully", nil, nil)
}

View File

@ -1,30 +1,24 @@
package handlers package handlers
import (
"Yimaru-Backend/internal/domain"
"github.com/gofiber/fiber/v2"
)
// @Summary Get supported currencies // @Summary Get supported currencies
// @Description Returns list of supported currencies // @Description Returns list of supported currencies
// @Tags Multi-Currency // @Tags Multi-Currency
// @Produce json // @Produce json
// @Success 200 {object} domain.Response{data=[]domain.Currency} // @Success 200 {object} domain.Response{data=[]domain.Currency}
// @Router /api/v1/currencies [get] // @Router /api/v1/currencies [get]
func (h *Handler) GetSupportedCurrencies(c *fiber.Ctx) error { // func (h *Handler) GetSupportedCurrencies(c *fiber.Ctx) error {
currencies, err := h.currSvc.GetSupportedCurrencies(c.Context()) // currencies, err := h.currSvc.GetSupportedCurrencies(c.Context())
if err != nil { // if err != nil {
return domain.UnExpectedErrorResponse(c) // return domain.UnExpectedErrorResponse(c)
} // }
return c.Status(fiber.StatusOK).JSON(domain.Response{ // return c.Status(fiber.StatusOK).JSON(domain.Response{
Success: true, // Success: true,
Message: "Supported currencies retrieved successfully", // Message: "Supported currencies retrieved successfully",
Data: currencies, // Data: currencies,
StatusCode: fiber.StatusOK, // StatusCode: fiber.StatusOK,
}) // })
} // }
// @Summary Convert currency // @Summary Convert currency
// @Description Converts amount from one currency to another // @Description Converts amount from one currency to another
@ -36,23 +30,23 @@ func (h *Handler) GetSupportedCurrencies(c *fiber.Ctx) error {
// @Success 200 {object} domain.Response{data=float64} // @Success 200 {object} domain.Response{data=float64}
// @Failure 400 {object} domain.ErrorResponse // @Failure 400 {object} domain.ErrorResponse
// @Router /api/v1/currencies/convert [get] // @Router /api/v1/currencies/convert [get]
func (h *Handler) ConvertCurrency(c *fiber.Ctx) error { // func (h *Handler) ConvertCurrency(c *fiber.Ctx) error {
from := domain.IntCurrency(c.Query("from")) // from := domain.IntCurrency(c.Query("from"))
to := domain.IntCurrency(c.Query("to")) // to := domain.IntCurrency(c.Query("to"))
amount := c.QueryFloat("amount", 0) // amount := c.QueryFloat("amount", 0)
// // if err != nil {
// // return domain.BadRequestResponse(c)
// // }
// converted, err := h.currSvc.Convert(c.Context(), amount, from, to)
// if err != nil { // if err != nil {
// return domain.BadRequestResponse(c) // return domain.UnExpectedErrorResponse(c)
// } // }
converted, err := h.currSvc.Convert(c.Context(), amount, from, to) // return c.Status(fiber.StatusOK).JSON(domain.Response{
if err != nil { // Success: true,
return domain.UnExpectedErrorResponse(c) // Message: "Currency converted successfully",
} // Data: converted,
// StatusCode: fiber.StatusOK,
return c.Status(fiber.StatusOK).JSON(domain.Response{ // })
Success: true, // }
Message: "Currency converted successfully",
Data: converted,
StatusCode: fiber.StatusOK,
})
}

View File

@ -4,10 +4,9 @@ import (
"Yimaru-Backend/internal/config" "Yimaru-Backend/internal/config"
"Yimaru-Backend/internal/services/arifpay" "Yimaru-Backend/internal/services/arifpay"
"Yimaru-Backend/internal/services/authentication" "Yimaru-Backend/internal/services/authentication"
"Yimaru-Backend/internal/services/currency"
notificationservice "Yimaru-Backend/internal/services/notification" notificationservice "Yimaru-Backend/internal/services/notification"
"Yimaru-Backend/internal/services/recommendation" "Yimaru-Backend/internal/services/recommendation"
referralservice "Yimaru-Backend/internal/services/referal" // referralservice "Yimaru-Backend/internal/services/referal"
"Yimaru-Backend/internal/services/settings" "Yimaru-Backend/internal/services/settings"
@ -22,13 +21,10 @@ import (
type Handler struct { type Handler struct {
arifpaySvc *arifpay.ArifpayService arifpaySvc *arifpay.ArifpayService
// instSvc *institutions.Service
currSvc *currency.Service
logger *slog.Logger logger *slog.Logger
settingSvc *settings.Service settingSvc *settings.Service
notificationSvc *notificationservice.Service notificationSvc *notificationservice.Service
userSvc *user.Service userSvc *user.Service
referralSvc *referralservice.Service
transactionSvc *transaction.Service transactionSvc *transaction.Service
recommendationSvc recommendation.RecommendationService recommendationSvc recommendation.RecommendationService
authSvc *authentication.Service authSvc *authentication.Service
@ -39,23 +35,14 @@ type Handler struct {
} }
func New( func New(
// directDepositSvc *directdeposit.Service,
// telebirrSvc *telebirr.TelebirrService,
arifpaySvc *arifpay.ArifpayService, arifpaySvc *arifpay.ArifpayService,
// santimpaySvc *santimpay.SantimPayService,
// instSvc *institutions.Service,
currSvc *currency.Service,
logger *slog.Logger, logger *slog.Logger,
settingSvc *settings.Service, settingSvc *settings.Service,
notificationSvc *notificationservice.Service, notificationSvc *notificationservice.Service,
validator *customvalidator.CustomValidator, validator *customvalidator.CustomValidator,
// reportSvc report.ReportService,
// chapaSvc *chapa.Service,
referralSvc *referralservice.Service,
recommendationSvc recommendation.RecommendationService, recommendationSvc recommendation.RecommendationService,
userSvc *user.Service, userSvc *user.Service,
transactionSvc *transaction.Service, transactionSvc *transaction.Service,
// ticketSvc *ticket.Service,
authSvc *authentication.Service, authSvc *authentication.Service,
jwtConfig jwtutil.JwtConfig, jwtConfig jwtutil.JwtConfig,
cfg *config.Config, cfg *config.Config,
@ -63,12 +50,9 @@ func New(
) *Handler { ) *Handler {
return &Handler{ return &Handler{
arifpaySvc: arifpaySvc, arifpaySvc: arifpaySvc,
instSvc: instSvc,
currSvc: currSvc,
logger: logger, logger: logger,
settingSvc: settingSvc, settingSvc: settingSvc,
notificationSvc: notificationSvc, notificationSvc: notificationSvc,
referralSvc: referralSvc,
validator: validator, validator: validator,
userSvc: userSvc, userSvc: userSvc,
transactionSvc: transactionSvc, transactionSvc: transactionSvc,

View File

@ -1,13 +1,5 @@
package handlers package handlers
import (
"Yimaru-Backend/internal/domain"
"strconv"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
// @Summary Create a new bank // @Summary Create a new bank
// @Tags Institutions - Banks // @Tags Institutions - Banks
// @Accept json // @Accept json
@ -16,18 +8,18 @@ import (
// @Success 201 {object} domain.Bank // @Success 201 {object} domain.Bank
// @Failure 400 {object} domain.ErrorResponse // @Failure 400 {object} domain.ErrorResponse
// @Router /api/v1/banks [post] // @Router /api/v1/banks [post]
func (h *Handler) CreateBank(c *fiber.Ctx) error { // func (h *Handler) CreateBank(c *fiber.Ctx) error {
var bank domain.Bank // var bank domain.Bank
if err := c.BodyParser(&bank); err != nil { // if err := c.BodyParser(&bank); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid payload"}) // return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid payload"})
} // }
err := h.instSvc.Create(c.Context(), &bank) // // err := h.instSvc.Create(c.Context(), &bank)
if err != nil { // // if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) // // return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
} // // }
return c.Status(fiber.StatusCreated).JSON(bank) // return c.Status(fiber.StatusCreated).JSON(bank)
} // }
// @Summary Get a bank by ID // @Summary Get a bank by ID
// @Tags Institutions - Banks // @Tags Institutions - Banks
@ -38,19 +30,19 @@ func (h *Handler) CreateBank(c *fiber.Ctx) error {
// @Failure 404 {object} domain.ErrorResponse // @Failure 404 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/banks/{id} [get] // @Router /api/v1/banks/{id} [get]
func (h *Handler) GetBankByID(c *fiber.Ctx) error { // func (h *Handler) GetBankByID(c *fiber.Ctx) error {
id, err := c.ParamsInt("id") // id, err := c.ParamsInt("id")
if err != nil || id <= 0 { // if err != nil || id <= 0 {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid bank ID"}) // return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid bank ID"})
} // }
bank, err := h.instSvc.GetByID(c.Context(), int64(id)) // // bank, err := h.instSvc.GetByID(c.Context(), int64(id))
if err != nil { // // if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "bank not found"}) // // return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "bank not found"})
} // // }
return c.JSON(bank) // return c.JSON(bank)
} // }
// @Summary Update a bank // @Summary Update a bank
// @Tags Institutions - Banks // @Tags Institutions - Banks
@ -63,42 +55,42 @@ func (h *Handler) GetBankByID(c *fiber.Ctx) error {
// @Failure 404 {object} domain.ErrorResponse // @Failure 404 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/banks/{id} [put] // @Router /api/v1/banks/{id} [put]
func (h *Handler) UpdateBank(c *fiber.Ctx) error { // func (h *Handler) UpdateBank(c *fiber.Ctx) error {
id, err := c.ParamsInt("id") // id, err := c.ParamsInt("id")
if err != nil || id <= 0 { // if err != nil || id <= 0 {
// return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid bank ID"}) // // return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid bank ID"})
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ // return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Failed to update bank", // Message: "Failed to update bank",
Error: err.Error(), // Error: err.Error(),
}) // })
} // }
var bank domain.Bank // var bank domain.Bank
if err := c.BodyParser(&bank); err != nil { // if err := c.BodyParser(&bank); err != nil {
// return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid payload"}) // // return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid payload"})
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ // return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Failed to update bank", // Message: "Failed to update bank",
Error: err.Error(), // Error: err.Error(),
}) // })
} // }
bank.ID = id // bank.ID = id
err = h.instSvc.Update(c.Context(), &bank) // err = h.instSvc.Update(c.Context(), &bank)
if err != nil { // if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ // return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to update bank", // Message: "Failed to update bank",
Error: err.Error(), // Error: err.Error(),
}) // })
} // }
return c.Status(fiber.StatusOK).JSON(domain.Response{ // return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Bank updated successfully", // Message: "Bank updated successfully",
StatusCode: fiber.StatusOK, // StatusCode: fiber.StatusOK,
Success: true, // Success: true,
Data: bank, // Data: bank,
}) // })
// return c.JSON(bank) // // return c.JSON(bank)
} // }
// @Summary Delete a bank // @Summary Delete a bank
// @Tags Institutions - Banks // @Tags Institutions - Banks
@ -109,19 +101,19 @@ func (h *Handler) UpdateBank(c *fiber.Ctx) error {
// @Failure 404 {object} domain.ErrorResponse // @Failure 404 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/banks/{id} [delete] // @Router /api/v1/banks/{id} [delete]
func (h *Handler) DeleteBank(c *fiber.Ctx) error { // func (h *Handler) DeleteBank(c *fiber.Ctx) error {
id, err := c.ParamsInt("id") // id, err := c.ParamsInt("id")
if err != nil || id <= 0 { // if err != nil || id <= 0 {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid bank ID"}) // return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid bank ID"})
} // }
err = h.instSvc.Delete(c.Context(), int64(id)) // err = h.instSvc.Delete(c.Context(), int64(id))
if err != nil { // if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) // return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
} // }
return c.SendStatus(fiber.StatusNoContent) // return c.SendStatus(fiber.StatusNoContent)
} // }
// @Summary List all banks with pagination and filtering // @Summary List all banks with pagination and filtering
// @Tags Institutions - Banks // @Tags Institutions - Banks
@ -135,51 +127,51 @@ func (h *Handler) DeleteBank(c *fiber.Ctx) error {
// @Failure 400 {object} domain.ErrorResponse // @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/banks [get] // @Router /api/v1/banks [get]
func (h *Handler) ListBanks(c *fiber.Ctx) error { // func (h *Handler) ListBanks(c *fiber.Ctx) error {
// Parse query parameters // // Parse query parameters
countryID, _ := strconv.Atoi(c.Query("country_id")) // countryID, _ := strconv.Atoi(c.Query("country_id"))
var countryIDPtr *int // var countryIDPtr *int
if c.Query("country_id") != "" { // if c.Query("country_id") != "" {
countryIDPtr = &countryID // countryIDPtr = &countryID
} // }
isActive, _ := strconv.ParseBool(c.Query("is_active")) // isActive, _ := strconv.ParseBool(c.Query("is_active"))
var isActivePtr *bool // var isActivePtr *bool
if c.Query("is_active") != "" { // if c.Query("is_active") != "" {
isActivePtr = &isActive // isActivePtr = &isActive
} // }
var searchTermPtr *string // var searchTermPtr *string
if searchTerm := c.Query("search"); searchTerm != "" { // if searchTerm := c.Query("search"); searchTerm != "" {
searchTermPtr = &searchTerm // searchTermPtr = &searchTerm
} // }
page, _ := strconv.Atoi(c.Query("page", "1")) // page, _ := strconv.Atoi(c.Query("page", "1"))
pageSize, _ := strconv.Atoi(c.Query("page_size", "50")) // pageSize, _ := strconv.Atoi(c.Query("page_size", "50"))
banks, pagination, err := h.instSvc.List( // banks, pagination, err := h.instSvc.List(
c.Context(), // c.Context(),
countryIDPtr, // countryIDPtr,
isActivePtr, // isActivePtr,
searchTermPtr, // searchTermPtr,
page, // page,
pageSize, // pageSize,
) // )
if err != nil { // if err != nil {
h.mongoLoggerSvc.Error("failed to list banks", // h.mongoLoggerSvc.Error("failed to list banks",
zap.Error(err), // zap.Error(err),
zap.Any("query_params", c.Queries()), // zap.Any("query_params", c.Queries()),
) // )
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ // return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to retrieve banks", // Message: "Failed to retrieve banks",
Error: err.Error(), // Error: err.Error(),
}) // })
} // }
return c.Status(fiber.StatusOK).JSON(domain.InstResponse{ // return c.Status(fiber.StatusOK).JSON(domain.InstResponse{
Message: "Banks retrieved successfully", // Message: "Banks retrieved successfully",
Status: "success", // Status: "success",
Data: banks, // Data: banks,
Pagination: pagination, // Pagination: pagination,
}) // })
} // }

View File

@ -2,15 +2,12 @@ package handlers
import ( import (
"Yimaru-Backend/internal/domain" "Yimaru-Backend/internal/domain"
"Yimaru-Backend/internal/services/authentication"
"Yimaru-Backend/internal/web_server/response" "Yimaru-Backend/internal/web_server/response"
"fmt"
"strconv" "strconv"
"time" "time"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.uber.org/zap"
) )
type CreateManagerReq struct { type CreateManagerReq struct {
@ -34,75 +31,75 @@ type CreateManagerReq struct {
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /api/v1/managers [post] // @Router /api/v1/managers [post]
func (h *Handler) CreateManager(c *fiber.Ctx) error { // func (h *Handler) CreateManager(c *fiber.Ctx) error {
// Get user_id from middleware // // Get user_id from middleware
var req CreateManagerReq // var req CreateManagerReq
if err := c.BodyParser(&req); err != nil { // if err := c.BodyParser(&req); err != nil {
h.logger.Error("RegisterUser failed", "error", err) // h.logger.Error("RegisterUser failed", "error", err)
h.mongoLoggerSvc.Info("CreateManager failed to create manager", // h.mongoLoggerSvc.Info("CreateManager failed to create manager",
zap.Int("status_code", fiber.StatusBadRequest), // zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error()) // return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
} // }
valErrs, ok := h.validator.Validate(c, req) // valErrs, ok := h.validator.Validate(c, req)
if !ok { // if !ok {
var errMsg string // var errMsg string
for field, msg := range valErrs { // for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg) // errMsg += fmt.Sprintf("%s: %s; ", field, msg)
} // }
h.mongoLoggerSvc.Info("Failed to validate CreateManager", // h.mongoLoggerSvc.Info("Failed to validate CreateManager",
zap.Any("request", req), // zap.Any("request", req),
zap.Int("status_code", fiber.StatusBadRequest), // zap.Int("status_code", fiber.StatusBadRequest),
zap.String("errMsg", errMsg), // zap.String("errMsg", errMsg),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusBadRequest, errMsg) // return fiber.NewError(fiber.StatusBadRequest, errMsg)
} // }
var companyID domain.ValidInt64 // var companyID domain.ValidInt64
role := c.Locals("role").(domain.Role) // role := c.Locals("role").(domain.Role)
if role == domain.RoleSuperAdmin { // if role == domain.RoleSuperAdmin {
if req.CompanyID == nil { // if req.CompanyID == nil {
h.logger.Error("RegisterUser failed error: company id is required") // h.logger.Error("RegisterUser failed error: company id is required")
h.mongoLoggerSvc.Info("RegisterUser failed error: company id is required", // h.mongoLoggerSvc.Info("RegisterUser failed error: company id is required",
zap.Int("status_code", fiber.StatusBadRequest), // zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusBadRequest, "Company ID is required for super-admin") // return fiber.NewError(fiber.StatusBadRequest, "Company ID is required for super-admin")
} // }
companyID = domain.ValidInt64{ // companyID = domain.ValidInt64{
Value: *req.CompanyID, // Value: *req.CompanyID,
Valid: true, // Valid: true,
} // }
} else { // } else {
companyID = c.Locals("company_id").(domain.ValidInt64) // companyID = c.Locals("company_id").(domain.ValidInt64)
} // }
user := domain.CreateUserReq{ // user := domain.CreateUserReq{
FirstName: req.FirstName, // FirstName: req.FirstName,
LastName: req.LastName, // LastName: req.LastName,
Email: req.Email, // Email: req.Email,
PhoneNumber: req.PhoneNumber, // PhoneNumber: req.PhoneNumber,
Password: req.Password, // Password: req.Password,
Role: string(domain.RoleBranchManager), // Role: string(domain.RoleBranchManager),
CompanyID: companyID, // OrganizationID: companyID,
} // }
_, err := h.userSvc.CreateUser(c.Context(), user, true) // _, err := h.userSvc.CreateUser(c.Context(), user, true)
if err != nil { // if err != nil {
h.mongoLoggerSvc.Error("CreateManager failed to create manager", // h.mongoLoggerSvc.Error("CreateManager failed to create manager",
zap.Int("status_code", fiber.StatusInternalServerError), // zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create manager:"+err.Error()) // return fiber.NewError(fiber.StatusInternalServerError, "Failed to create manager:"+err.Error())
} // }
return response.WriteJSON(c, fiber.StatusOK, "Manager created successfully", nil, nil) // return response.WriteJSON(c, fiber.StatusOK, "Manager created successfully", nil, nil)
} // }
type ManagersRes struct { type ManagersRes struct {
ID int64 `json:"id"` ID int64 `json:"id"`
@ -133,141 +130,141 @@ type ManagersRes struct {
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /api/v1/managers [get] // @Router /api/v1/managers [get]
func (h *Handler) GetAllManagers(c *fiber.Ctx) error { // func (h *Handler) GetAllManagers(c *fiber.Ctx) error {
role := c.Locals("role").(domain.Role) // role := c.Locals("role").(domain.Role)
companyId := c.Locals("company_id").(domain.ValidInt64) // companyId := c.Locals("company_id").(domain.ValidInt64)
// Checking to make sure that admin user has a company id in the token // // Checking to make sure that admin user has a company id in the token
if role != domain.RoleSuperAdmin && !companyId.Valid { // if role != domain.RoleSuperAdmin && !companyId.Valid {
h.mongoLoggerSvc.Error("Cannot get company ID from context", // h.mongoLoggerSvc.Error("Cannot get company ID from context",
zap.String("role", string(role)), // zap.String("role", string(role)),
zap.Int("status_code", fiber.StatusInternalServerError), // zap.Int("status_code", fiber.StatusInternalServerError),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusInternalServerError, "Cannot get company ID from context") // return fiber.NewError(fiber.StatusInternalServerError, "Cannot get company ID from context")
} // }
searchQuery := c.Query("query") // searchQuery := c.Query("query")
searchString := domain.ValidString{ // searchString := domain.ValidString{
Value: searchQuery, // Value: searchQuery,
Valid: searchQuery != "", // Valid: searchQuery != "",
} // }
createdBeforeQuery := c.Query("created_before") // createdBeforeQuery := c.Query("created_before")
var createdBefore domain.ValidTime // var createdBefore domain.ValidTime
if createdBeforeQuery != "" { // if createdBeforeQuery != "" {
createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery) // createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
if err != nil { // if err != nil {
h.mongoLoggerSvc.Info("invalid created_before format", // h.mongoLoggerSvc.Info("invalid created_before format",
zap.String("created_before", createdBeforeQuery), // zap.String("created_before", createdBeforeQuery),
zap.Int("status_code", fiber.StatusBadRequest), // zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusBadRequest, "Invalid created_before format") // return fiber.NewError(fiber.StatusBadRequest, "Invalid created_before format")
} // }
createdBefore = domain.ValidTime{ // createdBefore = domain.ValidTime{
Value: createdBeforeParsed, // Value: createdBeforeParsed,
Valid: true, // Valid: true,
} // }
} // }
createdAfterQuery := c.Query("created_after") // createdAfterQuery := c.Query("created_after")
var createdAfter domain.ValidTime // var createdAfter domain.ValidTime
if createdAfterQuery != "" { // if createdAfterQuery != "" {
createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery) // createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
if err != nil { // if err != nil {
h.mongoLoggerSvc.Info("invalid created_after format", // h.mongoLoggerSvc.Info("invalid created_after format",
zap.String("created_after", createdAfterQuery), // zap.String("created_after", createdAfterQuery),
zap.Int("status_code", fiber.StatusBadRequest), // zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusBadRequest, "Invalid created_after format") // return fiber.NewError(fiber.StatusBadRequest, "Invalid created_after format")
} // }
createdAfter = domain.ValidTime{ // createdAfter = domain.ValidTime{
Value: createdAfterParsed, // Value: createdAfterParsed,
Valid: true, // Valid: true,
} // }
} // }
filter := domain.UserFilter{ // filter := domain.UserFilter{
Role: string(domain.RoleBranchManager), // Role: string(domain.RoleBranchManager),
CompanyID: companyId, // OrganizationID: companyId,
Page: domain.ValidInt{ // Page: domain.ValidInt{
Value: c.QueryInt("page", 1) - 1, // Value: c.QueryInt("page", 1) - 1,
Valid: true, // Valid: true,
}, // },
PageSize: domain.ValidInt{ // PageSize: domain.ValidInt{
Value: c.QueryInt("page_size", 10), // Value: c.QueryInt("page_size", 10),
Valid: true, // Valid: true,
}, // },
Query: searchString, // Query: searchString,
CreatedBefore: createdBefore, // CreatedBefore: createdBefore,
CreatedAfter: createdAfter, // CreatedAfter: createdAfter,
} // }
valErrs, ok := h.validator.Validate(c, filter) // valErrs, ok := h.validator.Validate(c, filter)
if !ok { // if !ok {
var errMsg string // var errMsg string
for field, msg := range valErrs { // for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg) // errMsg += fmt.Sprintf("%s: %s; ", field, msg)
} // }
h.mongoLoggerSvc.Info("Failed to validate get all filters", // h.mongoLoggerSvc.Info("Failed to validate get all filters",
zap.Any("filter", filter), // zap.Any("filter", filter),
zap.Int("status_code", fiber.StatusBadRequest), // zap.Int("status_code", fiber.StatusBadRequest),
zap.String("errMsg", errMsg), // zap.String("errMsg", errMsg),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusBadRequest, errMsg) // return fiber.NewError(fiber.StatusBadRequest, errMsg)
} // }
managers, total, err := h.userSvc.GetAllUsers(c.Context(), filter) // managers, total, err := h.userSvc.GetAllUsers(c.Context(), filter)
if err != nil { // if err != nil {
h.logger.Error("GetAllManagers failed", "error", err) // h.logger.Error("GetAllManagers failed", "error", err)
h.mongoLoggerSvc.Error("GetAllManagers failed to get all managers", // h.mongoLoggerSvc.Error("GetAllManagers failed to get all managers",
zap.Any("filter", filter), // zap.Any("filter", filter),
zap.Int("status_code", fiber.StatusInternalServerError), // zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get Managers"+err.Error()) // return fiber.NewError(fiber.StatusInternalServerError, "Failed to get Managers"+err.Error())
} // }
var result []ManagersRes = make([]ManagersRes, len(managers)) // var result []ManagersRes = make([]ManagersRes, len(managers))
for index, manager := range managers { // for index, manager := range managers {
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), manager.ID) // lastLogin, err := h.authSvc.GetLastLogin(c.Context(), manager.ID)
if err != nil { // if err != nil {
if err == authentication.ErrRefreshTokenNotFound { // if err == authentication.ErrRefreshTokenNotFound {
lastLogin = &manager.CreatedAt // lastLogin = &manager.CreatedAt
} else { // } else {
h.mongoLoggerSvc.Error("Failed to get user last login", // h.mongoLoggerSvc.Error("Failed to get user last login",
zap.Int64("userID", manager.ID), // zap.Int64("userID", manager.ID),
zap.Int("status_code", fiber.StatusInternalServerError), // zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login:"+err.Error()) // return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login:"+err.Error())
} // }
} // }
result[index] = ManagersRes{ // result[index] = ManagersRes{
ID: manager.ID, // ID: manager.ID,
FirstName: manager.FirstName, // FirstName: manager.FirstName,
LastName: manager.LastName, // LastName: manager.LastName,
Email: manager.Email, // Email: manager.Email,
PhoneNumber: manager.PhoneNumber, // PhoneNumber: manager.PhoneNumber,
Role: manager.Role, // Role: manager.Role,
EmailVerified: manager.EmailVerified, // EmailVerified: manager.EmailVerified,
PhoneVerified: manager.PhoneVerified, // PhoneVerified: manager.PhoneVerified,
CreatedAt: manager.CreatedAt, // CreatedAt: manager.CreatedAt,
UpdatedAt: manager.UpdatedAt, // UpdatedAt: manager.UpdatedAt,
SuspendedAt: manager.SuspendedAt, // SuspendedAt: manager.SuspendedAt,
Suspended: manager.Suspended, // Suspended: manager.Suspended,
LastLogin: *lastLogin, // LastLogin: *lastLogin,
} // }
} // }
return response.WritePaginatedJSON(c, fiber.StatusOK, "Managers retrieved successfully", result, nil, filter.Page.Value, int(total)) // return response.WritePaginatedJSON(c, fiber.StatusOK, "Managers retrieved successfully", result, nil, filter.Page.Value, int(total))
} // }
// GetManagerByID godoc // GetManagerByID godoc
// @Summary Get manager by id // @Summary Get manager by id
@ -281,103 +278,103 @@ func (h *Handler) GetAllManagers(c *fiber.Ctx) error {
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /api/v1/managers/{id} [get] // @Router /api/v1/managers/{id} [get]
func (h *Handler) GetManagerByID(c *fiber.Ctx) error { // func (h *Handler) GetManagerByID(c *fiber.Ctx) error {
role := c.Locals("role").(domain.Role) // role := c.Locals("role").(domain.Role)
companyId := c.Locals("company_id").(domain.ValidInt64) // companyId := c.Locals("company_id").(domain.ValidInt64)
requestUserID := c.Locals("user_id").(int64) // requestUserID := c.Locals("user_id").(int64)
// Only Super Admin / Admin / Branch Manager can view this route // // Only Super Admin / Admin / Branch Manager can view this route
if role != domain.RoleSuperAdmin && role != domain.RoleAdmin && role != domain.RoleBranchManager { // if role != domain.RoleSuperAdmin && role != domain.RoleAdmin && role != domain.RoleBranchManager {
h.mongoLoggerSvc.Warn("Attempt to access from unauthorized role", // h.mongoLoggerSvc.Warn("Attempt to access from unauthorized role",
zap.Int64("userID", requestUserID), // zap.Int64("userID", requestUserID),
zap.String("role", string(role)), // zap.String("role", string(role)),
zap.Int("status_code", fiber.StatusForbidden), // zap.Int("status_code", fiber.StatusForbidden),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusForbidden, "This role cannot view this route") // return fiber.NewError(fiber.StatusForbidden, "This role cannot view this route")
} // }
if role != domain.RoleSuperAdmin && !companyId.Valid { // if role != domain.RoleSuperAdmin && !companyId.Valid {
h.mongoLoggerSvc.Error("Cannot get company ID in context", // h.mongoLoggerSvc.Error("Cannot get company ID in context",
zap.String("role", string(role)), // zap.String("role", string(role)),
zap.Int("status_code", fiber.StatusInternalServerError), // zap.Int("status_code", fiber.StatusInternalServerError),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusInternalServerError, "Cannot get company ID in context") // return fiber.NewError(fiber.StatusInternalServerError, "Cannot get company ID in context")
} // }
userIDstr := c.Params("id") // userIDstr := c.Params("id")
userID, err := strconv.ParseInt(userIDstr, 10, 64) // userID, err := strconv.ParseInt(userIDstr, 10, 64)
if err != nil { // if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid managers ID") // return fiber.NewError(fiber.StatusBadRequest, "Invalid managers ID")
} // }
user, err := h.userSvc.GetUserByID(c.Context(), userID) // user, err := h.userSvc.GetUserByID(c.Context(), userID)
if err != nil { // if err != nil {
h.mongoLoggerSvc.Error("Failed to get manager by id", // h.mongoLoggerSvc.Error("Failed to get manager by id",
zap.Int64("userID", userID), // zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError), // zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get managers:"+err.Error()) // return fiber.NewError(fiber.StatusInternalServerError, "Failed to get managers:"+err.Error())
} // }
// A Branch Manager can only fetch his own branch info // // A Branch Manager can only fetch his own branch info
if role == domain.RoleBranchManager && user.ID != requestUserID { // if role == domain.RoleBranchManager && user.ID != requestUserID {
h.mongoLoggerSvc.Warn("Attempt to access another branch manager info", // h.mongoLoggerSvc.Warn("Attempt to access another branch manager info",
zap.String("userID", userIDstr), // zap.String("userID", userIDstr),
zap.Int("status_code", fiber.StatusForbidden), // zap.Int("status_code", fiber.StatusForbidden),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusForbidden, "User Access Not Allowed") // return fiber.NewError(fiber.StatusForbidden, "User Access Not Allowed")
} // }
// Check that only admin from company can view this route // // Check that only admin from company can view this route
if role != domain.RoleSuperAdmin && user.CompanyID.Value != companyId.Value { // if role != domain.RoleSuperAdmin && user.OrganizationID.Value != companyId.Value {
h.mongoLoggerSvc.Warn("Attempt to access info from another company", // h.mongoLoggerSvc.Warn("Attempt to access info from another company",
zap.String("userID", userIDstr), // zap.String("userID", userIDstr),
zap.Int("status_code", fiber.StatusForbidden), // zap.Int("status_code", fiber.StatusForbidden),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusForbidden, "Cannot access another company information") // return fiber.NewError(fiber.StatusForbidden, "Cannot access another company information")
} // }
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID) // lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
if err != nil { // if err != nil {
if err != authentication.ErrRefreshTokenNotFound { // if err != authentication.ErrRefreshTokenNotFound {
h.mongoLoggerSvc.Error("Failed to get user last login", // h.mongoLoggerSvc.Error("Failed to get user last login",
zap.Int64("userID", userID), // zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError), // zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login"+err.Error()) // return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login"+err.Error())
} // }
lastLogin = &user.CreatedAt // lastLogin = &user.CreatedAt
} // }
res := ManagersRes{ // res := ManagersRes{
ID: user.ID, // ID: user.ID,
FirstName: user.FirstName, // FirstName: user.FirstName,
LastName: user.LastName, // LastName: user.LastName,
Email: user.Email, // Email: user.Email,
PhoneNumber: user.PhoneNumber, // PhoneNumber: user.PhoneNumber,
Role: user.Role, // Role: user.Role,
EmailVerified: user.EmailVerified, // EmailVerified: user.EmailVerified,
PhoneVerified: user.PhoneVerified, // PhoneVerified: user.PhoneVerified,
CreatedAt: user.CreatedAt, // CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt, // UpdatedAt: user.UpdatedAt,
SuspendedAt: user.SuspendedAt, // SuspendedAt: user.SuspendedAt,
Suspended: user.Suspended, // Suspended: user.Suspended,
LastLogin: *lastLogin, // LastLogin: *lastLogin,
} // }
return response.WriteJSON(c, fiber.StatusOK, "User retrieved successfully", res, nil) // return response.WriteJSON(c, fiber.StatusOK, "User retrieved successfully", res, nil)
} // }
type updateManagerReq struct { type updateManagerReq struct {
FirstName string `json:"first_name" example:"John"` FirstName string `json:"first_name" example:"John"`
@ -433,7 +430,7 @@ func (h *Handler) UpdateManagers(c *fiber.Ctx) error {
} }
err = h.userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{ err = h.userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{
UserId: ManagersId, UserID: ManagersId,
FirstName: domain.ValidString{ FirstName: domain.ValidString{
Value: req.FirstName, Value: req.FirstName,
Valid: req.FirstName != "", Valid: req.FirstName != "",
@ -446,7 +443,7 @@ func (h *Handler) UpdateManagers(c *fiber.Ctx) error {
Value: req.Suspended, Value: req.Suspended,
Valid: true, Valid: true,
}, },
CompanyID: companyID, OrganizationID: companyID,
}, },
) )
if err != nil { if err != nil {

View File

@ -5,11 +5,9 @@ import (
"Yimaru-Backend/internal/web_server/ws" "Yimaru-Backend/internal/web_server/ws"
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
@ -131,45 +129,45 @@ func (h *Handler) ConnectSocket(c *fiber.Ctx) error {
return nil return nil
} }
func (h *Handler) MarkNotificationAsRead(c *fiber.Ctx) error { // func (h *Handler) MarkNotificationAsRead(c *fiber.Ctx) error {
type Request struct { // type Request struct {
NotificationIDs []string `json:"notification_ids" validate:"required"` // NotificationIDs []string `json:"notification_ids" validate:"required"`
} // }
var req Request // var req Request
if err := c.BodyParser(&req); err != nil { // if err := c.BodyParser(&req); err != nil {
h.mongoLoggerSvc.Info("Failed to parse request body", // h.mongoLoggerSvc.Info("Failed to parse request body",
zap.Int("status_code", fiber.StatusBadRequest), // zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") // return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
} // }
userID, ok := c.Locals("user_id").(int64) // userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 { // if !ok || userID == 0 {
h.mongoLoggerSvc.Error("Invalid user ID in context", // h.mongoLoggerSvc.Error("Invalid user ID in context",
zap.Int64("userID", userID), // zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError), // zap.Int("status_code", fiber.StatusInternalServerError),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusInternalServerError, "invalid user ID in context") // return fiber.NewError(fiber.StatusInternalServerError, "invalid user ID in context")
} // }
fmt.Printf("Notification IDs: %v \n", req.NotificationIDs) // fmt.Printf("Notification IDs: %v \n", req.NotificationIDs)
if err := h.notificationSvc.MarkAsRead(context.Background(), req.NotificationIDs, userID); err != nil { // if err := h.notificationSvc.MarkAsRead(context.Background(), req.NotificationIDs, userID); err != nil {
h.mongoLoggerSvc.Error("Failed to mark notifications as read", // h.mongoLoggerSvc.Error("Failed to mark notifications as read",
zap.String("notificationID", strings.Join(req.NotificationIDs, ",")), // zap.String("notificationID", strings.Join(req.NotificationIDs, ",")),
zap.Int64("userID", userID), // zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError), // zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update notification status:", err.Error()) // return fiber.NewError(fiber.StatusInternalServerError, "Failed to update notification status:", err.Error())
} // }
return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Notification marked as read"}) // return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Notification marked as read"})
} // }
func (h *Handler) CreateAndSendNotification(c *fiber.Ctx) error { func (h *Handler) CreateAndSendNotification(c *fiber.Ctx) error {
type Request struct { type Request struct {
@ -373,9 +371,9 @@ func (h *Handler) GetUserNotification(c *fiber.Ctx) error {
} }
func (h *Handler) GetAllRecipientIDs(ctx context.Context, receiver domain.NotificationRecieverSide) ([]int64, error) { // func (h *Handler) GetAllRecipientIDs(ctx context.Context, receiver domain.NotificationRecieverSide) ([]int64, error) {
return h.notificationSvc.ListRecipientIDs(ctx, receiver) // return h.notificationSvc.ListRecipientIDs(ctx, receiver)
} // }
func (h *Handler) CountUnreadNotifications(c *fiber.Ctx) error { func (h *Handler) CountUnreadNotifications(c *fiber.Ctx) error {

View File

@ -26,23 +26,23 @@ func (h *Handler) CreateReferralCode(c *fiber.Ctx) error {
) )
return fiber.NewError(fiber.StatusInternalServerError, "Invalid user identification") return fiber.NewError(fiber.StatusInternalServerError, "Invalid user identification")
} }
referralCode, err := h.referralSvc.CreateReferralCode(c.Context(), userID, companyID.Value) // referralCode, err := h.referralSvc.CreateReferralCode(c.Context(), userID, companyID.Value)
if err != nil { // if err != nil {
h.mongoLoggerSvc.Error("Failed to create referral", // h.mongoLoggerSvc.Error("Failed to create referral",
zap.Int64("userID", userID), // zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError), // zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create referral") // return fiber.NewError(fiber.StatusInternalServerError, "Failed to create referral")
} // }
fmt.Printf("Successfully created referral!") fmt.Printf("Successfully created referral!")
res := domain.ConvertReferralCodeRes(referralCode) // res := domain.ConvertReferralCodeRes(referralCode)
return response.WriteJSON(c, fiber.StatusOK, "Referral created successfully", res, nil) return response.WriteJSON(c, fiber.StatusOK, "Referral created successfully", nil, nil)
} }
func (h *Handler) GetReferralCode(c *fiber.Ctx) error { func (h *Handler) GetReferralCode(c *fiber.Ctx) error {
@ -72,7 +72,7 @@ func (h *Handler) GetReferralCode(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user") return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user")
} }
if !user.CompanyID.Valid || user.CompanyID.Value != companyID.Value { if !user.OrganizationID.Valid || user.OrganizationID.Value != companyID.Value {
h.mongoLoggerSvc.Warn("User attempt to login to different company", h.mongoLoggerSvc.Warn("User attempt to login to different company",
zap.Int64("userID", userID), zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError), zap.Int("status_code", fiber.StatusInternalServerError),
@ -82,88 +82,24 @@ func (h *Handler) GetReferralCode(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "Failed to retrieve user") return fiber.NewError(fiber.StatusBadRequest, "Failed to retrieve user")
} }
referrals, err := h.referralSvc.GetReferralCodesByUser(c.Context(), user.ID) // referrals, err := h.referralSvc.GetReferralCodesByUser(c.Context(), user.ID)
if err != nil { // if err != nil {
h.mongoLoggerSvc.Error("Failed to get user referrals", // h.mongoLoggerSvc.Error("Failed to get user referrals",
zap.Int64("userID", userID), // zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError), // zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user referral codes") // return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user referral codes")
} // }
result := domain.ConvertReferralCodeResList(referrals) // result := domain.ConvertReferralCodeResList(referrals)
return response.WriteJSON(c, fiber.StatusOK, "Referral Code Fetched Successfully", result, nil) return response.WriteJSON(c, fiber.StatusOK, "Referral Code Fetched Successfully", nil, nil)
} }
// GetReferralStats godoc
// @Summary Get referral statistics
// @Description Retrieves referral statistics for the authenticated user
// @Tags referral
// @Accept json
// @Produce json
// @Success 200 {object} domain.ReferralStats
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Security Bearer
// @Router /api/v1/tenant/{tenant_slug}/referral/stats [get]
func (h *Handler) GetReferralStats(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid {
h.BadRequestLogger().Error("invalid company id")
return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
}
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
h.mongoLoggerSvc.Error("Invalid user ID in context",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Invalid user id")
}
user, err := h.userSvc.GetUserByID(c.Context(), userID)
if err != nil {
h.mongoLoggerSvc.Error("Failed to get user",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user")
}
if !user.CompanyID.Valid || user.CompanyID.Value != companyID.Value {
h.mongoLoggerSvc.Warn("User attempt to login to different company",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Failed to retrieve user")
}
stats, err := h.referralSvc.GetReferralStats(c.Context(), user.ID, companyID.Value)
if err != nil {
h.mongoLoggerSvc.Error("Failed to get referral stats",
zap.Int64("userID", userID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve referral stats")
}
res := domain.ConvertReferralStatsRes(stats)
return response.WriteJSON(c, fiber.StatusOK, "Referral stats retrieved successfully", res, nil)
}
// // UpdateReferralSettings godoc // // UpdateReferralSettings godoc
// // @Summary Update referral settings // // @Summary Update referral settings
// // @Description Updates referral settings (admin only) // // @Description Updates referral settings (admin only)

View File

@ -108,85 +108,85 @@ func (h *Handler) UpdateGlobalSettingList(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusOK, "setting updated", res, nil) return response.WriteJSON(c, fiber.StatusOK, "setting updated", res, nil)
} }
func (h *Handler) SaveCompanySettingList(c *fiber.Ctx) error { // func (h *Handler) SaveCompanySettingList(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64) // companyID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid { // if !companyID.Valid {
h.BadRequestLogger().Error("invalid company id") // h.BadRequestLogger().Error("invalid company id")
return fiber.NewError(fiber.StatusBadRequest, "invalid company id") // return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
} // }
var req domain.SaveSettingListReq // var req domain.SaveSettingListReq
if err := c.BodyParser(&req); err != nil { // if err := c.BodyParser(&req); err != nil {
h.mongoLoggerSvc.Info("Failed to parse SaveSettingListReq", // h.mongoLoggerSvc.Info("Failed to parse SaveSettingListReq",
zap.Int("status_code", fiber.StatusBadRequest), // zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusBadRequest, "Invalid request") // return fiber.NewError(fiber.StatusBadRequest, "Invalid request")
} // }
valErrs, ok := h.validator.Validate(c, req) // valErrs, ok := h.validator.Validate(c, req)
if !ok { // if !ok {
var errMsg string // var errMsg string
for field, msg := range valErrs { // for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg) // errMsg += fmt.Sprintf("%s: %s; ", field, msg)
} // }
h.mongoLoggerSvc.Info("Failed to validate SaveSettingListReq", // h.mongoLoggerSvc.Info("Failed to validate SaveSettingListReq",
zap.Int("status_code", fiber.StatusBadRequest), // zap.Int("status_code", fiber.StatusBadRequest),
zap.String("errMsg", errMsg), // zap.String("errMsg", errMsg),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusBadRequest, errMsg) // return fiber.NewError(fiber.StatusBadRequest, errMsg)
} // }
settingList := domain.ConvertSaveSettingListReq(req) // // settingList := domain.ConvertSaveSettingListReq(req)
err := h.settingSvc.InsertCompanySettingList(c.Context(), settingList, companyID.Value) // // err := h.settingSvc.InsertCompanySettingList(c.Context(), settingList, companyID.Value)
if err != nil { // // if err != nil {
h.mongoLoggerSvc.Info("failed to save setting", // // h.mongoLoggerSvc.Info("failed to save setting",
zap.Any("setting_list", settingList), // // zap.Any("setting_list", settingList),
zap.Int("status_code", fiber.StatusInternalServerError), // // zap.Int("status_code", fiber.StatusInternalServerError),
zap.Time("timestamp", time.Now()), // // zap.Time("timestamp", time.Now()),
) // // )
return fiber.NewError(fiber.StatusInternalServerError, "failed to save setting") // // return fiber.NewError(fiber.StatusInternalServerError, "failed to save setting")
} // // }
settingsList, err := h.settingSvc.GetOverrideSettingsList(c.Context(), companyID.Value) // settingsList, err := h.settingSvc.GetOverrideSettingsList(c.Context(), companyID.Value)
if err != nil { // if err != nil {
h.mongoLoggerSvc.Error("Failed to fetch settings", // h.mongoLoggerSvc.Error("Failed to fetch settings",
zap.Int("status_code", fiber.StatusInternalServerError), // zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get setting list:"+err.Error()) // return fiber.NewError(fiber.StatusInternalServerError, "Failed to get setting list:"+err.Error())
} // }
return response.WriteJSON(c, fiber.StatusOK, "setting updated", settingsList, nil) // return response.WriteJSON(c, fiber.StatusOK, "setting updated", settingsList, nil)
} // }
func (h *Handler) GetCompanySettingList(c *fiber.Ctx) error { // func (h *Handler) GetCompanySettingList(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64) // companyID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid { // if !companyID.Valid {
h.BadRequestLogger().Error("invalid company id") // h.BadRequestLogger().Error("invalid company id")
return fiber.NewError(fiber.StatusBadRequest, "invalid company id") // return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
} // }
settingsList, err := h.settingSvc.GetOverrideSettingsList(c.Context(), companyID.Value) // settingsList, err := h.settingSvc.GetOverrideSettingsList(c.Context(), companyID.Value)
if err != nil { // if err != nil {
h.mongoLoggerSvc.Error("Failed to fetch settings", // h.mongoLoggerSvc.Error("Failed to fetch settings",
zap.Int("status_code", fiber.StatusInternalServerError), // zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get setting list:"+err.Error()) // return fiber.NewError(fiber.StatusInternalServerError, "Failed to get setting list:"+err.Error())
} // }
res := domain.ConvertSettingListRes(settingsList) // res := domain.ConvertSettingListRes(settingsList)
return response.WriteJSON(c, fiber.StatusOK, "All Settings retrieved successfully", res, nil) // return response.WriteJSON(c, fiber.StatusOK, "All Settings retrieved successfully", res, nil)
} // }
// /api/v1/{tenant_slug}/settings/{key} // /api/v1/{tenant_slug}/settings/{key}
func (h *Handler) DeleteCompanySetting(c *fiber.Ctx) error { func (h *Handler) DeleteCompanySetting(c *fiber.Ctx) error {
@ -205,16 +205,16 @@ func (h *Handler) DeleteCompanySetting(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "setting key must be passed") return fiber.NewError(fiber.StatusBadRequest, "setting key must be passed")
} }
err := h.settingSvc.DeleteCompanySetting(c.Context(), companyID.Value, settingKey) // err := h.settingSvc.DeleteCompanySetting(c.Context(), companyID.Value, settingKey)
if err != nil { // if err != nil {
h.mongoLoggerSvc.Error("Failed to delete company override settings", // h.mongoLoggerSvc.Error("Failed to delete company override settings",
zap.Int("status_code", fiber.StatusInternalServerError), // zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete company override settings:"+err.Error()) // return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete company override settings:"+err.Error())
} // }
return response.WriteJSON(c, fiber.StatusOK, "setting deleted", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "setting deleted", nil, nil)
} }
@ -226,16 +226,16 @@ func (h *Handler) DeleteAllCompanySetting(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "invalid company id") return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
} }
err := h.settingSvc.DeleteAllCompanySetting(c.Context(), companyID.Value) // err := h.settingSvc.DeleteAllCompanySetting(c.Context(), companyID.Value)
if err != nil { // if err != nil {
h.mongoLoggerSvc.Error("Failed to delete company override settings", // h.mongoLoggerSvc.Error("Failed to delete company override settings",
zap.Int("status_code", fiber.StatusInternalServerError), // zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete company override settings:"+err.Error()) // return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete company override settings:"+err.Error())
} // }
return response.WriteJSON(c, fiber.StatusOK, "setting deleted", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "setting deleted", nil, nil)
} }

View File

@ -33,77 +33,77 @@ type CreateTransactionApproverReq struct {
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /api/v1/admin [post] // @Router /api/v1/admin [post]
func (h *Handler) CreateTransactionApprover(c *fiber.Ctx) error { // func (h *Handler) CreateTransactionApprover(c *fiber.Ctx) error {
var companyID domain.ValidInt64 // var companyID domain.ValidInt64
var req CreateTransactionApproverReq // var req CreateTransactionApproverReq
if err := c.BodyParser(&req); err != nil { // if err := c.BodyParser(&req); err != nil {
h.mongoLoggerSvc.Info("failed to parse CreateAdmin request", // h.mongoLoggerSvc.Info("failed to parse CreateAdmin request",
zap.Int64("status_code", fiber.StatusBadRequest), // zap.Int64("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request:"+err.Error())
}
valErrs, ok := h.validator.Validate(c, req)
if !ok {
var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.mongoLoggerSvc.Error("validation failed for CreateAdmin request",
zap.Int64("status_code", fiber.StatusBadRequest),
zap.Any("validation_errors", valErrs),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg)
}
// _, err := h.companySvc.GetCompanyByID(c.Context(), req.CompanyID)
// if err != nil {
// h.mongoLoggerSvc.Error("invalid company ID for CreateAdmin",
// zap.Int64("status_code", fiber.StatusInternalServerError),
// zap.Int64("company_id", req.CompanyID),
// zap.Error(err), // zap.Error(err),
// zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
// ) // )
// return fiber.NewError(fiber.StatusInternalServerError, "Company ID is invalid:"+err.Error()) // return fiber.NewError(fiber.StatusBadRequest, "Invalid request:"+err.Error())
// } // }
companyID = domain.ValidInt64{
Value: req.CompanyID,
Valid: true,
}
user := domain.CreateUserReq{ // valErrs, ok := h.validator.Validate(c, req)
FirstName: req.FirstName, // if !ok {
LastName: req.LastName, // var errMsg string
Email: req.Email, // for field, msg := range valErrs {
PhoneNumber: req.PhoneNumber, // errMsg += fmt.Sprintf("%s: %s; ", field, msg)
Password: req.Password, // }
Role: string(domain.RoleTransactionApprover), // h.mongoLoggerSvc.Error("validation failed for CreateAdmin request",
CompanyID: companyID, // zap.Int64("status_code", fiber.StatusBadRequest),
} // zap.Any("validation_errors", valErrs),
// zap.Time("timestamp", time.Now()),
// )
// return fiber.NewError(fiber.StatusBadRequest, errMsg)
// }
newUser, err := h.userSvc.CreateUser(c.Context(), user, true) // // _, err := h.companySvc.GetCompanyByID(c.Context(), req.CompanyID)
if err != nil { // // if err != nil {
h.mongoLoggerSvc.Error("failed to create admin user", // // h.mongoLoggerSvc.Error("invalid company ID for CreateAdmin",
zap.Int64("status_code", fiber.StatusInternalServerError), // // zap.Int64("status_code", fiber.StatusInternalServerError),
zap.Any("request", req), // // zap.Int64("company_id", req.CompanyID),
zap.Error(err), // // zap.Error(err),
zap.Time("timestamp", time.Now()), // // zap.Time("timestamp", time.Now()),
) // // )
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create admin:"+err.Error()) // // return fiber.NewError(fiber.StatusInternalServerError, "Company ID is invalid:"+err.Error())
} // // }
// companyID = domain.ValidInt64{
// Value: req.CompanyID,
// Valid: true,
// }
h.mongoLoggerSvc.Info("transaction_approver created successfully", // user := domain.CreateUserReq{
zap.Int64("transaction_approver_id", newUser.ID), // FirstName: req.FirstName,
zap.String("email", newUser.Email), // LastName: req.LastName,
zap.Time("timestamp", time.Now()), // Email: req.Email,
) // PhoneNumber: req.PhoneNumber,
// Password: req.Password,
// Role: string(domain.RoleTransactionApprover),
// OrganizationID: companyID,
// }
return response.WriteJSON(c, fiber.StatusOK, "Transaction Approver created successfully", nil, nil) // newUser, err := h.userSvc.CreateUser(c.Context(), user, true)
} // if err != nil {
// h.mongoLoggerSvc.Error("failed to create admin user",
// zap.Int64("status_code", fiber.StatusInternalServerError),
// zap.Any("request", req),
// zap.Error(err),
// zap.Time("timestamp", time.Now()),
// )
// return fiber.NewError(fiber.StatusInternalServerError, "Failed to create admin:"+err.Error())
// }
// h.mongoLoggerSvc.Info("transaction_approver created successfully",
// zap.Int64("transaction_approver_id", newUser.ID),
// zap.String("email", newUser.Email),
// zap.Time("timestamp", time.Now()),
// )
// return response.WriteJSON(c, fiber.StatusOK, "Transaction Approver created successfully", nil, nil)
// }
type TransactionApproverRes struct { type TransactionApproverRes struct {
ID int64 `json:"id"` ID int64 `json:"id"`
@ -134,144 +134,144 @@ type TransactionApproverRes struct {
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /api/v1/t-approver [get] // @Router /api/v1/t-approver [get]
func (h *Handler) GetAllTransactionApprovers(c *fiber.Ctx) error { // func (h *Handler) GetAllTransactionApprovers(c *fiber.Ctx) error {
role := c.Locals("role").(domain.Role) // role := c.Locals("role").(domain.Role)
companyID := c.Locals("company_id").(domain.ValidInt64) // companyID := c.Locals("company_id").(domain.ValidInt64)
searchQuery := c.Query("query") // searchQuery := c.Query("query")
searchString := domain.ValidString{ // searchString := domain.ValidString{
Value: searchQuery, // Value: searchQuery,
Valid: searchQuery != "", // Valid: searchQuery != "",
} // }
createdBeforeQuery := c.Query("created_before") // createdBeforeQuery := c.Query("created_before")
var createdBefore domain.ValidTime // var createdBefore domain.ValidTime
if createdBeforeQuery != "" { // if createdBeforeQuery != "" {
createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery) // createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
if err != nil { // if err != nil {
h.logger.Info("invalid start_time format", "error", err) // h.logger.Info("invalid start_time format", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid start_time format") // return fiber.NewError(fiber.StatusBadRequest, "Invalid start_time format")
} // }
createdBefore = domain.ValidTime{ // createdBefore = domain.ValidTime{
Value: createdBeforeParsed, // Value: createdBeforeParsed,
Valid: true, // Valid: true,
} // }
} // }
createdAfterQuery := c.Query("created_after") // createdAfterQuery := c.Query("created_after")
var createdAfter domain.ValidTime // var createdAfter domain.ValidTime
if createdAfterQuery != "" { // if createdAfterQuery != "" {
createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery) // createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
if err != nil { // if err != nil {
h.logger.Info("invalid start_time format", "error", err) // h.logger.Info("invalid start_time format", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid start_time format") // return fiber.NewError(fiber.StatusBadRequest, "Invalid start_time format")
} // }
createdAfter = domain.ValidTime{ // createdAfter = domain.ValidTime{
Value: createdAfterParsed, // Value: createdAfterParsed,
Valid: true, // Valid: true,
} // }
} // }
var companyIDFilter domain.ValidInt64 // var companyIDFilter domain.ValidInt64
if role == domain.RoleSuperAdmin { // if role == domain.RoleSuperAdmin {
companyIDQuery := int64(c.QueryInt("company_id")) // companyIDQuery := int64(c.QueryInt("company_id"))
companyIDFilter = domain.ValidInt64{ // companyIDFilter = domain.ValidInt64{
Value: companyIDQuery, // Value: companyIDQuery,
Valid: companyIDQuery != 0, // Valid: companyIDQuery != 0,
} // }
} else { // } else {
if !companyID.Valid { // if !companyID.Valid {
h.logger.Info("invalid companyID") // h.logger.Info("invalid companyID")
return fiber.NewError(fiber.StatusBadRequest, "Unable to get company ID") // return fiber.NewError(fiber.StatusBadRequest, "Unable to get company ID")
} // }
companyIDFilter = companyID // companyIDFilter = companyID
} // }
filter := domain.UserFilter{ // filter := domain.UserFilter{
Role: string(domain.RoleTransactionApprover), // Role: string(domain.RoleTransactionApprover),
CompanyID: companyIDFilter, // OrganizationID: companyIDFilter,
Page: domain.ValidInt{ // Page: domain.ValidInt{
Value: c.QueryInt("page", 1) - 1, // Value: c.QueryInt("page", 1) - 1,
Valid: true, // Valid: true,
}, // },
PageSize: domain.ValidInt{ // PageSize: domain.ValidInt{
Value: c.QueryInt("page_size", 10), // Value: c.QueryInt("page_size", 10),
Valid: true, // Valid: true,
}, // },
Query: searchString, // Query: searchString,
CreatedBefore: createdBefore, // CreatedBefore: createdBefore,
CreatedAfter: createdAfter, // CreatedAfter: createdAfter,
} // }
valErrs, ok := h.validator.Validate(c, filter) // valErrs, ok := h.validator.Validate(c, filter)
if !ok { // if !ok {
var errMsg string // var errMsg string
for field, msg := range valErrs { // for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg) // errMsg += fmt.Sprintf("%s: %s; ", field, msg)
} // }
h.mongoLoggerSvc.Info("invalid filter values in GetAllAdmins request", // h.mongoLoggerSvc.Info("invalid filter values in GetAllAdmins request",
zap.Int("status_code", fiber.StatusBadRequest), // zap.Int("status_code", fiber.StatusBadRequest),
zap.Any("validation_errors", valErrs), // zap.Any("validation_errors", valErrs),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusBadRequest, errMsg) // return fiber.NewError(fiber.StatusBadRequest, errMsg)
} // }
users, total, err := h.userSvc.GetAllUsers(c.Context(), filter) // users, total, err := h.userSvc.GetAllUsers(c.Context(), filter)
if err != nil { // if err != nil {
h.mongoLoggerSvc.Error("failed to get users from user service", // h.mongoLoggerSvc.Error("failed to get users from user service",
zap.Int("status_code", fiber.StatusInternalServerError), // zap.Int("status_code", fiber.StatusInternalServerError),
zap.Any("filter", filter), // zap.Any("filter", filter),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get users"+err.Error()) // return fiber.NewError(fiber.StatusInternalServerError, "Failed to get users"+err.Error())
} // }
result := make([]TransactionApproverRes, len(users)) // result := make([]TransactionApproverRes, len(users))
for index, admin := range users { // for index, admin := range users {
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), admin.ID) // lastLogin, err := h.authSvc.GetLastLogin(c.Context(), admin.ID)
if err != nil { // if err != nil {
if err == authentication.ErrRefreshTokenNotFound { // if err == authentication.ErrRefreshTokenNotFound {
lastLogin = &admin.CreatedAt // lastLogin = &admin.CreatedAt
} else { // } else {
h.mongoLoggerSvc.Error("failed to get last login for admin", // h.mongoLoggerSvc.Error("failed to get last login for admin",
zap.Int("status_code", fiber.StatusInternalServerError), // zap.Int("status_code", fiber.StatusInternalServerError),
zap.Int64("admin_id", admin.ID), // zap.Int64("admin_id", admin.ID),
zap.Error(err), // zap.Error(err),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login"+err.Error()) // return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login"+err.Error())
} // }
} // }
result[index] = TransactionApproverRes{ // result[index] = TransactionApproverRes{
ID: admin.ID, // ID: admin.ID,
FirstName: admin.FirstName, // FirstName: admin.FirstName,
LastName: admin.LastName, // LastName: admin.LastName,
Email: admin.Email, // Email: admin.Email,
PhoneNumber: admin.PhoneNumber, // PhoneNumber: admin.PhoneNumber,
Role: admin.Role, // Role: admin.Role,
EmailVerified: admin.EmailVerified, // EmailVerified: admin.EmailVerified,
PhoneVerified: admin.PhoneVerified, // PhoneVerified: admin.PhoneVerified,
CreatedAt: admin.CreatedAt, // CreatedAt: admin.CreatedAt,
UpdatedAt: admin.UpdatedAt, // UpdatedAt: admin.UpdatedAt,
SuspendedAt: admin.SuspendedAt, // SuspendedAt: admin.SuspendedAt,
Suspended: admin.Suspended, // Suspended: admin.Suspended,
LastLogin: *lastLogin, // LastLogin: *lastLogin,
} // }
} // }
h.mongoLoggerSvc.Info("approvers retrieved successfully", // h.mongoLoggerSvc.Info("approvers retrieved successfully",
zap.Int("status_code", fiber.StatusOK), // zap.Int("status_code", fiber.StatusOK),
zap.Int("count", len(result)), // zap.Int("count", len(result)),
zap.Int("page", filter.Page.Value+1), // zap.Int("page", filter.Page.Value+1),
zap.Time("timestamp", time.Now()), // zap.Time("timestamp", time.Now()),
) // )
return response.WritePaginatedJSON(c, fiber.StatusOK, "Admins retrieved successfully", result, nil, filter.Page.Value, int(total)) // return response.WritePaginatedJSON(c, fiber.StatusOK, "Admins retrieved successfully", result, nil, filter.Page.Value, int(total))
} // }
// GetAdminByID godoc // GetAdminByID godoc
// @Summary Get admin by id // @Summary Get admin by id
@ -404,7 +404,7 @@ func (h *Handler) UpdateTransactionApprover(c *fiber.Ctx) error {
} }
err = h.userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{ err = h.userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{
UserId: ApproverID, UserID: ApproverID,
FirstName: domain.ValidString{ FirstName: domain.ValidString{
Value: req.FirstName, Value: req.FirstName,
Valid: req.FirstName != "", Valid: req.FirstName != "",

View File

@ -50,7 +50,7 @@ func (h *Handler) GetTenantSlugByToken(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user profile:"+err.Error()) return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user profile:"+err.Error())
} }
if !user.CompanyID.Valid { if !user.OrganizationID.Valid {
if user.Role == domain.RoleSuperAdmin { if user.Role == domain.RoleSuperAdmin {
return fiber.NewError(fiber.StatusBadRequest, "Role Super-Admin Doesn't have a company-id") return fiber.NewError(fiber.StatusBadRequest, "Role Super-Admin Doesn't have a company-id")
} }
@ -75,7 +75,7 @@ func (h *Handler) GetTenantSlugByToken(c *fiber.Ctx) error {
// } // }
res := GetTenantSlugByToken{ res := GetTenantSlugByToken{
Slug: strconv.FormatInt(user.CompanyID.Value,10), Slug: strconv.FormatInt(user.OrganizationID.Value, 10),
} }
return response.WriteJSON(c, fiber.StatusOK, "Tenant Slug retrieved successfully", res, nil) return response.WriteJSON(c, fiber.StatusOK, "Tenant Slug retrieved successfully", res, nil)
@ -102,11 +102,11 @@ type CheckPhoneEmailExistRes struct {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /api/v1/{tenant_slug}/user/checkPhoneEmailExist [post] // @Router /api/v1/{tenant_slug}/user/checkPhoneEmailExist [post]
func (h *Handler) CheckPhoneEmailExist(c *fiber.Ctx) error { func (h *Handler) CheckPhoneEmailExist(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64) // companyID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid { // if !companyID.Valid {
h.BadRequestLogger().Error("invalid company id") // h.BadRequestLogger().Error("invalid company id")
return fiber.NewError(fiber.StatusBadRequest, "invalid company id") // return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
} // }
var req CheckPhoneEmailExistReq var req CheckPhoneEmailExistReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.mongoLoggerSvc.Info("Failed to parse CheckPhoneEmailExist request", h.mongoLoggerSvc.Info("Failed to parse CheckPhoneEmailExist request",
@ -125,7 +125,7 @@ func (h *Handler) CheckPhoneEmailExist(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, errMsg) return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
emailExist, phoneExist, err := h.userSvc.CheckPhoneEmailExist(c.Context(), req.PhoneNumber, req.Email, companyID) emailExist, phoneExist, err := h.userSvc.CheckPhoneEmailExist(c.Context(), req.PhoneNumber, req.Email, domain.ValidInt64{})
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("Failed to check phone/email existence", h.mongoLoggerSvc.Error("Failed to check phone/email existence",
zap.Any("request", req), zap.Any("request", req),
@ -160,11 +160,11 @@ type RegisterCodeReq struct {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /api/v1/{tenant_slug}/user/sendRegisterCode [post] // @Router /api/v1/{tenant_slug}/user/sendRegisterCode [post]
func (h *Handler) SendRegisterCode(c *fiber.Ctx) error { func (h *Handler) SendRegisterCode(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64) // companyID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid { // if !companyID.Valid {
h.BadRequestLogger().Error("invalid company id") // h.BadRequestLogger().Error("invalid company id")
return fiber.NewError(fiber.StatusBadRequest, "invalid company id") // return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
} // }
var req RegisterCodeReq var req RegisterCodeReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.mongoLoggerSvc.Info("Failed to parse SendRegisterCode request", h.mongoLoggerSvc.Info("Failed to parse SendRegisterCode request",
@ -195,7 +195,7 @@ func (h *Handler) SendRegisterCode(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided") return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided")
} }
if err := h.userSvc.SendRegisterCode(c.Context(), medium, sentTo, domain.AfroMessage, companyID); err != nil { if err := h.userSvc.SendRegisterCode(c.Context(), medium, sentTo, domain.AfroMessage, domain.ValidInt64{}); err != nil {
h.mongoLoggerSvc.Error("Failed to send register code", h.mongoLoggerSvc.Error("Failed to send register code",
zap.String("Medium", string(medium)), zap.String("Medium", string(medium)),
zap.String("Send To", string(sentTo)), zap.String("Send To", string(sentTo)),
@ -231,11 +231,6 @@ type RegisterUserReq struct {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /api/v1/{tenant_slug}/user/register [post] // @Router /api/v1/{tenant_slug}/user/register [post]
func (h *Handler) RegisterUser(c *fiber.Ctx) error { func (h *Handler) RegisterUser(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid {
h.BadRequestLogger().Error("invalid company id")
return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
}
var req RegisterUserReq var req RegisterUserReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.mongoLoggerSvc.Info("Failed to parse RegisterUser request", h.mongoLoggerSvc.Info("Failed to parse RegisterUser request",
@ -262,8 +257,8 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
Otp: req.Otp, Otp: req.Otp,
ReferralCode: req.ReferralCode, ReferralCode: req.ReferralCode,
OtpMedium: domain.OtpMediumEmail, OtpMedium: domain.OtpMediumEmail,
CompanyID: companyID, OrganizationID: domain.ValidInt64{},
Role: string(domain.RoleCustomer), Role: string(domain.RoleStudent),
} }
medium, err := getMedium(req.Email, req.PhoneNumber) medium, err := getMedium(req.Email, req.PhoneNumber)
if err != nil { if err != nil {
@ -279,7 +274,7 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
user.OtpMedium = medium user.OtpMedium = medium
newUser, err := h.userSvc.RegisterUser(c.Context(), user) _, err = h.userSvc.RegisterUser(c.Context(), user)
if err != nil { if err != nil {
if errors.Is(err, domain.ErrOtpAlreadyUsed) { if errors.Is(err, domain.ErrOtpAlreadyUsed) {
return fiber.NewError(fiber.StatusBadRequest, "Otp already used") return fiber.NewError(fiber.StatusBadRequest, "Otp already used")
@ -303,44 +298,6 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusInternalServerError, "failed to register user:"+err.Error()) return fiber.NewError(fiber.StatusInternalServerError, "failed to register user:"+err.Error())
} }
// _, err = h.walletSvc.CreateCustomerWallet(c.Context(), newUser.ID)
// if err != nil {
// h.mongoLoggerSvc.Error("Failed to create wallet for user",
// zap.Int64("userID", newUser.ID),
// zap.Int("status_code", fiber.StatusInternalServerError),
// zap.Error(err),
// zap.Time("timestamp", time.Now()),
// )
// return fiber.NewError(fiber.StatusInternalServerError, "Failed to create user wallet:"+err.Error())
// }
if req.ReferralCode != "" {
err = h.referralSvc.ProcessReferral(c.Context(), newUser.ID, req.ReferralCode, companyID.Value)
if err != nil {
h.mongoLoggerSvc.Error("Failed to process referral during registration",
zap.String("phone", req.PhoneNumber),
zap.String("code", req.ReferralCode),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
}
}
// TODO: Remove later
// _, err = h.walletSvc.AddToWallet(
// c.Context(), newWallet.RegularID, domain.ToCurrency(10000.0), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{},
// "Added 10000.0 to wallet only as test for deployment")
if err != nil {
h.mongoLoggerSvc.Error("Failed to update wallet for user",
zap.Int64("userID", newUser.ID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update user wallet:"+err.Error())
}
return response.WriteJSON(c, fiber.StatusOK, "Registration successful", nil, nil) return response.WriteJSON(c, fiber.StatusOK, "Registration successful", nil, nil)
} }
@ -424,11 +381,11 @@ func (h *Handler) SendResetCode(c *fiber.Ctx) error {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /api/v1/{tenant_slug}/user/sendResetCode [post] // @Router /api/v1/{tenant_slug}/user/sendResetCode [post]
func (h *Handler) SendTenantResetCode(c *fiber.Ctx) error { func (h *Handler) SendTenantResetCode(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64) // companyID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid { // if !companyID.Valid {
h.BadRequestLogger().Error("invalid company id") // h.BadRequestLogger().Error("invalid company id")
return fiber.NewError(fiber.StatusBadRequest, "invalid company id") // return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
} // }
var req ResetCodeReq var req ResetCodeReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.mongoLoggerSvc.Info("Failed to parse SendResetCode request", h.mongoLoggerSvc.Info("Failed to parse SendResetCode request",
@ -465,7 +422,7 @@ func (h *Handler) SendTenantResetCode(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided") return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided")
} }
if err := h.userSvc.SendResetCode(c.Context(), medium, sentTo, domain.AfroMessage, companyID); err != nil { if err := h.userSvc.SendResetCode(c.Context(), medium, sentTo, domain.AfroMessage, domain.ValidInt64{}); err != nil {
h.mongoLoggerSvc.Error("Failed to send reset code", h.mongoLoggerSvc.Error("Failed to send reset code",
zap.String("medium", string(medium)), zap.String("medium", string(medium)),
zap.String("sentTo", string(sentTo)), zap.String("sentTo", string(sentTo)),
@ -562,11 +519,11 @@ func (h *Handler) ResetPassword(c *fiber.Ctx) error {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /api/v1/{tenant_slug}/user/resetPassword [post] // @Router /api/v1/{tenant_slug}/user/resetPassword [post]
func (h *Handler) ResetTenantPassword(c *fiber.Ctx) error { func (h *Handler) ResetTenantPassword(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64) // companyID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid { // if !companyID.Valid {
h.BadRequestLogger().Error("invalid company id") // h.BadRequestLogger().Error("invalid company id")
return fiber.NewError(fiber.StatusBadRequest, "invalid company id") // return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
} // }
var req ResetPasswordReq var req ResetPasswordReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
@ -604,7 +561,7 @@ func (h *Handler) ResetTenantPassword(c *fiber.Ctx) error {
Password: req.Password, Password: req.Password,
Otp: req.Otp, Otp: req.Otp,
OtpMedium: medium, OtpMedium: medium,
CompanyID: companyID.Value, OrganizationID: 1,
} }
if err := h.userSvc.ResetPassword(c.Context(), resetReq); err != nil { if err := h.userSvc.ResetPassword(c.Context(), resetReq); err != nil {
@ -852,9 +809,14 @@ func (h *Handler) SearchUserByNameOrPhone(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, errMsg) return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
companyID := c.Locals("company_id").(domain.ValidInt64) // companyID := c.Locals("company_id").(domain.ValidInt64)
// strCompanyID := fmt.Sprintf("%v", companyID)
role, err := strconv.ParseInt(string(*req.Role), 10, 64)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "failed to get users"+err.Error())
}
users, err := h.userSvc.SearchUserByNameOrPhone(c.Context(), req.SearchString, req.Role, companyID) users, err := h.userSvc.SearchUserByNameOrPhone(c.Context(), req.SearchString, &role, nil)
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("Failed to get user by name or phone", h.mongoLoggerSvc.Error("Failed to get user by name or phone",
zap.Any("request", req), zap.Any("request", req),
@ -912,11 +874,11 @@ func (h *Handler) SearchUserByNameOrPhone(c *fiber.Ctx) error {
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /api/v1/{tenant_slug}/user/search [post] // @Router /api/v1/{tenant_slug}/user/search [post]
func (h *Handler) SearchCompanyUserByNameOrPhone(c *fiber.Ctx) error { func (h *Handler) SearchCompanyUserByNameOrPhone(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64) // companyID := c.Locals("company_id").(domain.ValidInt64)
if !companyID.Valid { // if !companyID.Valid {
h.BadRequestLogger().Error("invalid company id") // h.BadRequestLogger().Error("invalid company id")
return fiber.NewError(fiber.StatusBadRequest, "invalid company id") // return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
} // }
var req SearchUserByNameOrPhoneReq var req SearchUserByNameOrPhoneReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
@ -937,7 +899,13 @@ func (h *Handler) SearchCompanyUserByNameOrPhone(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, errMsg) return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
users, err := h.userSvc.SearchUserByNameOrPhone(c.Context(), req.SearchString, req.Role, companyID) // strCompanyID := fmt.Sprintf("%v", companyID)
role, err := strconv.ParseInt(string(*req.Role), 10, 64)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "failed to get users"+err.Error())
}
users, err := h.userSvc.SearchUserByNameOrPhone(c.Context(), req.SearchString, &role, nil)
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("Failed to get user by name or phone", h.mongoLoggerSvc.Error("Failed to get user by name or phone",
zap.Any("request", req), zap.Any("request", req),
@ -1162,43 +1130,3 @@ func (h *Handler) UpdateUserSuspend(c *fiber.Ctx) error {
} }
return response.WriteJSON(c, fiber.StatusOK, "User suspend status updated successfully", res, nil) return response.WriteJSON(c, fiber.StatusOK, "User suspend status updated successfully", res, nil)
} }
// GetBetByUserID godoc
// @Summary Gets user bets
// @Description Gets user bets
// @Tags user
// @Accept json
// @Produce json
// @Success 200 {array} domain.BetRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/{tenant_slug}/user/bets [get]
// func (h *Handler) GetBetByUserID(c *fiber.Ctx) error {
// userID, ok := c.Locals("user_id").(int64)
// if !ok || userID == 0 {
// h.mongoLoggerSvc.Error("Invalid user ID in context",
// zap.Int64("userID", userID),
// zap.Int("status_code", fiber.StatusInternalServerError),
// zap.Time("timestamp", time.Now()),
// )
// return fiber.NewError(fiber.StatusInternalServerError, "Invalid user identification")
// }
// // bets, err := h.betSvc.GetBetByUserID(c.Context(), userID)
// // if err != nil {
// // h.mongoLoggerSvc.Error("Failed to get bets",
// // zap.Int64("userID", userID),
// // zap.Int("status_code", fiber.StatusInternalServerError),
// // zap.Error(err),
// // zap.Time("timestamp", time.Now()),
// // )
// // return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve bets:"+err.Error())
// // }
// // res := make([]domain.BetRes, len(bets))
// // for i, bet := range bets {
// // res[i] = domain.ConvertBet(bet)
// // }
// return response.WriteJSON(c, fiber.StatusOK, "User bets retrieved successfully", res, nil)
// }

View File

@ -23,17 +23,6 @@ type UserClaim struct {
CompanyID domain.NullJwtInt64 CompanyID domain.NullJwtInt64
} }
type PopOKClaim struct {
jwt.RegisteredClaims
UserID int64 `json:"user_id"`
Username string `json:"username"`
Currency string `json:"currency"`
Lang string `json:"lang"`
Mode string `json:"mode"`
SessionID string `json:"session_id"`
CompanyID domain.NullJwtInt64 `json:"company_id"`
}
type JwtConfig struct { type JwtConfig struct {
JwtAccessKey string JwtAccessKey string
JwtAccessExpiry int JwtAccessExpiry int
@ -42,9 +31,9 @@ type JwtConfig struct {
func CreateJwt(userId int64, Role domain.Role, CompanyID domain.ValidInt64, key string, expiry int) (string, error) { func CreateJwt(userId int64, Role domain.Role, CompanyID domain.ValidInt64, key string, expiry int) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, UserClaim{ token := jwt.NewWithClaims(jwt.SigningMethodHS256, UserClaim{
RegisteredClaims: jwt.RegisteredClaims{ RegisteredClaims: jwt.RegisteredClaims{
Issuer: "fortune-bet", Issuer: "yimaru.com",
IssuedAt: jwt.NewNumericDate(time.Now()), IssuedAt: jwt.NewNumericDate(time.Now()),
Audience: jwt.ClaimStrings{"api.fortunebets.net"}, Audience: jwt.ClaimStrings{"api.yimaru.com"},
NotBefore: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()),
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expiry) * time.Second)), ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expiry) * time.Second)),
}, },
@ -59,29 +48,6 @@ func CreateJwt(userId int64, Role domain.Role, CompanyID domain.ValidInt64, key
return jwtToken, err return jwtToken, err
} }
func CreatePopOKJwt(userID int64, CompanyID domain.ValidInt64, username, currency, lang, mode, sessionID, key string, expiry time.Duration) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, PopOKClaim{
RegisteredClaims: jwt.RegisteredClaims{
Issuer: "fortune-bet",
Audience: jwt.ClaimStrings{"popokgaming.com"},
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
ExpiresAt: jwt.NewNumericDate(time.Now().Add(expiry)),
},
UserID: userID,
Username: username, // ✅ Must be a valid string
Currency: currency,
Lang: lang,
Mode: mode,
SessionID: sessionID,
CompanyID: domain.NullJwtInt64{
Value: CompanyID.Value,
Valid: CompanyID.Valid,
},
})
return token.SignedString([]byte(key))
}
func ParseJwt(jwtToken string, key string) (*UserClaim, error) { func ParseJwt(jwtToken string, key string) (*UserClaim, error) {
token, err := jwt.ParseWithClaims(jwtToken, &UserClaim{}, func(token *jwt.Token) (interface{}, error) { token, err := jwt.ParseWithClaims(jwtToken, &UserClaim{}, func(token *jwt.Token) (interface{}, error) {
return []byte(key), nil return []byte(key), nil
@ -101,22 +67,3 @@ func ParseJwt(jwtToken string, key string) (*UserClaim, error) {
} }
return nil, errors.New("invalid token claims") return nil, errors.New("invalid token claims")
} }
func ParsePopOKJwt(jwtToken string, key string) (*PopOKClaim, error) {
token, err := jwt.ParseWithClaims(jwtToken, &PopOKClaim{}, func(token *jwt.Token) (interface{}, error) {
return []byte(key), nil
})
if err != nil {
if errors.Is(err, jwt.ErrTokenExpired) {
return nil, ErrExpiredToken
}
if errors.Is(err, jwt.ErrTokenMalformed) {
return nil, ErrMalformedToken
}
return nil, err
}
if claims, ok := token.Claims.(*PopOKClaim); ok && token.Valid {
return claims, nil
}
return nil, errors.New("invalid PopOK token claims")
}

View File

@ -57,7 +57,6 @@ package jwtutil
// // Verify that the claims match the user and other values // // Verify that the claims match the user and other values
// assert.Equal(t, strconv.Itoa(int(user.ID)), claims.UserId, "User ID should match") // assert.Equal(t, strconv.Itoa(int(user.ID)), claims.UserId, "User ID should match")
// assert.Equal(t, "github.com/lafetz/snippitstash", claims.Issuer, "Issuer should match") // assert.Equal(t, "github.com/lafetz/snippitstash", claims.Issuer, "Issuer should match")
// assert.Equal(t, "fortune.com", claims.Audience[0], "Audience should match")
// assert.True(t, claims.ExpiresAt.Time.After(time.Now()), "Token should not be expired yet") // assert.True(t, claims.ExpiresAt.Time.After(time.Now()), "Token should not be expired yet")
// // Ensure the parsing fails when using an invalid token // // Ensure the parsing fails when using an invalid token

View File

@ -129,7 +129,7 @@ func (a *App) SuperAdminOnly(c *fiber.Ctx) error {
func (a *App) CompanyOnly(c *fiber.Ctx) error { func (a *App) CompanyOnly(c *fiber.Ctx) error {
userID := c.Locals("user_id").(int64) userID := c.Locals("user_id").(int64)
userRole := c.Locals("role").(domain.Role) userRole := c.Locals("role").(domain.Role)
if userRole == domain.RoleCustomer { if userRole == domain.RoleStudent {
a.mongoLoggerSvc.Warn("Attempt to access restricted CompanyOnly route", a.mongoLoggerSvc.Warn("Attempt to access restricted CompanyOnly route",
zap.Int64("userID", userID), zap.Int64("userID", userID),
zap.String("role", string(userRole)), zap.String("role", string(userRole)),
@ -159,7 +159,7 @@ func (a *App) OnlyAdminAndAbove(c *fiber.Ctx) error {
func (a *App) OnlyBranchManagerAndAbove(c *fiber.Ctx) error { func (a *App) OnlyBranchManagerAndAbove(c *fiber.Ctx) error {
userID := c.Locals("user_id").(int64) userID := c.Locals("user_id").(int64)
userRole := c.Locals("role").(domain.Role) userRole := c.Locals("role").(domain.Role)
if userRole != domain.RoleSuperAdmin && userRole != domain.RoleAdmin && userRole != domain.RoleBranchManager { if userRole != domain.RoleSuperAdmin && userRole != domain.RoleAdmin {
a.mongoLoggerSvc.Warn("Attempt to access restricted OnlyBranchMangerAndAbove route", a.mongoLoggerSvc.Warn("Attempt to access restricted OnlyBranchMangerAndAbove route",
zap.Int64("userID", userID), zap.Int64("userID", userID),
zap.String("role", string(userRole)), zap.String("role", string(userRole)),

View File

@ -18,7 +18,6 @@ func (a *App) initAppRoutes() {
a.settingSvc, a.settingSvc,
a.NotidicationStore, a.NotidicationStore,
a.validator, a.validator,
a.referralSvc,
a.recommendationSvc, a.recommendationSvc,
a.userSvc, a.userSvc,
a.transactionSvc, a.transactionSvc,
@ -70,20 +69,12 @@ func (a *App) initAppRoutes() {
// Get S // Get S
groupV1.Get("/tenant", a.authMiddleware, h.GetTenantSlugByToken) groupV1.Get("/tenant", a.authMiddleware, h.GetTenantSlugByToken)
//Direct_deposit
// groupV1.Post("/direct-deposits", a.authMiddleware, h.CreateDirectDeposit)
// groupV1.Post("/direct-deposits/:depositID/approve", a.authMiddleware, h.ApproveDirectDeposit)
// groupV1.Post("/direct-deposits/:depositID/reject", a.authMiddleware, h.RejectDirectDeposit)
// groupV1.Get("/direct-deposits", a.authMiddleware, h.GetDirectDepositsByStatus)
// groupV1.Get("/direct-deposits/:depositID", a.authMiddleware, h.GetDirectDepositByID)
// groupV1.Delete("/direct-deposits/:depositID", a.authMiddleware, h.DeleteDirectDeposit)
// Swagger // Swagger
a.fiber.Get("/swagger/*", fiberSwagger.FiberWrapHandler()) a.fiber.Get("/swagger/*", fiberSwagger.FiberWrapHandler())
groupV1.Get("/", func(c *fiber.Ctx) error { groupV1.Get("/", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{ return c.JSON(fiber.Map{
"message": "FortuneBet API V1", "message": "Welcome to Yimaru Backend API v1",
"version": "1.0.1", "version": "1.0.1",
}) })
}) })
@ -128,23 +119,7 @@ func (a *App) initAppRoutes() {
// groupV1.Post("/arifpay/b2c/transfer", a.authMiddleware, h.ExecuteArifpayB2CTransfer) // groupV1.Post("/arifpay/b2c/transfer", a.authMiddleware, h.ExecuteArifpayB2CTransfer)
// groupV1.Post("/arifpay/transaction-id/verify-transaction", a.authMiddleware, h.ArifpayVerifyByTransactionIDHandler) // groupV1.Post("/arifpay/transaction-id/verify-transaction", a.authMiddleware, h.ArifpayVerifyByTransactionIDHandler)
// groupV1.Get("/arifpay/session-id/verify-transaction/:session_id", a.authMiddleware, h.ArifpayVerifyBySessionIDHandler) // groupV1.Get("/arifpay/session-id/verify-transaction/:session_id", a.authMiddleware, h.ArifpayVerifyBySessionIDHandler)
// groupV1.Get("/arifpay/payment-methods", a.authMiddleware, h.GetArifpayPaymentMethodsHandler) // groupV1.Get("/arifpay/payment-methods", a.authMiddleware, h.GetArifpayPaymentMethodsHandler
//Telebirr
// groupV1.Post("/telebirr/init-payment", a.authMiddleware, h.CreateTelebirrPaymentHandler)
// groupV1.Post("/telebirr/callback", h.HandleTelebirrCallback)
//Santimpay
// groupV1.Post("/santimpay/init-payment", a.authMiddleware, h.InititateSantimPayPaymentHandler)
// groupV1.Post("/santimpay/callback", h.ProcessSantimPayCallbackHandler)
// groupV1.Post("/santimpay/direct-payment", a.authMiddleware, h.ProcessSantimPayDirectPaymentHandler)
// groupV1.Get("/santimpay/b2c/partners", h.GetSantimPayB2CPartnersHandler)
// groupV1.Post("/santimpay/b2c/withdraw", a.authMiddleware, h.ProcessSantimPayB2CWithdrawalHandler)
// groupV1.Post("/santimpay/transaction/verify", a.authMiddleware, h.CheckSantimPayTransactionStatusHandler)
// groupV1.Post("/arifpay/b2c/transfer", a.authMiddleware, h.B2CTransferHandler)
// groupV1.Post("/arifpay/transaction-id/verify-transaction", a.authMiddleware, h.ArifpayVerifyByTransactionIDHandler)
// groupV1.Get("/arifpay/session-id/verify-transaction/:session_id", a.authMiddleware, h.ArifpayVerifyBySessionIDHandler)
// User Routes // User Routes
groupV1.Post("/user/resetPassword", h.ResetPassword) groupV1.Post("/user/resetPassword", h.ResetPassword)
@ -159,272 +134,26 @@ func (a *App) initAppRoutes() {
groupV1.Get("/user/admin-profile", a.authMiddleware, h.AdminProfile) groupV1.Get("/user/admin-profile", a.authMiddleware, h.AdminProfile)
tenant.Get("/user/customer-profile", a.authMiddleware, h.CustomerProfile) tenant.Get("/user/customer-profile", a.authMiddleware, h.CustomerProfile)
// tenant.Get("/user/bets", a.authMiddleware, h.GetBetByUserID)
groupV1.Get("/user/single/:id", a.authMiddleware, h.GetUserByID) groupV1.Get("/user/single/:id", a.authMiddleware, h.GetUserByID)
groupV1.Post("/user/suspend", a.authMiddleware, h.UpdateUserSuspend) groupV1.Post("/user/suspend", a.authMiddleware, h.UpdateUserSuspend)
groupV1.Delete("/user/delete/:id", a.authMiddleware, h.DeleteUser) groupV1.Delete("/user/delete/:id", a.authMiddleware, h.DeleteUser)
groupV1.Post("/user/search", a.authMiddleware, h.SearchUserByNameOrPhone) groupV1.Post("/user/search", a.authMiddleware, h.SearchUserByNameOrPhone)
// tenant.Get("/user/wallet", a.authMiddleware, h.GetCustomerWallet)
// Referral Routes
tenant.Post("/referral/create", a.authMiddleware, h.CreateReferralCode)
tenant.Get("/referral/code", a.authMiddleware, h.GetReferralCode)
tenant.Get("/referral/stats", a.authMiddleware, h.GetReferralStats)
// groupV1.Post("/referral/settings", a.authMiddleware, h.CreateReferralSettings)
// groupV1.Get("/referral/settings", a.authMiddleware, h.GetReferralSettings)
// groupV1.Patch("/referral/settings", a.authMiddleware, h.UpdateReferralSettings)
// Raffle Routes
// tenant.Get("/raffle/list", h.GetTenantRaffles)
// a.fiber.Get("/raffle/standing/:id/:limit", h.GetRaffleStanding) //This needs to be accessible by non-login user
// a.fiber.Post("/raffle/create", a.authMiddleware, h.CreateRaffle)
// a.fiber.Post("/raffle/add-filter", a.authMiddleware, h.AddRaffleFilter)
// a.fiber.Get("/raffle/delete/:id", a.authMiddleware, h.DeleteRaffle)
// a.fiber.Get("/raffle/company/:id", a.authMiddleware, h.GetRafflesOfCompany)
// a.fiber.Get("raffle/winners/:id/:limit", a.authMiddleware, h.GetRaffleWinners)
// a.fiber.Post("/raffle-ticket/create", a.authMiddleware, h.CreateRaffleTicket)
// a.fiber.Get("/raffle-ticket/:id", a.authMiddleware, h.GetUserRaffleTickets)
// a.fiber.Get("/raffle-ticket/suspend/:id", a.authMiddleware, h.SuspendRaffleTicket)
// a.fiber.Get("/raffle-ticket/unsuspend/:id", a.authMiddleware, h.UnSuspendRaffleTicket)
// Bonus Routes
// tenant.Get("/bonus", a.authMiddleware, h.GetBonusesByUserID)
// tenant.Get("/bonus/stats", a.authMiddleware, h.GetBonusStats)
// tenant.Post("/bonus/claim/:id", a.authMiddleware, h.ClaimBonus)
// groupV1.Post("/bonus/create", a.authMiddleware, h.CreateBonusMultiplier)
// groupV1.Put("/bonus/update", a.authMiddleware, h.UpdateBonusMultiplier)
groupV1.Get("/cashiers", a.authMiddleware, a.CompanyOnly, h.GetAllCashiers)
groupV1.Get("/cashiers/:id", a.authMiddleware, a.CompanyOnly, h.GetCashierByID)
groupV1.Post("/cashiers", a.authMiddleware, a.CompanyOnly, h.CreateCashier)
groupV1.Put("/cashiers/:id", a.authMiddleware, a.CompanyOnly, h.UpdateCashier)
// tenant.Get("/customer", a.authMiddleware, a.CompanyOnly, h.GetAllTenantCustomers)
// tenant.Get("/customer/:id", a.authMiddleware, a.CompanyOnly, h.GetTenantCustomerByID)
// tenant.Put("/customer/:id", a.authMiddleware, a.CompanyOnly, h.UpdateTenantCustomer)
// // tenant.Get("/customer/:id/bets", a.authMiddleware, a.CompanyOnly, h.GetTenantCustomerBets)
// groupV1.Get("/customer", a.authMiddleware, a.SuperAdminOnly, h.GetAllCustomers)
// groupV1.Get("/customer/:id", a.authMiddleware, a.SuperAdminOnly, h.GetCustomerByID)
// groupV1.Put("/customer/:id", a.authMiddleware, a.SuperAdminOnly, h.UpdateCustomer)
// groupV1.Get("/customer/:id/bets", a.authMiddleware, h.GetCustomerBets)
groupV1.Get("/admin", a.authMiddleware, a.SuperAdminOnly, h.GetAllAdmins) groupV1.Get("/admin", a.authMiddleware, a.SuperAdminOnly, h.GetAllAdmins)
groupV1.Get("/admin/:id", a.authMiddleware, a.SuperAdminOnly, h.GetAdminByID) groupV1.Get("/admin/:id", a.authMiddleware, a.SuperAdminOnly, h.GetAdminByID)
groupV1.Post("/admin", a.authMiddleware, a.SuperAdminOnly, h.CreateAdmin) groupV1.Post("/admin", a.authMiddleware, a.SuperAdminOnly, h.CreateAdmin)
groupV1.Put("/admin/:id", a.authMiddleware, a.SuperAdminOnly, h.UpdateAdmin) groupV1.Put("/admin/:id", a.authMiddleware, a.SuperAdminOnly, h.UpdateAdmin)
groupV1.Get("/t-approver", a.authMiddleware, a.OnlyAdminAndAbove, h.GetAllTransactionApprovers) // groupV1.Get("/t-approver", a.authMiddleware, a.OnlyAdminAndAbove, h.GetAllTransactionApprovers)
groupV1.Get("/t-approver/:id", a.authMiddleware, a.OnlyAdminAndAbove, h.GetTransactionApproverByID) // groupV1.Get("/t-approver/:id", a.authMiddleware, a.OnlyAdminAndAbove, h.GetTransactionApproverByID)
groupV1.Post("/t-approver", a.authMiddleware, a.OnlyAdminAndAbove, h.CreateTransactionApprover) // groupV1.Post("/t-approver", a.authMiddleware, a.OnlyAdminAndAbove, h.CreateTransactionApprover)
groupV1.Put("/t-approver/:id", a.authMiddleware, a.OnlyAdminAndAbove, h.UpdateTransactionApprover) // groupV1.Put("/t-approver/:id", a.authMiddleware, a.OnlyAdminAndAbove, h.UpdateTransactionApprover)
groupV1.Get("/managers", a.authMiddleware, a.OnlyAdminAndAbove, h.GetAllManagers)
groupV1.Get("/managers/:id", a.authMiddleware, h.GetManagerByID)
groupV1.Post("/managers", a.authMiddleware, a.OnlyAdminAndAbove, h.CreateManager)
groupV1.Put("/managers/:id", a.authMiddleware, a.OnlyAdminAndAbove, h.UpdateManagers)
// groupV1.Get("/manager/:id/branch", a.authMiddleware, a.OnlyAdminAndAbove, h.GetBranchByManagerID)
// groupV1.Get("/odds", a.authMiddleware, a.SuperAdminOnly, h.GetAllOdds)
// groupV1.Get("/odds/upcoming/:upcoming_id", a.authMiddleware, a.SuperAdminOnly, h.GetOddsByUpcomingID)
// groupV1.Get("/odds/upcoming/:upcoming_id/market/:market_id", a.authMiddleware, a.SuperAdminOnly, h.GetOddsByMarketID)
// groupV1.Post("/odds/settings", a.authMiddleware, a.SuperAdminOnly, h.SaveOddSettings)
// groupV1.Get("/odds/market-settings", a.authMiddleware, a.SuperAdminOnly, h.GetAllGlobalMarketSettings)
// groupV1.Put("/odds/bet-outcome/:id", a.authMiddleware, a.SuperAdminOnly, h.UpdateAllBetOutcomeStatusByOddID)
// groupV1.Put("/odds/bet-outcome", a.authMiddleware, a.SuperAdminOnly, h.BulkUpdateAllBetOutcomeStatusByOddID)
// tenant.Get("/odds", h.GetAllTenantOdds)
// tenant.Get("/odds/upcoming/:upcoming_id", h.GetTenantOddsByUpcomingID)
// tenant.Get("/odds/upcoming/:upcoming_id/market/:market_id", h.GetTenantOddsByMarketID)
// tenant.Post("/odds/settings", a.authMiddleware, a.CompanyOnly, h.SaveTenantOddsSetting)
// tenant.Delete("/odds/settings/:id", a.authMiddleware, a.CompanyOnly, h.RemoveOddsSettings)
// tenant.Delete("/odds/settings", a.authMiddleware, a.CompanyOnly, h.RemoveAllOddsSettings)
// tenant.Post("/odds/market-settings", a.authMiddleware, a.CompanyOnly, h.InsertCompanyMarketSettings)
// tenant.Get("/odds/market-settings", a.authMiddleware, a.CompanyOnly, h.GetAllTenantMarketSettings)
// tenant.Delete("/odds/market-settings", a.authMiddleware, a.CompanyOnly, h.DeleteAllCompanyMarketSettings)
// tenant.Delete("/odds/market-settings/:id", a.authMiddleware, a.CompanyOnly, h.DeleteCompanyMarketSettings)
// groupV1.Get("/events", h.GetAllEvents)
// groupV1.Get("/events/:id", a.authMiddleware, h.GetEventByID)
// groupV1.Delete("/events/:id", a.authMiddleware, a.SuperAdminOnly, h.SetEventStatusToRemoved)
// groupV1.Patch("/events/:id/is_monitored", a.authMiddleware, a.SuperAdminOnly, h.SetEventIsMonitored)
// groupV1.Put("/events/:id/settings", a.authMiddleware, a.SuperAdminOnly, h.UpdateGlobalSettingList)
// groupV1.Get("/events/:id/bets", a.authMiddleware, a.SuperAdminOnly, h.GetBetsByEventID)
// tenant.Get("/upcoming-events", h.GetTenantUpcomingEvents)
// tenant.Get("/top-leagues", h.GetTopLeagues)
// tenant.Get("/events", h.GetTenantEvents)
// tenant.Get("/events/:id", h.GetTenantEventByID)
// tenant.Put("/events/:id/settings", a.authMiddleware, a.CompanyOnly, h.UpdateTenantEventSettings)
// tenant.Get("/events/:id/bets", a.authMiddleware, a.CompanyOnly, h.GetTenantBetsByEventID)
//EnetPulse
// groupV1.Get("/odds/pre-match", h.GetPreMatchOdds)
// groupV1.Get("/sports", h.GetAllSports)
// groupV1.Get("/tournament_templates", h.GetAllTournamentTemplates)
// groupV1.Get("/tournaments", h.GetAllTournaments)
// groupV1.Get("/tournament_stages", h.GetAllTournamentStages)
// groupV1.Get("/fixtures", h.GetFixturesByDate)
// groupV1.Get("/results", h.GetAllResults)
// groupV1.Get("/preodds", h.GetAllPreoddsWithBettingOffers)
// groupV1.Get("/bettingoffers", h.GetAllBettingOffers)
// groupV1.Get("/fixtures/preodds", h.GetFixturesWithPreodds)
// // Leagues
// groupV1.Get("/leagues", a.authMiddleware, a.SuperAdminOnly, h.GetAllLeagues)
// groupV1.Put("/leagues/:id/settings", a.authMiddleware, a.SuperAdminOnly, h.UpdateGlobalLeagueSetting)
// tenant.Get("/leagues", h.GetAllLeagues)
// tenant.Put("/leagues/:id/featured", a.authMiddleware, a.CompanyOnly, h.SetLeagueFeatured)
// tenant.Put("/leagues/:id/set-active", a.authMiddleware, a.CompanyOnly, h.SetLeagueActive)
// groupV1.Get("/result/b365/:id", h.GetBet365ResultsByEventID)
// Branch
// groupV1.Post("/branch", a.authMiddleware, a.CompanyOnly, h.CreateBranch)
// groupV1.Get("/branch", a.authMiddleware, a.CompanyOnly, h.GetAllBranches)
// groupV1.Get("/branch/:id", a.authMiddleware, a.CompanyOnly, h.GetBranchByID)
// groupV1.Post("/branch/:id/return", a.authMiddleware, a.CompanyOnly, h.ReturnBranchWallet)
// // groupV1.Get("/branch/:id/bets", a.authMiddleware, a.CompanyOnly, h.GetBetByBranchID)
// groupV1.Put("/branch/:id", a.authMiddleware, a.CompanyOnly, h.UpdateBranch)
// groupV1.Put("/branch/:id/set-active", a.authMiddleware, a.CompanyOnly, h.UpdateBranchStatus)
// groupV1.Put("/branch/:id/set-inactive", a.authMiddleware, a.CompanyOnly, h.UpdateBranchStatus)
// groupV1.Delete("/branch/:id", a.authMiddleware, a.CompanyOnly, h.DeleteBranch)
// groupV1.Get("/search/branch", a.authMiddleware, a.CompanyOnly, h.SearchBranch)
// groupV1.Get("/branchLocation", a.authMiddleware, a.CompanyOnly, h.GetAllBranchLocations)
// groupV1.Get("/branch/:id/cashiers", a.authMiddleware, a.CompanyOnly, h.GetBranchCashiers)
// groupV1.Get("/branchCashier", a.authMiddleware, a.CompanyOnly, h.GetBranchForCashier)
// // Branch Operation
// groupV1.Get("/supportedOperation", a.authMiddleware, h.GetAllSupportedOperations)
// groupV1.Post("/supportedOperation", a.authMiddleware, a.SuperAdminOnly, h.CreateSupportedOperation)
// groupV1.Post("/operation", a.authMiddleware, a.CompanyOnly, h.CreateBranchOperation)
// groupV1.Get("/branch/:id/operation", a.authMiddleware, a.CompanyOnly, h.GetBranchOperations)
// groupV1.Delete("/branch/:id/operation/:opID", a.authMiddleware, a.CompanyOnly, h.DeleteBranchOperation)
// // Company
// groupV1.Post("/company", a.authMiddleware, a.SuperAdminOnly, h.CreateCompany)
// groupV1.Get("/company", a.authMiddleware, a.SuperAdminOnly, h.GetAllCompanies)
// groupV1.Get("/company/:id", a.authMiddleware, a.SuperAdminOnly, h.GetCompanyByID)
// groupV1.Put("/company/:id", a.authMiddleware, a.SuperAdminOnly, h.UpdateCompany)
// groupV1.Delete("/company/:id", a.authMiddleware, a.SuperAdminOnly, h.DeleteCompany)
// groupV1.Get("/company/:id/branch", a.authMiddleware, a.CompanyOnly, h.GetBranchByCompanyID)
// groupV1.Get("/search/company", a.authMiddleware, a.CompanyOnly, h.SearchCompany)
// groupV1.Get("/admin-company", a.authMiddleware, a.CompanyOnly, h.GetCompanyForAdmin)
// groupV1.Get("/ticket", h.GetAllTickets)
// groupV1.Get("/ticket/:id", h.GetTicketByID)
// Ticket Routes
// tenant.Post("/ticket", h.CreateTenantTicket)
// tenant.Get("/ticket", h.GetAllTenantTickets)
// tenant.Get("/ticket/:id", h.GetTenantTicketByID)
// Bet Routes
// tenant.Post("/sport/bet", a.authMiddleware, h.CreateBet)
// tenant.Post("/sport/bet/fastcode", a.authMiddleware, h.CreateBetWithFastCode)
// tenant.Get("/sport/bet/fastcode/:fast_code", h.GetBetByFastCode)
// tenant.Get("/sport/bet", a.authMiddleware, a.CompanyOnly, h.GetAllTenantBets)
// tenant.Get("/sport/bet/:id", a.authMiddleware, h.GetTenantBetByID)
// tenant.Patch("/sport/bet/:id", a.authMiddleware, h.UpdateCashOut)
// tenant.Delete("/sport/bet/:id", a.authMiddleware, h.DeleteTenantBet)
// groupV1.Get("/sport/bet/:id", a.authMiddleware, a.CompanyOnly, h.GetBetByID)
// groupV1.Get("/sport/bet", a.authMiddleware, a.SuperAdminOnly, h.GetAllBet)
// groupV1.Delete("/sport/bet/:id", a.authMiddleware, a.SuperAdminOnly, h.DeleteBet)
// tenant.Post("/sport/random/bet", a.authMiddleware, h.RandomBet)
// // Wallet
// groupV1.Get("/wallet", h.GetAllWallets)
// groupV1.Get("/wallet/:id", h.GetWalletByID)
// groupV1.Put("/wallet/:id", h.UpdateWalletActive)
// groupV1.Get("/branchWallet", a.authMiddleware, h.GetAllBranchWallets)
// groupV1.Get("/customerWallet", a.authMiddleware, h.GetAllCustomerWallets)
// groupV1.Get("/cashierWallet", a.authMiddleware, h.GetWalletForCashier)
// Transfer
// /transfer/wallet - transfer from one wallet to another wallet
// groupV1.Post("/transfer/wallet/:id", a.authMiddleware, h.TransferToWallet)
// groupV1.Get("/transfer/wallet/:id", a.authMiddleware, h.GetTransfersByWallet)
// groupV1.Post("/transfer/refill/:id", a.authMiddleware, h.RefillWallet)
//Chapa Routes
// groupV1.Post("/chapa/payments/webhook/verify", h.WebhookCallback)
// groupV1.Get("/chapa/transaction/manual/verify/:tx_ref", a.authMiddleware, h.ManualVerifyTransaction)
// groupV1.Put("/chapa/transaction/cancel/:tx_ref", a.authMiddleware, h.CancelDeposit)
// groupV1.Get("/chapa/transactions", a.authMiddleware, h.FetchAllTransactions)
// groupV1.Get("/chapa/transaction/events/:ref_id", a.authMiddleware, h.GetTransactionEvents)
// groupV1.Post("/chapa/payments/deposit", a.authMiddleware, h.InitiateDeposit)
// groupV1.Post("/chapa/payments/withdraw", a.authMiddleware, h.InitiateWithdrawal)
// groupV1.Get("/chapa/banks", h.GetSupportedBanks)
// groupV1.Get("/chapa/payments/receipt/:chapa_ref", a.authMiddleware, h.GetPaymentReceipt)
// groupV1.Get("/chapa/transfers", a.authMiddleware, h.GetAllTransfers)
// groupV1.Get("/chapa/balance", a.authMiddleware, h.GetAccountBalance)
// groupV1.Post("/chapa/swap", a.authMiddleware, h.SwapCurrency)
// Currencies
groupV1.Get("/currencies", h.GetSupportedCurrencies)
groupV1.Get("/currencies/convert", h.ConvertCurrency)
//Report Routes
// groupV1.Get("/reports/dashboard", a.authMiddleware, a.OnlyAdminAndAbove, h.GetDashboardReport)
// groupV1.Get("/report-files/download/:filename", h.DownloadReportFile)
// groupV1.Get("/report-files/list", a.authMiddleware, a.OnlyAdminAndAbove, h.ListReportFiles)
// groupV1.Post("/reports/requests", a.authMiddleware, a.OnlyAdminAndAbove, h.CreateReportRequest)
// groupV1.Get("/reports/requests", a.authMiddleware, a.OnlyAdminAndAbove, h.GetAllReportRequests)
// groupV1.Get("/reports/download/:id", a.authMiddleware, a.OnlyAdminAndAbove, h.DownloadReportByID)
//Alea Play Virtual Game Routes
// groupV1.Get("/alea-play/launch", a.authMiddleware, h.LaunchAleaGame)
// groupV1.Post("/webhooks/alea-play", a.authMiddleware, h.HandleAleaCallback)
// //Veli Virtual Game Routes
// groupV1.Post("/veli/providers", h.GetProviders)
// groupV1.Post("/veli/games-list", h.GetGamesByProvider)
// groupV1.Post("/veli/start-game", a.authMiddleware, h.StartGame)
// groupV1.Post("/veli/start-demo-game", h.StartDemoGame)
// a.fiber.Post("/balance", h.GetBalance)
// groupV1.Post("/veli/gaming-activity", a.authMiddleware, h.GetGamingActivity)
// groupV1.Post("/veli/huge-wins", a.authMiddleware, h.GetHugeWins)
// groupV1.Post("/veli/credit-balances", a.authMiddleware, h.GetCreditBalances)
// //Atlas Virtual Game Routes
// groupV1.Get("/atlas/games", h.GetAtlasVGames)
// groupV1.Post("/atlas/init-game", a.authMiddleware, h.InitAtlasGame)
// a.fiber.Post("/account", h.AtlasGetUserDataCallback)
// a.fiber.Post("/betwin", h.HandleAtlasBetWin)
// a.fiber.Post("/result", h.HandleRoundResult)
// a.fiber.Post("/rollback", h.HandleRollback)
// a.fiber.Post("/freespin", h.FreeSpinResultCallback)
// a.fiber.Post("/jackpot", h.JackpotCallback)
// groupV1.Post("/atlas/freespin", a.authMiddleware, h.CreateFreeSpin)
//mongoDB logs //mongoDB logs
groupV1.Get("/logs", a.authMiddleware, a.SuperAdminOnly, handlers.GetLogsHandler(context.Background())) groupV1.Get("/logs", a.authMiddleware, a.SuperAdminOnly, handlers.GetLogsHandler(context.Background()))
// Recommendation Routes
// group.Get("/virtual-games/recommendations/:userID", h.GetRecommendations)
// Transactions /shop/transactions
// groupV1.Post("/shop/bet", a.authMiddleware, a.CompanyOnly, h.CreateShopBet)
// groupV1.Get("/shop/bet", a.authMiddleware, a.CompanyOnly, h.GetAllShopBets)
// groupV1.Get("/shop/bet/:id", a.authMiddleware, a.CompanyOnly, h.GetShopBetByBetID)
// groupV1.Post("/shop/bet/:id/cashout", a.authMiddleware, a.CompanyOnly, h.CashoutBet)
// groupV1.Post("/shop/bet/:id/generate", a.authMiddleware, a.CompanyOnly, h.CashoutBet)
// groupV1.Get("/shop/cashout/:id", a.authMiddleware, a.CompanyOnly, h.GetShopBetByCashoutID)
// groupV1.Post("/shop/cashout", a.authMiddleware, a.CompanyOnly, h.CashoutByCashoutID)
// groupV1.Post("/shop/deposit", a.authMiddleware, a.CompanyOnly, h.DepositForCustomer)
// groupV1.Get("/shop/deposit", a.authMiddleware, a.CompanyOnly, h.DepositForCustomer)
// groupV1.Get("/shop/transaction", a.authMiddleware, a.CompanyOnly, h.GetAllTransactions) // groupV1.Get("/shop/transaction", a.authMiddleware, a.CompanyOnly, h.GetAllTransactions)
// groupV1.Get("/shop/transaction/:id", a.authMiddleware, a.CompanyOnly, h.GetTransactionByID) // groupV1.Get("/shop/transaction/:id", a.authMiddleware, a.CompanyOnly, h.GetTransactionByID)
// groupV1.Get("/shop/transaction/:id/bet", a.authMiddleware, a.CompanyOnly, h.GetShopBetByTransactionID) // groupV1.Get("/shop/transaction/:id/bet", a.authMiddleware, a.CompanyOnly, h.GetShopBetByTransactionID)
@ -434,33 +163,10 @@ func (a *App) initAppRoutes() {
groupV1.Get("/ws/connect", a.WebsocketAuthMiddleware, h.ConnectSocket) groupV1.Get("/ws/connect", a.WebsocketAuthMiddleware, h.ConnectSocket)
groupV1.Get("/notifications", a.authMiddleware, h.GetUserNotification) groupV1.Get("/notifications", a.authMiddleware, h.GetUserNotification)
groupV1.Get("/notifications/all", a.authMiddleware, h.GetAllNotifications) groupV1.Get("/notifications/all", a.authMiddleware, h.GetAllNotifications)
groupV1.Post("/notifications/mark-as-read", a.authMiddleware, h.MarkNotificationAsRead) // groupV1.Post("/notifications/mark-as-read", a.authMiddleware, h.MarkNotificationAsRead)
groupV1.Get("/notifications/unread", a.authMiddleware, h.CountUnreadNotifications) groupV1.Get("/notifications/unread", a.authMiddleware, h.CountUnreadNotifications)
groupV1.Post("/notifications/create", a.authMiddleware, h.CreateAndSendNotification) groupV1.Post("/notifications/create", a.authMiddleware, h.CreateAndSendNotification)
// Virtual Game Routes
// a.fiber.Post("/virtual-game/launch", a.authMiddleware, h.LaunchVirtualGame)
// a.fiber.Post("/virtual-game/callback", h.HandleVirtualGameCallback)
// a.fiber.Post("/playerInfo", h.HandlePlayerInfo)
// a.fiber.Post("/bet", h.HandleBet)
// a.fiber.Post("/win", h.HandleWin)
// a.fiber.Post("/cancel", h.HandleCancel)
// a.fiber.Post("/promoWin ", h.HandlePromoWin)
// a.fiber.Post("/tournamentWin ", h.HandleTournamentWin)
// a.fiber.Get("/popok/games", h.GetGameList)
// a.fiber.Get("/popok/games/recommend", a.authMiddleware, h.RecommendGames)
// groupV1.Post("/virtual-game/favorites", a.authMiddleware, h.AddFavorite)
// groupV1.Delete("/virtual-game/favorites/:gameID", a.authMiddleware, h.RemoveFavorite)
// groupV1.Get("/virtual-game/favorites", a.authMiddleware, h.ListFavorites)
// groupV1.Get("/orchestrator/virtual-game/provider-reports/asc", a.OnlyAdminAndAbove, h.ListVirtualGameProviderReportsAscHandler)
// groupV1.Get("/orchestrator/virtual-game/provider-reports/desc", a.OnlyAdminAndAbove, h.ListVirtualGameProviderReportsDescHandler)
// groupV1.Delete("/virtual-game/orchestrator/providers/:provideID", a.authMiddleware, h.RemoveProvider)
// groupV1.Get("/virtual-game/orchestrator/providers/:provideID", a.authMiddleware, h.GetProviderByID)
// groupV1.Get("/virtual-game/orchestrator/games", h.ListVirtualGames)
// groupV1.Get("/virtual-game/orchestrator/providers", h.ListProviders)
// groupV1.Patch("/virtual-game/orchestrator/providers/:provideID/status", a.authMiddleware, h.SetProviderEnabled)
//Issue Reporting Routes //Issue Reporting Routes
// groupV1.Post("/issues", a.authMiddleware, h.CreateIssue) //anyone who has logged can report a // groupV1.Post("/issues", a.authMiddleware, h.CreateIssue) //anyone who has logged can report a
// groupV1.Get("/issues/customer/:customer_id", a.authMiddleware, a.OnlyAdminAndAbove, h.GetUserIssues) // groupV1.Get("/issues/customer/:customer_id", a.authMiddleware, a.OnlyAdminAndAbove, h.GetUserIssues)
@ -473,12 +179,4 @@ func (a *App) initAppRoutes() {
groupV1.Get("/settings/:key", a.authMiddleware, a.SuperAdminOnly, h.GetGlobalSettingByKey) groupV1.Get("/settings/:key", a.authMiddleware, a.SuperAdminOnly, h.GetGlobalSettingByKey)
groupV1.Put("/settings", a.authMiddleware, a.SuperAdminOnly, h.UpdateGlobalSettingList) groupV1.Put("/settings", a.authMiddleware, a.SuperAdminOnly, h.UpdateGlobalSettingList)
tenant.Get("/settings", a.authMiddleware, a.OnlyAdminAndAbove, h.GetCompanySettingList)
tenant.Put("/settings", a.authMiddleware, a.OnlyAdminAndAbove, h.SaveCompanySettingList)
tenant.Delete("/settings/:key", a.authMiddleware, a.OnlyAdminAndAbove, h.DeleteCompanySetting)
tenant.Delete("/settings", a.authMiddleware, a.OnlyAdminAndAbove, h.DeleteAllCompanySetting)
// groupV1.Get("/stats/total/events", h.GetTotalEventStats)
// groupV1.Get("/stats/interval/events", h.GetTotalEventStatsByInterval)
} }

View File

@ -10,7 +10,7 @@ coverage:
@mkdir -p coverage @mkdir -p coverage
@docker compose up -d test @docker compose up -d test
@docker compose exec test sh -c "go test -coverprofile=coverage.out ./internal/... && go tool cover -func=coverage.out -o coverage/coverage.txt" @docker compose exec test sh -c "go test -coverprofile=coverage.out ./internal/... && go tool cover -func=coverage.out -o coverage/coverage.txt"
@docker cp $(shell docker ps -q -f "name=fortunebet-test-1"):/app/coverage ./ || true @docker cp $(shell docker ps -q -f "name=yimaru-test-1"):/app/coverage ./ || true
@docker compose stop test @docker compose stop test
.PHONY: build .PHONY: build
@ -46,45 +46,45 @@ postgres:
.PHONY: backup .PHONY: backup
backup: backup:
@mkdir -p backup @mkdir -p backup
@docker exec -t fortunebet-backend-postgres-1 pg_dump -U root --data-only --exclude-table=schema_migrations gh | gzip > backup/dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql.gz @docker exec -t yimaru-backend-postgres-1 pg_dump -U root --data-only --exclude-table=schema_migrations gh | gzip > backup/dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql.gz
restore: restore:
@echo "Restoring latest backup..." @echo "Restoring latest backup..."
@latest_file=$$(ls -t backup/dump_*.sql.gz | head -n 1); \ @latest_file=$$(ls -t backup/dump_*.sql.gz | head -n 1); \
echo "Restoring from $$latest_file"; \ echo "Restoring from $$latest_file"; \
gunzip -c $$latest_file | docker exec -i fortunebet-backend-postgres-1 psql -U root -d gh gunzip -c $$latest_file | docker exec -i yimaru-backend-postgres-1 psql -U root -d gh
restore_file: restore_file:
@echo "Restoring latest backup..." @echo "Restoring latest backup..."
gunzip -c $(file) | docker exec -i fortunebet-backend-postgres-1 psql -U root -d gh gunzip -c $(file) | docker exec -i yimaru-backend-postgres-1 psql -U root -d gh
.PHONY: seed_data .PHONY: seed_data
seed_data: seed_data:
@echo "Waiting for PostgreSQL to be ready..." @echo "Waiting for PostgreSQL to be ready..."
@until docker exec fortunebet-backend-postgres-1 pg_isready -U root -d gh; do \ @until docker exec yimaru-backend-postgres-1 pg_isready -U root -d gh; do \
echo "PostgreSQL is not ready yet..."; \ echo "PostgreSQL is not ready yet..."; \
sleep 1; \ sleep 1; \
done done
@for file in db/data/*.sql; do \ @for file in db/data/*.sql; do \
echo "Seeding $$file..."; \ echo "Seeding $$file..."; \
cat $$file | docker exec -i fortunebet-backend-postgres-1 psql -U root -d gh; \ cat $$file | docker exec -i yimaru-backend-postgres-1 psql -U root -d gh; \
done done
.PHONY: seed_dev_data .PHONY: seed_dev_data
seed_dev_data: seed_dev_data:
@echo "Waiting for PostgreSQL to be ready..." @echo "Waiting for PostgreSQL to be ready..."
@until docker exec fortunebet-backend-postgres-1 pg_isready -U root -d gh; do \ @until docker exec yimaru-backend-postgres-1 pg_isready -U root -d gh; do \
echo "PostgreSQL is not ready yet..."; \ echo "PostgreSQL is not ready yet..."; \
sleep 1; \ sleep 1; \
done done
cat db/scripts/fix_autoincrement_desync.sql | docker exec -i fortunebet-backend-postgres-1 psql -U root -d gh; cat db/scripts/fix_autoincrement_desync.sql | docker exec -i yimaru-backend-postgres-1 psql -U root -d gh;
@for file in db/dev_data/*.sql; do \ @for file in db/dev_data/*.sql; do \
if [ -f "$$file" ]; then \ if [ -f "$$file" ]; then \
echo "Seeding $$file..."; \ echo "Seeding $$file..."; \
cat $$file | docker exec -i fortunebet-backend-postgres-1 psql -U root -d gh; \ cat $$file | docker exec -i yimaru-backend-postgres-1 psql -U root -d gh; \
fi \ fi \
done done
postgres_log: postgres_log:
docker logs fortunebet-backend-postgres-1 docker logs yimaru-backend-postgres-1
.PHONY: swagger .PHONY: swagger
swagger: swagger:
@swag init -g cmd/main.go @swag init -g cmd/main.go
@ -94,15 +94,15 @@ logs:
db-up: | logs db-up: | logs
@mkdir -p logs @mkdir -p logs
@docker compose up -d postgres migrate mongo @docker compose up -d postgres migrate mongo
@docker logs fortunebet-backend-postgres-1 > logs/postgres.log 2>&1 & @docker logs yimaru-backend-postgres-1 > logs/postgres.log 2>&1 &
.PHONY: db-down .PHONY: db-down
db-down: db-down:
@docker compose down -v @docker compose down -v
# @docker volume rm fortunebet-backend_postgres_data # @docker volume rm yimaru-backend_postgres_data
.PHONY: sqlc-gen .PHONY: sqlc-gen
sqlc-gen: sqlc-gen:
@sqlc generate @sqlc generate
app_log: app_log:
@mkdir -p app_logs @mkdir -p app_logs
export_logs: | app_log export_logs: | app_log
@docker exec fortunebet-mongo mongoexport --db=logdb --collection=applogs -u root -p secret --authenticationDatabase=admin --out - > app_logs/log_`date +%Y-%m-%d"_"%H_%M_%S`.json @docker exec yimaru-mongo mongoexport --db=logdb --collection=applogs -u root -p secret --authenticationDatabase=admin --out - > app_logs/log_`date +%Y-%m-%d"_"%H_%M_%S`.json

108
makefile copy Normal file
View File

@ -0,0 +1,108 @@
include .env
.PHONY: test
test:
@docker compose up -d test
@docker compose exec test go test ./...
@docker compose stop test
.PHONY: coverage
coverage:
@mkdir -p coverage
@docker compose up -d test
@docker compose exec test sh -c "go test -coverprofile=coverage.out ./internal/... && go tool cover -func=coverage.out -o coverage/coverage.txt"
@docker cp $(shell docker ps -q -f "name=yimaru-test-1"):/app/coverage ./ || true
@docker compose stop test
.PHONY: build
build:
@docker compose build app
.PHONY: run
run:
@docker compose up
.PHONY: stop
stop:
@docker compose down
.PHONY: air
air:
@echo "Running air locally (not in Docker)"
@air -c .air.toml
.PHONY: migrations/new
migrations/new:
@echo 'Creating migration files for DB_URL'
@migrate create -seq -ext=.sql -dir=./db/migrations $(name)
.PHONY: migrations/up
migrations/up:
@echo 'Running up migrations...'
@docker compose up migrate
.PHONY: postgres
postgres:
@echo 'Running postgres db...'
docker compose -f docker-compose.yml exec postgres psql -U root -d gh
.PHONY: backup
backup:
@mkdir -p backup
@docker exec -t yimaru-backend-postgres-1 pg_dump -U root --data-only --exclude-table=schema_migrations gh | gzip > backup/dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql.gz
restore:
@echo "Restoring latest backup..."
@latest_file=$$(ls -t backup/dump_*.sql.gz | head -n 1); \
echo "Restoring from $$latest_file"; \
gunzip -c $$latest_file | docker exec -i yimaru-backend-postgres-1 psql -U root -d gh
restore_file:
@echo "Restoring latest backup..."
gunzip -c $(file) | docker exec -i yimaru-backend-postgres-1 psql -U root -d gh
.PHONY: seed_data
seed_data:
@echo "Waiting for PostgreSQL to be ready..."
@until docker exec yimaru-backend-postgres-1 pg_isready -U root -d gh; do \
echo "PostgreSQL is not ready yet..."; \
sleep 1; \
done
@for file in db/data/*.sql; do \
echo "Seeding $$file..."; \
cat $$file | docker exec -i yimaru-backend-postgres-1 psql -U root -d gh; \
done
.PHONY: seed_dev_data
seed_dev_data:
@echo "Waiting for PostgreSQL to be ready..."
@until docker exec yimaru-backend-postgres-1 pg_isready -U root -d gh; do \
echo "PostgreSQL is not ready yet..."; \
sleep 1; \
done
cat db/scripts/fix_autoincrement_desync.sql | docker exec -i yimaru-backend-postgres-1 psql -U root -d gh;
@for file in db/dev_data/*.sql; do \
if [ -f "$$file" ]; then \
echo "Seeding $$file..."; \
cat $$file | docker exec -i yimaru-backend-postgres-1 psql -U root -d gh; \
fi \
done
postgres_log:
docker logs yimaru-backend-postgres-1
.PHONY: swagger
swagger:
@swag init -g cmd/main.go
.PHONY: db-up
logs:
@mkdir -p logs
db-up: | logs
@mkdir -p logs
@docker compose up -d postgres migrate mongo
@docker logs yimaru-backend-postgres-1 > logs/postgres.log 2>&1 &
.PHONY: db-down
db-down:
@docker compose down -v
# @docker volume rm yimaru-backend_postgres_data
.PHONY: sqlc-gen
sqlc-gen:
@sqlc generate
app_log:
@mkdir -p app_logs
export_logs: | app_log
@docker exec yimaru-mongo mongoexport --db=logdb --collection=applogs -u root -p secret --authenticationDatabase=admin --out - > app_logs/log_`date +%Y-%m-%d"_"%H_%M_%S`.json