This commit is contained in:
2026-02-11 17:46:22 +03:00
commit eacfacb13b
266 changed files with 51337 additions and 0 deletions

587
svc/main.go Normal file
View File

@@ -0,0 +1,587 @@
package main
import (
"bssapp-backend/db"
"bssapp-backend/internal/auditlog"
"bssapp-backend/internal/mailer"
"bssapp-backend/middlewares"
"bssapp-backend/permissions"
"bssapp-backend/repository"
"bssapp-backend/routes"
"database/sql"
"log"
"net/http"
"os"
"runtime/debug"
"github.com/gorilla/mux"
"github.com/joho/godotenv"
)
/*
===========================================================
✅ CORS
===========================================================
*/
func enableCORS(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "http://localhost:9000")
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
h.ServeHTTP(w, r)
})
}
/*
===========================================================
✅ V3 — Method-aware Route Auto Register
- mk_sys_routes: (path, method, module_code, action)
- unique: (path, method)
- admin auto-allow: mk_sys_role_permissions (role_id=3)
===========================================================
*/
func autoRegisterRouteV3(
pg *sql.DB,
path string,
method string,
module string,
action string,
) {
tx, err := pg.Begin()
if err != nil {
log.Println("❌ TX begin error:", err)
return
}
defer tx.Rollback()
// 1) ROUTE REGISTER (path+method)
_, err = tx.Exec(`
INSERT INTO mk_sys_routes
(path, method, module_code, action)
VALUES
($1, $2, $3, $4)
ON CONFLICT (path, method) DO UPDATE
SET
module_code = EXCLUDED.module_code,
action = EXCLUDED.action
`,
path,
method,
module,
action,
)
if err != nil {
log.Printf("❌ Route register error (%s %s): %v", method, path, err)
return
}
// 2) ADMIN AUTO PERMISSION (module+action bazlı)
_, err = tx.Exec(`
INSERT INTO mk_sys_role_permissions
(role_id, module_code, action, allowed)
SELECT
id,
$1,
$2,
true
FROM dfrole
WHERE id = 3 -- ADMIN
ON CONFLICT DO NOTHING
`,
module,
action,
)
if err != nil {
log.Printf("❌ Admin perm seed error (%s %s): %v", method, path, err)
return
}
if err := tx.Commit(); err != nil {
log.Println("❌ TX commit error:", err)
return
}
log.Printf("✅ Route+Perm registered → %s %s [%s:%s]",
method, path, module, action,
)
}
/*
===========================================================
✅ V3 Route Bind Helper
- tek satırda: autoRegister + wrap + Handle
===========================================================
*/
func bindV3(
r *mux.Router,
pg *sql.DB,
path string,
method string,
module string,
action string,
h http.Handler,
) {
// main
autoRegisterRouteV3(pg, path, method, module, action)
r.Handle(path, h).Methods(method, "OPTIONS")
}
/*
===========================================================
InitRoutes — FULL V3 (Method-aware) PERMISSION EDITION
===========================================================
*/
func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router {
r := mux.NewRouter()
/*
===========================================================
✅ wrapV3 (method-aware):
- AuthMiddleware (JWT)
- panic recover
- AuthzGuardByRoute(pgDB) => route(path+method) lookup
===========================================================
*/
wrapV3 := func(h http.Handler) http.Handler {
return middlewares.AuthMiddleware(
pgDB,
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if rec := recover(); rec != nil {
log.Printf("🔥 PANIC %s %s\n%v", r.Method, r.URL.Path, rec)
debug.PrintStack()
http.Error(w, "internal server error", 500)
}
}()
// ✅ method-aware route guard
middlewares.AuthzGuardByRoute(pgDB)(h).ServeHTTP(w, r)
}),
)
}
// ============================================================
// PUBLIC (NO AUTHZ)
// ============================================================
bindV3(r, pgDB,
"/api/auth/login", "POST",
"auth", "login",
http.HandlerFunc(routes.LoginHandler(pgDB)),
)
bindV3(r, pgDB,
"/api/auth/refresh", "POST",
"auth", "refresh",
routes.AuthRefreshHandler(pgDB),
)
// ============================================================
// SYSTEM
// ============================================================
bindV3(r, pgDB,
"/api/password/change", "POST",
"system", "update",
wrapV3(http.HandlerFunc(routes.FirstPasswordChangeHandler(pgDB))),
)
bindV3(r, pgDB,
"/api/activity-logs", "GET",
"user", "view",
wrapV3(routes.AdminActivityLogsHandler(pgDB)),
)
bindV3(r, pgDB,
"/api/test-mail", "POST",
"user", "insert",
wrapV3(routes.TestMailHandler(ml)),
)
// ============================================================
// PERMISSIONS
// ============================================================
rolePerm := "/api/roles/{id}/permissions"
bindV3(r, pgDB,
rolePerm, "GET",
"user", "update",
wrapV3(routes.GetRolePermissionMatrix(pgDB)),
)
bindV3(r, pgDB,
rolePerm, "POST",
"user", "update",
wrapV3(routes.SaveRolePermissionMatrix(pgDB)),
)
userPerm := "/api/users/{id}/permissions"
bindV3(r, pgDB,
userPerm, "GET",
"user", "update",
wrapV3(routes.GetUserPermissionsHandler(pgDB)),
)
bindV3(r, pgDB,
userPerm, "POST",
"user", "update",
wrapV3(routes.SaveUserPermissionsHandler(pgDB)),
)
// ✅ permissions/routes (system:view)
bindV3(r, pgDB,
"/api/permissions/routes", "GET",
"system", "view",
wrapV3(routes.GetUserRoutePermissionsHandler(pgDB)),
)
// ✅ permissions/effective (system:view)
bindV3(r, pgDB,
"/api/permissions/effective", "GET",
"system", "view",
wrapV3(routes.GetMyEffectivePermissions(pgDB)),
)
// ✅ my permission matrix (system:view) (Senin tabloda user:view görünüyor; düzeltiyoruz)
bindV3(r, pgDB,
"/api/permissions/matrix", "GET",
"system", "view",
wrapV3(routes.GetMyPermissionMatrix(pgDB)),
)
// ============================================================
// ROLE + DEPARTMENT PERMISSIONS
// ============================================================
rdPerm := "/api/roles/{roleId}/departments/{deptCode}/permissions"
rdHandler := routes.NewRoleDepartmentPermissionHandler(pgDB)
bindV3(r, pgDB,
rdPerm, "GET",
"user", "update",
wrapV3(http.HandlerFunc(rdHandler.Get)),
)
bindV3(r, pgDB,
rdPerm, "POST",
"user", "update",
wrapV3(http.HandlerFunc(rdHandler.Save)),
)
// ============================================================
// USERS
// ============================================================
bindV3(r, pgDB,
"/api/users/list", "GET",
"user", "view",
wrapV3(routes.UserListRoute(pgDB)),
)
bindV3(r, pgDB,
"/api/users", "POST",
"user", "insert",
wrapV3(routes.UserCreateRoute(pgDB)),
)
bindV3(r, pgDB,
"/api/users/{id}", "GET",
"user", "update",
wrapV3(routes.UserDetailRoute(pgDB)),
)
bindV3(r, pgDB,
"/api/users/{id}", "PUT",
"user", "update",
wrapV3(routes.UserDetailRoute(pgDB)),
)
bindV3(r, pgDB,
"/api/users/{id}/admin-reset-password", "POST",
"user", "update",
wrapV3(routes.AdminResetPasswordHandler(pgDB)),
)
bindV3(r, pgDB,
"/api/users/{id}/send-password-mail", "POST",
"user", "update",
wrapV3(routes.SendPasswordResetMailHandler(pgDB, ml)),
)
// ✅ eski kısayol create endpoint (senin eski main.goda vardı)
bindV3(r, pgDB,
"/api/users/create", "POST",
"user", "insert",
wrapV3(routes.UserCreateRoute(pgDB)),
)
// ============================================================
// LOOKUPS
// ============================================================
lookups := map[string]http.Handler{
"/api/lookups/roles": routes.GetRoleLookupRoute(pgDB),
"/api/lookups/departments": routes.GetDepartmentLookupRoute(pgDB),
"/api/lookups/nebim-users": routes.GetNebimUserLookupRoute(pgDB),
"/api/lookups/piyasalar": routes.GetPiyasaLookupRoute(pgDB),
"/api/lookups/users-perm": routes.GetUsersForPermissionSelectRoute(pgDB),
"/api/lookups/roles-perm": routes.GetRolesForPermissionSelectRoute(pgDB),
"/api/lookups/departments-perm": routes.GetDepartmentsForPermissionSelectRoute(pgDB),
"/api/lookups/modules": routes.GetModuleLookupRoute(pgDB),
}
for path, handler := range lookups {
bindV3(r, pgDB,
path, "GET",
"user", "view",
wrapV3(handler),
)
}
// ============================================================
// CUSTOMER
// ============================================================
bindV3(r, pgDB,
"/api/accounts", "GET",
"customer", "view",
wrapV3(http.HandlerFunc(routes.GetAccountsHandler)),
)
bindV3(r, pgDB,
"/api/customer-list", "GET",
"customer", "view",
wrapV3(http.HandlerFunc(routes.GetCustomerListHandler)),
)
// ============================================================
// FINANCE
// ============================================================
bindV3(r, pgDB,
"/api/today-currency", "GET",
"finance", "view",
wrapV3(routes.GetTodayCurrencyV3Handler(mssql)),
)
bindV3(r, pgDB,
"/api/export-pdf", "GET",
"finance", "export",
wrapV3(routes.ExportPDFHandler(mssql)),
)
bindV3(r, pgDB,
"/api/exportstamentheaderreport-pdf", "GET",
"finance", "export",
wrapV3(routes.ExportStatementHeaderReportPDFHandler(mssql)),
)
// ============================================================
// REPORT (STATEMENTS)
// ============================================================
bindV3(r, pgDB,
"/api/statements", "GET",
"finance", "view",
wrapV3(http.HandlerFunc(routes.GetStatementHeadersHandler)),
)
// ⚠️ Senin handler: GetStatementDetailsHandler vars["accountCode"] bekliyor,
// routeda {id} var. Burada, DB routeunu ve pathi bozmayalım diye {id}yi koruyorum,
// ama handler içinde accountCode := mux.Vars(r)["id"] yapman daha doğru.
bindV3(r, pgDB,
"/api/statements/{id}/details", "GET",
"finance", "view",
wrapV3(http.HandlerFunc(routes.GetStatementDetailsHandler)),
)
// ============================================================
// ORDER
// ============================================================
orderRoutes := []struct {
Path string
Method string
Action string
Handle http.Handler
}{
{"/api/order/create", "POST", "insert", routes.CreateOrderHandler(pgDB, mssql)},
{"/api/order/update", "POST", "update", http.HandlerFunc(routes.UpdateOrderHandler)},
{"/api/order/get/{id}", "GET", "view", routes.GetOrderByIDHandler(mssql)},
{"/api/orders/list", "GET", "view", routes.OrderListRoute(mssql)},
{"/api/orders/export", "GET", "export", routes.OrderListExcelRoute(mssql)},
{"/api/order/check/{id}", "GET", "view", routes.OrderExistsHandler(mssql)},
{"/api/order/validate", "POST", "insert", routes.ValidateOrderHandler(mssql)},
{"/api/order/pdf/{id}", "GET", "export", routes.OrderPDFHandler(mssql)},
{"/api/order-inventory", "GET", "view", http.HandlerFunc(routes.GetOrderInventoryHandler)},
{"/api/orderpricelistb2b", "GET", "view", routes.GetOrderPriceListB2BHandler(pgDB, mssql)},
{"/api/min-price", "GET", "view", routes.GetOrderPriceListB2BHandler(pgDB, mssql)},
}
for _, rt := range orderRoutes {
bindV3(r, pgDB,
rt.Path, rt.Method,
"order", rt.Action,
wrapV3(rt.Handle),
)
}
// ============================================================
// PRODUCTS (✅ handler mapping fix)
// ============================================================
bindV3(r, pgDB,
"/api/products", "GET",
"order", "view",
wrapV3(http.HandlerFunc(routes.GetProductListHandler)),
)
bindV3(r, pgDB,
"/api/product-detail", "GET",
"order", "view",
wrapV3(http.HandlerFunc(routes.GetProductDetailHandler)),
)
bindV3(r, pgDB,
"/api/product-colors", "GET",
"order", "view",
wrapV3(http.HandlerFunc(routes.GetProductColorsHandler)),
)
bindV3(r, pgDB,
"/api/product-colorsize", "GET",
"order", "view",
wrapV3(http.HandlerFunc(routes.GetProductColorSizesHandler)),
)
bindV3(r, pgDB,
"/api/product-secondcolor", "GET",
"order", "view",
wrapV3(http.HandlerFunc(routes.GetProductSecondColorsHandler)),
)
// ============================================================
// ROLE MANAGEMENT
// ============================================================
bindV3(r, pgDB,
"/api/roles", "GET",
"user", "view",
wrapV3(routes.GetRolesHandler(pgDB)),
)
bindV3(r, pgDB,
"/api/departments", "GET",
"user", "view",
wrapV3(routes.GetDepartmentsHandler(pgDB)),
)
bindV3(r, pgDB,
"/api/piyasalar", "GET",
"user", "view",
wrapV3(routes.GetPiyasalarHandler(pgDB)),
)
// ============================================================
// ROLE RELATIONS
// ============================================================
bindV3(r, pgDB,
"/api/roles/{id}/departments", "POST",
"user", "update",
wrapV3(routes.UpdateRoleDepartmentsHandler(pgDB)),
)
bindV3(r, pgDB,
"/api/roles/{id}/piyasalar", "POST",
"user", "update",
wrapV3(routes.UpdateRolePiyasalarHandler(pgDB)),
)
// ============================================================
// USER ↔ ROLE
// ============================================================
bindV3(r, pgDB,
"/api/users/{id}/roles", "POST",
"user", "update",
wrapV3(routes.UpdateUserRolesHandler(pgDB)),
)
// ============================================================
// ADMIN EXTRA (eski main.goda vardı, yeni sisteme alındı)
// ============================================================
bindV3(r, pgDB,
"/api/admin/users/{id}/piyasa-sync", "POST",
"admin", "user.update",
wrapV3(http.HandlerFunc(routes.AdminSyncUserPiyasaHandler)),
)
return r
}
func main() {
log.Println("🔥🔥🔥 BSSAPP BACKEND STARTED — LOGIN ROUTE SHOULD EXIST 🔥🔥🔥")
// -------------------------------------------------------
// 🔑 ENV
// -------------------------------------------------------
if err := godotenv.Load(".env", "mail.env"); err != nil {
log.Println("⚠️ .env / mail.env bulunamadı")
}
jwtSecret := os.Getenv("JWT_SECRET")
if len(jwtSecret) < 10 {
log.Fatal("❌ JWT_SECRET tanımlı değil veya çok kısa (min 10 karakter)")
}
log.Println("🔐 JWT_SECRET yüklendi")
// -------------------------------------------------------
// 🔗 DATABASE
// -------------------------------------------------------
db.ConnectMSSQL()
pgDB, err := db.ConnectPostgres()
if err != nil {
log.Fatalf("❌ PostgreSQL bağlantı hatası: %v", err)
}
defer pgDB.Close()
// -------------------------------------------------------
// 🔐 ADMIN ROLE + DEPARTMENT AUTO SEED
// -------------------------------------------------------
if err := permissions.SeedAdminRoleDepartments(pgDB); err != nil {
log.Println("❌ Admin dept seed failed:", err)
} else {
log.Println("✅ Admin dept permissions seeded")
}
// -------------------------------------------------------
// 🕵️ AUDIT LOG INIT
// -------------------------------------------------------
auditlog.Init(pgDB, 1000)
log.Println("🕵️ AuditLog sistemi başlatıldı (buffer=1000)")
// -------------------------------------------------------
// ✉️ MAILER INIT
// -------------------------------------------------------
graphMailer, err := mailer.NewGraphMailer()
if err != nil {
log.Fatalf("❌ Graph Mailer init error: %v", err)
}
log.Println("✉️ Graph Mailer hazır")
// -------------------------------------------------------
// 👤 DEBUG
// -------------------------------------------------------
repository.NewUserRepository(pgDB).DebugListUsers()
// -------------------------------------------------------
// 🌍 SERVER
// -------------------------------------------------------
router := InitRoutes(pgDB, db.MssqlDB, graphMailer)
handler := enableCORS(
middlewares.GlobalAuthMiddleware(
pgDB,
middlewares.RequestLogger(router),
),
)
log.Println("✅ Server çalışıyor: http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", handler))
}