Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-06-19 15:44:12 +03:00
parent da9d7c2fd5
commit 1054a15547
13 changed files with 828 additions and 62 deletions

View File

@@ -555,6 +555,17 @@ func UserCreateRoute(db *sql.DB) http.HandlerFunc {
}
}
if err := ensureOrderPriceListUserPriceGroupSchema(db); err != nil {
log.Printf("USER CREATE PRICE GROUP SCHEMA ERROR user_id=%d err=%v", newID, err)
http.Error(w, "Fiyat grubu tablosu hazirlanamadi", http.StatusInternalServerError)
return
}
if err := saveOrderPriceListUserPriceGroupsTx(tx, newID, payload.OrderPriceListPriceGroups); err != nil {
log.Printf("USER CREATE PRICE GROUP SAVE ERROR user_id=%d err=%v", newID, err)
http.Error(w, "Fiyat gruplari eklenemedi", http.StatusInternalServerError)
return
}
if err := tx.Commit(); err != nil {
if pe, ok := err.(*pq.Error); ok {
log.Printf(

View File

@@ -0,0 +1,278 @@
package routes
import (
"bssapp-backend/auth"
"database/sql"
"encoding/json"
"log"
"net/http"
"strconv"
"strings"
"time"
"github.com/gorilla/mux"
"github.com/lib/pq"
)
type orderPriceListPriceGroupOption struct {
Value string `json:"value"`
Label string `json:"label"`
}
type orderPriceListUserPriceGroupRow struct {
UserID int64 `json:"user_id"`
Username string `json:"username"`
FullName string `json:"full_name"`
Email string `json:"email"`
PriceGroups []string `json:"price_groups"`
}
type orderPriceListUserPriceGroupPayload struct {
PriceGroups []string `json:"price_groups"`
}
var orderPriceListAllPriceGroups = []orderPriceListPriceGroupOption{
{Value: "usd1", Label: "USD 1"},
{Value: "usd2", Label: "USD 2"},
{Value: "usd3", Label: "USD 3"},
{Value: "usd4", Label: "USD 4"},
{Value: "usd5", Label: "USD 5"},
{Value: "usd6", Label: "USD 6"},
{Value: "eur1", Label: "EUR 1"},
{Value: "eur2", Label: "EUR 2"},
{Value: "eur3", Label: "EUR 3"},
{Value: "eur4", Label: "EUR 4"},
{Value: "eur5", Label: "EUR 5"},
{Value: "eur6", Label: "EUR 6"},
{Value: "try1", Label: "TRY 1"},
{Value: "try2", Label: "TRY 2"},
{Value: "try3", Label: "TRY 3"},
{Value: "try4", Label: "TRY 4"},
{Value: "try5", Label: "TRY 5"},
{Value: "try6", Label: "TRY 6"},
}
func ensureOrderPriceListUserPriceGroupSchema(db *sql.DB) error {
stmts := []string{
`CREATE TABLE IF NOT EXISTS mk_order_price_list_user_price_group (
user_id BIGINT NOT NULL,
price_group TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (user_id, price_group)
)`,
`CREATE INDEX IF NOT EXISTS ix_order_price_list_user_price_group_user ON mk_order_price_list_user_price_group (user_id)`,
`ALTER TABLE mk_order_price_list_user_price_group DROP CONSTRAINT IF EXISTS ck_order_price_list_user_price_group`,
`ALTER TABLE mk_order_price_list_user_price_group ADD CONSTRAINT ck_order_price_list_user_price_group CHECK (price_group IN ('usd1','usd2','usd3','usd4','usd5','usd6','eur1','eur2','eur3','eur4','eur5','eur6','try1','try2','try3','try4','try5','try6'))`,
}
for _, stmt := range stmts {
if _, err := db.Exec(stmt); err != nil {
return err
}
}
return nil
}
func normalizeOrderPriceListPriceGroups(groups []string) []string {
allowed := map[string]bool{}
order := []string{}
for _, opt := range orderPriceListAllPriceGroups {
allowed[opt.Value] = true
order = append(order, opt.Value)
}
set := map[string]bool{}
for _, item := range groups {
v := strings.ToLower(strings.TrimSpace(item))
if allowed[v] {
set[v] = true
}
}
out := make([]string, 0, len(set))
for _, v := range order {
if set[v] {
out = append(out, v)
}
}
return out
}
func loadOrderPriceListUserPriceGroups(db *sql.DB, userID int64) ([]string, error) {
if err := ensureOrderPriceListUserPriceGroupSchema(db); err != nil {
return nil, err
}
rows, err := db.Query(`
SELECT price_group
FROM mk_order_price_list_user_price_group
WHERE user_id = $1
ORDER BY
CASE SUBSTRING(price_group, 1, 3)
WHEN 'usd' THEN 1
WHEN 'eur' THEN 2
WHEN 'try' THEN 3
ELSE 9
END,
CAST(SUBSTRING(price_group, 4) AS INT)
`, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var out []string
for rows.Next() {
var group string
if err := rows.Scan(&group); err != nil {
return nil, err
}
out = append(out, strings.ToLower(strings.TrimSpace(group)))
}
return normalizeOrderPriceListPriceGroups(out), rows.Err()
}
func saveOrderPriceListUserPriceGroupsTx(tx *sql.Tx, userID int64, groups []string) error {
if _, err := tx.Exec(`DELETE FROM mk_order_price_list_user_price_group WHERE user_id = $1`, userID); err != nil {
return err
}
for _, group := range normalizeOrderPriceListPriceGroups(groups) {
if _, err := tx.Exec(`
INSERT INTO mk_order_price_list_user_price_group (user_id, price_group, created_at, updated_at)
VALUES ($1, $2, $3, $3)
ON CONFLICT (user_id, price_group)
DO UPDATE SET updated_at = EXCLUDED.updated_at
`, userID, group, time.Now()); err != nil {
return err
}
}
return nil
}
func GetOrderPriceListPriceGroupLookupsHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if err := ensureOrderPriceListUserPriceGroupSchema(db); err != nil {
log.Printf("[order-price-list-price-groups] schema error: %v", err)
http.Error(w, "price group schema error", http.StatusInternalServerError)
return
}
_ = json.NewEncoder(w).Encode(map[string]any{"price_groups": orderPriceListAllPriceGroups})
}
}
func GetMyOrderPriceListPriceGroupsHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
claims, ok := auth.GetClaimsFromContext(r.Context())
if !ok || claims == nil {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
groups, err := loadOrderPriceListUserPriceGroups(db, int64(claims.ID))
if err != nil {
log.Printf("[order-price-list-price-groups] my groups error user=%d err=%v", claims.ID, err)
http.Error(w, "price groups lookup error", http.StatusInternalServerError)
return
}
_ = json.NewEncoder(w).Encode(map[string]any{
"price_groups": groups,
"restricted": len(groups) > 0,
"all_groups": orderPriceListAllPriceGroups,
})
}
}
func GetUserOrderPriceListPriceGroupsHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
id, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
if err != nil || id <= 0 {
http.Error(w, "invalid user id", http.StatusBadRequest)
return
}
groups, err := loadOrderPriceListUserPriceGroups(db, id)
if err != nil {
log.Printf("[order-price-list-price-groups] user groups error user=%d err=%v", id, err)
http.Error(w, "price groups lookup error", http.StatusInternalServerError)
return
}
_ = json.NewEncoder(w).Encode(map[string]any{"price_groups": groups})
}
}
func SaveUserOrderPriceListPriceGroupsHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
id, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
if err != nil || id <= 0 {
http.Error(w, "invalid user id", http.StatusBadRequest)
return
}
var payload orderPriceListUserPriceGroupPayload
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
http.Error(w, "invalid payload", http.StatusBadRequest)
return
}
if err := ensureOrderPriceListUserPriceGroupSchema(db); err != nil {
http.Error(w, "price group schema error", http.StatusInternalServerError)
return
}
tx, err := db.Begin()
if err != nil {
http.Error(w, "transaction error", http.StatusInternalServerError)
return
}
defer tx.Rollback()
if err := saveOrderPriceListUserPriceGroupsTx(tx, id, payload.PriceGroups); err != nil {
log.Printf("[order-price-list-price-groups] save error user=%d err=%v", id, err)
http.Error(w, "price groups save error", http.StatusInternalServerError)
return
}
if err := tx.Commit(); err != nil {
http.Error(w, "commit error", http.StatusInternalServerError)
return
}
_ = json.NewEncoder(w).Encode(map[string]any{"success": true})
}
}
func GetOrderPriceListUserPriceGroupRowsHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if err := ensureOrderPriceListUserPriceGroupSchema(db); err != nil {
http.Error(w, "price group schema error", http.StatusInternalServerError)
return
}
rows, err := db.Query(`
SELECT u.id, u.username, COALESCE(u.full_name, ''), COALESCE(u.email, ''),
COALESCE(array_agg(m.price_group ORDER BY
CASE SUBSTRING(m.price_group, 1, 3)
WHEN 'usd' THEN 1
WHEN 'eur' THEN 2
WHEN 'try' THEN 3
ELSE 9
END,
CAST(SUBSTRING(m.price_group, 4) AS INT)
) FILTER (WHERE m.price_group IS NOT NULL), ARRAY[]::text[]) AS price_groups
FROM mk_dfusr u
LEFT JOIN mk_order_price_list_user_price_group m ON m.user_id = u.id
WHERE COALESCE(u.is_active, TRUE) = TRUE
GROUP BY u.id, u.username, u.full_name, u.email
ORDER BY u.username
`)
if err != nil {
log.Printf("[order-price-list-price-groups] rows error: %v", err)
http.Error(w, "price groups rows error", http.StatusInternalServerError)
return
}
defer rows.Close()
out := []orderPriceListUserPriceGroupRow{}
for rows.Next() {
var row orderPriceListUserPriceGroupRow
if err := rows.Scan(&row.UserID, &row.Username, &row.FullName, &row.Email, pq.Array(&row.PriceGroups)); err != nil {
http.Error(w, "price groups scan error", http.StatusInternalServerError)
return
}
row.PriceGroups = normalizeOrderPriceListPriceGroups(row.PriceGroups)
out = append(out, row)
}
_ = json.NewEncoder(w).Encode(out)
}
}

View File

@@ -184,6 +184,12 @@ func handleUserGet(db *sql.DB, w http.ResponseWriter, userID int64) {
}
}
if groups, err := loadOrderPriceListUserPriceGroups(db, userID); err == nil {
u.OrderPriceListPriceGroups = groups
} else {
log.Printf("WARN [UserDetail] order price list price groups lookup failed user_id=%d err=%v", userID, err)
}
// --------------------------------------------------
// 🟢 RESPONSE
// --------------------------------------------------
@@ -326,6 +332,17 @@ func handleUserUpdate(db *sql.DB, w http.ResponseWriter, r *http.Request, userID
}
}
if err := ensureOrderPriceListUserPriceGroupSchema(db); err != nil {
log.Printf("ERROR [UserDetail] price group schema failed user_id=%d err=%v", userID, err)
http.Error(w, "Fiyat grubu tablosu hazirlanamadi", http.StatusInternalServerError)
return
}
if err := saveOrderPriceListUserPriceGroupsTx(tx, userID, payload.OrderPriceListPriceGroups); err != nil {
log.Printf("ERROR [UserDetail] price groups save failed user_id=%d err=%v", userID, err)
http.Error(w, "Fiyat gruplari guncellenemedi", http.StatusInternalServerError)
return
}
if err := tx.Commit(); err != nil {
log.Printf("❌ [UserDetail] commit failed user_id=%d err=%v", userID, err)
http.Error(w, "Commit başarısız", http.StatusInternalServerError)
@@ -384,6 +401,7 @@ func handleUserDelete(db *sql.DB, w http.ResponseWriter, r *http.Request, userID
`DELETE FROM dfusr_dprt WHERE dfusr_id = $1`,
`DELETE FROM dfusr_piyasa WHERE dfusr_id = $1`,
`DELETE FROM dfusr_nebim_user WHERE dfusr_id = $1`,
`DELETE FROM mk_order_price_list_user_price_group WHERE user_id = $1`,
}
isUndefinedTable := func(err error) bool {