Squashed commit of the following:
commitd2ec8c0f07Author: mario <dev.mario@sismedika@gmail.com> Date: Thu May 15 15:42:33 2025 +0700 add: db tx commit and rollback implementation commit264435f67eAuthor: mario <dev.mario@sismedika@gmail.com> Date: Thu May 15 14:34:20 2025 +0700 fix: shortlink generation logic update/create commit047ab1937aAuthor: mario <dev.mario@sismedika@gmail.com> Date: Thu May 15 11:06:04 2025 +0700 fix: if multiple studies patient, show first study by default commitc13f834b92Author: mario <dev.mario@sismedika@gmail.com> Date: Thu May 15 09:46:32 2025 +0700 add: register and login with DB query AND some struct type correction commitdd4451c2a8Author: mario <dev.mario@sismedika@gmail.com> Date: Wed May 14 10:23:33 2025 +0700 new file structure & koneksi ke DB commit8289881df3Author: mario <dev.mario@sismedika@gmail.com> Date: Tue May 13 16:49:07 2025 +0700 edit: rm debug route commitdd784da232Author: mario <dev.mario@sismedika@gmail.com> Date: Tue May 13 15:44:11 2025 +0700 add: implement shortlink commit2687a761ccAuthor: mario <dev.mario@sismedika@gmail.com> Date: Tue May 13 11:47:19 2025 +0700 add new dummy doctor user commiteb67eaca46Author: mario <dev.mario@sismedika@gmail.com> Date: Tue May 13 11:46:28 2025 +0700 add: ref_doctor studylist filter commit0d4825d152Author: mario <dev.mario@sismedika@gmail.com> Date: Tue May 13 10:07:16 2025 +0700 edit study_iuids & accNum in patient jwt to array commit2d1f135fdaAuthor: mario <dev.mario@sismedika@gmail.com> Date: Tue May 13 09:52:45 2025 +0700 patient see their multiple studies commit13bb380f51Author: mario <dev.mario@sismedika@gmail.com> Date: Fri May 9 10:13:16 2025 +0700 add: cors handler route and readme commit6c9ab574ceAuthor: mario <dev.mario@sismedika@gmail.com> Date: Mon May 5 11:50:36 2025 +0700 add: login & token validation tapi belum connect ke DB commit297c9a6a01Author: mario <dev.mario@sismedika@gmail.com> Date: Mon Apr 28 15:37:02 2025 +0700 add readme.md commit9b8e0260f3Author: mario <dev.mario@sismedika@gmail.com> Date: Mon Apr 7 15:46:07 2025 +0700 connected-to-google commitf340bc5916Author: mario <dev.mario@sismedika.com> Date: Mon Apr 7 11:14:18 2025 +0700 init
This commit is contained in:
93
internal/api/handlers/auth.go
Normal file
93
internal/api/handlers/auth.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/api/models"
|
||||
"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/api/service"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// AuthHandler handles authentication requests
|
||||
type AuthHandler struct {
|
||||
logger *zap.Logger
|
||||
authService *service.AuthService
|
||||
}
|
||||
|
||||
// NewAuthHandler creates a new auth handler
|
||||
func NewAuthHandler(logger *zap.Logger, authService *service.AuthService) *AuthHandler {
|
||||
return &AuthHandler{
|
||||
logger: logger,
|
||||
authService: authService,
|
||||
}
|
||||
}
|
||||
|
||||
// Login handles user login
|
||||
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse login request
|
||||
var req models.LoginRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
h.logger.Error("Failed to parse login request", zap.Error(err))
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Authenticate user using mock database
|
||||
response, err := h.authService.Login(req.Email, req.Password)
|
||||
if err != nil {
|
||||
h.logger.Warn("Login failed", zap.Error(err), zap.String("email", req.Email))
|
||||
http.Error(w, "Invalid credentials", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Log successful login with role information
|
||||
h.logger.Info("User logged in successfully",
|
||||
zap.String("email", req.Email),
|
||||
zap.String("userID", response.User.ID),
|
||||
zap.String("role", response.User.Role))
|
||||
|
||||
// Return tokens and user info
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// RefreshToken handles token refresh
|
||||
func (h *AuthHandler) RefreshToken(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse refresh token request
|
||||
var req models.RefreshRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
h.logger.Error("Failed to parse refresh token request", zap.Error(err))
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Refresh token
|
||||
accessToken, err := h.authService.RefreshToken(req.RefreshToken)
|
||||
if err != nil {
|
||||
h.logger.Warn("Token refresh failed", zap.Error(err))
|
||||
http.Error(w, "Invalid refresh token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Return new access token
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(models.RefreshResponse{
|
||||
AccessToken: accessToken,
|
||||
})
|
||||
}
|
||||
|
||||
// Logout handles user logout
|
||||
func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
|
||||
// In a real implementation, you would invalidate the refresh token
|
||||
// For now, just return a success message
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"message": "Successfully logged out",
|
||||
})
|
||||
}
|
||||
247
internal/api/handlers/dicom.go
Normal file
247
internal/api/handlers/dicom.go
Normal file
@@ -0,0 +1,247 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/api/middleware"
|
||||
"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/auth"
|
||||
"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/proxy"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// DicomHandler handles DICOM Web requests
|
||||
type DicomHandler struct {
|
||||
client *proxy.Client
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewDicomHandler creates a new DICOM handler
|
||||
func NewDicomHandler(client *proxy.Client, logger *zap.Logger) *DicomHandler {
|
||||
return &DicomHandler{
|
||||
client: client,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// buildRefDoctorFilter constructs a properly encoded query string for referring doctor filtering
|
||||
func (h *DicomHandler) buildRefDoctorFilter(doctorName string, queryParams url.Values) string {
|
||||
// Extract basic parameters with fallbacks
|
||||
limit := queryParams.Get("limit")
|
||||
if limit == "" {
|
||||
limit = "101" // Default limit used by OHIF
|
||||
}
|
||||
|
||||
offset := queryParams.Get("offset")
|
||||
if offset == "" {
|
||||
offset = "0" // Default offset
|
||||
}
|
||||
|
||||
includeField := queryParams.Get("includefield")
|
||||
|
||||
// Make sure includefield includes 00080090 (ReferringPhysician)
|
||||
if includeField != "" && !strings.Contains(includeField, "00080090") {
|
||||
includeField = includeField + ",00080090"
|
||||
} else if includeField == "" {
|
||||
includeField = "00081030,00080060,00080090"
|
||||
}
|
||||
|
||||
// Properly encode the doctor's name
|
||||
encodedName := strings.ReplaceAll(doctorName, " ", "%20")
|
||||
encodedName = strings.ReplaceAll(encodedName, ",", "%2C")
|
||||
encodedName = strings.ReplaceAll(encodedName, ".", "%2E")
|
||||
|
||||
// Construct query string manually to avoid double-encoding
|
||||
return fmt.Sprintf("limit=%s&offset=%s&fuzzymatching=false&includefield=%s&00080090=%s",
|
||||
limit, offset, includeField, encodedName)
|
||||
}
|
||||
|
||||
// isStudyListRequest checks if a path refers to the top-level studies endpoint
|
||||
func isStudyListRequest(path string) bool {
|
||||
// Normalize the path first
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
|
||||
// Check for exact match with "/studies"
|
||||
return path == "/studies"
|
||||
}
|
||||
|
||||
// ForwardRequest forwards the request to Google Healthcare API
|
||||
func (h *DicomHandler) ForwardRequest(w http.ResponseWriter, r *http.Request) {
|
||||
// Get claims from context if they exist
|
||||
var claims *auth.CustomClaims
|
||||
claimsValue := r.Context().Value(middleware.ClaimsKey)
|
||||
|
||||
// Add detailed debug logging about claims
|
||||
if claimsValue == nil {
|
||||
h.logger.Warn("Claims not found in context",
|
||||
zap.String("path", r.URL.Path),
|
||||
zap.String("method", r.Method))
|
||||
} else {
|
||||
claims = claimsValue.(*auth.CustomClaims)
|
||||
h.logger.Debug("Claims retrieved from context",
|
||||
zap.String("userID", claims.UserID),
|
||||
zap.String("role", claims.Role),
|
||||
zap.String("userName", claims.UserName))
|
||||
}
|
||||
|
||||
// Get the path after /dicomWeb
|
||||
urlPath := chi.URLParam(r, "*")
|
||||
|
||||
// If the URL parameter is empty, try to extract it from the URL path
|
||||
if urlPath == "" {
|
||||
// Remove /dicomWeb prefix from the URL
|
||||
prefix := "/dicomWeb"
|
||||
if strings.HasPrefix(r.URL.Path, prefix) {
|
||||
urlPath = r.URL.Path[len(prefix):]
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize the path
|
||||
if !strings.HasPrefix(urlPath, "/") {
|
||||
urlPath = "/" + urlPath
|
||||
}
|
||||
|
||||
h.logger.Debug("Forwarding request",
|
||||
zap.String("path", urlPath),
|
||||
zap.String("method", r.Method),
|
||||
zap.String("url", r.URL.String()))
|
||||
|
||||
// Copy query parameters
|
||||
queryParams := r.URL.Query()
|
||||
queryString := ""
|
||||
|
||||
// Apply role-specific query modifications
|
||||
if claims != nil {
|
||||
switch claims.Role {
|
||||
case "patient":
|
||||
// For patients requesting study list, filter to only show their studies
|
||||
if isStudyListRequest(urlPath) {
|
||||
// Check if studies are available in the claim
|
||||
if len(claims.StudyIUIDs) > 0 {
|
||||
// Remove existing StudyInstanceUID param if it exists
|
||||
queryParams.Del("StudyInstanceUID")
|
||||
|
||||
// For DICOMweb, we can use comma-separated UIDs
|
||||
queryParams.Set("StudyInstanceUID", claims.StudyIUIDs[0])
|
||||
|
||||
h.logger.Debug("Filtering by studies",
|
||||
zap.Strings("studies", claims.StudyIUIDs))
|
||||
}
|
||||
} else if strings.HasPrefix(urlPath, "/studies/") {
|
||||
// This is a request for a specific study - check if the patient is authorized
|
||||
|
||||
// Extract the study ID from the path
|
||||
// Format: /studies/{studyID}/...
|
||||
pathParts := strings.Split(strings.TrimPrefix(urlPath, "/"), "/")
|
||||
if len(pathParts) >= 2 {
|
||||
studyID := pathParts[1]
|
||||
|
||||
// Check if this study is in the patient's authorized studies
|
||||
authorized := false
|
||||
|
||||
// Check StudyIUIDs array
|
||||
for _, id := range claims.StudyIUIDs {
|
||||
if id == studyID {
|
||||
authorized = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If not authorized, return 403 Forbidden
|
||||
if !authorized {
|
||||
h.logger.Warn("Unauthorized study access attempt",
|
||||
zap.String("studyID", studyID),
|
||||
zap.String("patientID", claims.PatientID),
|
||||
zap.Strings("authorizedStudies", claims.StudyIUIDs),
|
||||
)
|
||||
http.Error(w, "Forbidden: You are not authorized to access this study", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Debug("Authorized access to specific study",
|
||||
zap.String("studyID", studyID),
|
||||
zap.String("patientID", claims.PatientID),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Use standard query parameter encoding
|
||||
queryString = queryParams.Encode()
|
||||
|
||||
case "ref_doctor":
|
||||
// For ref_doctor requesting study list, apply filter
|
||||
if isStudyListRequest(urlPath) {
|
||||
// Use our helper function to build the properly encoded query
|
||||
queryString = h.buildRefDoctorFilter(claims.UserName, queryParams)
|
||||
|
||||
h.logger.Debug("Applied referring physician filter",
|
||||
zap.String("doctorName", claims.UserName),
|
||||
zap.String("queryString", queryString))
|
||||
} else {
|
||||
// For other paths, use standard query parameter encoding
|
||||
queryString = queryParams.Encode()
|
||||
}
|
||||
|
||||
case "expertise_doctor":
|
||||
// No restrictions for expertise_doctor
|
||||
queryString = queryParams.Encode()
|
||||
}
|
||||
} else {
|
||||
// No claims, use standard query parameter encoding
|
||||
queryString = queryParams.Encode()
|
||||
}
|
||||
|
||||
// Add the query string to the path
|
||||
if queryString != "" {
|
||||
urlPath = urlPath + "?" + queryString
|
||||
}
|
||||
|
||||
// Read request body if present
|
||||
var bodyBytes []byte
|
||||
if r.Body != nil && r.ContentLength > 0 {
|
||||
var err error
|
||||
bodyBytes, err = io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to read request body", zap.Error(err))
|
||||
http.Error(w, "Failed to read request body", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Get request headers
|
||||
headers := make(map[string]string)
|
||||
for k, v := range r.Header {
|
||||
if len(v) > 0 {
|
||||
headers[k] = v[0]
|
||||
}
|
||||
}
|
||||
|
||||
// Forward the request to Healthcare API
|
||||
response, err := h.client.ForwardRequest(
|
||||
r.Context(),
|
||||
r.Method,
|
||||
urlPath,
|
||||
headers,
|
||||
bodyBytes)
|
||||
|
||||
if err != nil {
|
||||
h.logger.Error("Request forwarding failed", zap.Error(err))
|
||||
http.Error(w, fmt.Sprintf("Request failed: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Set response headers
|
||||
for k, v := range response.Headers {
|
||||
w.Header().Set(k, v)
|
||||
}
|
||||
|
||||
// Set status and write response body
|
||||
w.WriteHeader(response.StatusCode)
|
||||
w.Write(response.Body)
|
||||
}
|
||||
19
internal/api/handlers/healthcheck.go
Normal file
19
internal/api/handlers/healthcheck.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HealthCheck provides a simple health check endpoint
|
||||
func HealthCheck(w http.ResponseWriter, r *http.Request) {
|
||||
response := map[string]interface{}{
|
||||
"status": "ok",
|
||||
"timestamp": time.Now().Format(time.RFC3339),
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
68
internal/api/handlers/register.go
Normal file
68
internal/api/handlers/register.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/api/service"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// RegisterHandler handles user registration requests
|
||||
type RegisterHandler struct {
|
||||
logger *zap.Logger
|
||||
registerService *service.RegisterService
|
||||
}
|
||||
|
||||
// NewRegisterHandler creates a new registration handler
|
||||
func NewRegisterHandler(logger *zap.Logger, registerService *service.RegisterService) *RegisterHandler {
|
||||
return &RegisterHandler{
|
||||
logger: logger,
|
||||
registerService: registerService,
|
||||
}
|
||||
}
|
||||
|
||||
// Register handles the creation of new users, patients, and doctors
|
||||
func (h *RegisterHandler) Register(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse registration request
|
||||
var req service.RegisterRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
h.logger.Error("Failed to parse registration request", zap.Error(err))
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
if req.Email == "" || req.Password == "" || req.Name == "" || req.Role == "" {
|
||||
http.Error(w, "Missing required fields", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Perform registration
|
||||
user, err := h.registerService.Register(&req)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case service.ErrEmailExists:
|
||||
http.Error(w, "Email already exists", http.StatusConflict)
|
||||
case service.ErrInvalidRole:
|
||||
http.Error(w, "Invalid user role", http.StatusBadRequest)
|
||||
case service.ErrInvalidPatient:
|
||||
http.Error(w, "Invalid patient data", http.StatusBadRequest)
|
||||
case service.ErrInvalidDoctor:
|
||||
http.Error(w, "Invalid doctor data", http.StatusBadRequest)
|
||||
default:
|
||||
h.logger.Error("Registration failed", zap.Error(err))
|
||||
http.Error(w, "Registration failed", http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Return created user
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
|
||||
// Don't include password in response
|
||||
user.Password = ""
|
||||
|
||||
json.NewEncoder(w).Encode(user)
|
||||
}
|
||||
129
internal/api/handlers/shortlink.go
Normal file
129
internal/api/handlers/shortlink.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/api/middleware"
|
||||
"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/api/models"
|
||||
"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/api/service"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ShortLinkHandler handles shortlink operations
|
||||
type ShortLinkHandler struct {
|
||||
logger *zap.Logger
|
||||
shortLinkService *service.ShortLinkService
|
||||
}
|
||||
|
||||
// NewShortLinkHandler creates a new shortlink handler
|
||||
func NewShortLinkHandler(logger *zap.Logger, shortLinkService *service.ShortLinkService) *ShortLinkHandler {
|
||||
return &ShortLinkHandler{
|
||||
logger: logger,
|
||||
shortLinkService: shortLinkService,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateShortLink handles shortlink generation requests
|
||||
func (h *ShortLinkHandler) GenerateShortLink(w http.ResponseWriter, r *http.Request) {
|
||||
// Only allow admin or expertise_doctor roles to generate shortlinks
|
||||
userRole, ok := r.Context().Value(middleware.UserRoleKey).(string)
|
||||
if !ok || (userRole != "admin" && userRole != "expertise_doctor") {
|
||||
h.logger.Warn("Unauthorized attempt to generate shortlink",
|
||||
zap.String("role", userRole))
|
||||
http.Error(w, "Only admin or expertise doctor can generate short links", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse request body
|
||||
var req models.GenerateShortLinkRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
h.logger.Error("Failed to parse shortlink generation request", zap.Error(err))
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Get user ID from context
|
||||
userID, ok := r.Context().Value(middleware.UserIDKey).(string)
|
||||
if !ok {
|
||||
h.logger.Error("User ID not found in context")
|
||||
http.Error(w, "User context not found", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Generate shortlink using configured baseURL from service
|
||||
response, err := h.shortLinkService.GenerateShortLink(&req, userID)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to generate shortlink",
|
||||
zap.Error(err),
|
||||
zap.String("patientID", req.PatientID),
|
||||
zap.String("studyUID", req.StudyUID))
|
||||
|
||||
statusCode := http.StatusInternalServerError
|
||||
message := "Failed to generate shortlink"
|
||||
|
||||
if err == service.ErrInvalidStudyUID {
|
||||
statusCode = http.StatusBadRequest
|
||||
message = "Invalid StudyInstanceUID"
|
||||
}
|
||||
|
||||
http.Error(w, message, statusCode)
|
||||
return
|
||||
}
|
||||
|
||||
// Log successful shortlink generation
|
||||
h.logger.Info("Shortlink generated successfully",
|
||||
zap.String("token", response.ShortToken),
|
||||
zap.String("patientID", req.PatientID),
|
||||
zap.String("studyUID", req.StudyUID),
|
||||
zap.String("createdBy", userID))
|
||||
|
||||
// Return response
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// ShortLinkAuth handles authentication requests using shortlinks
|
||||
func (h *ShortLinkHandler) ShortLinkAuth(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse request body
|
||||
var req models.ShortLinkAuthRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
h.logger.Error("Failed to parse shortlink auth request", zap.Error(err))
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate and authenticate
|
||||
response, err := h.shortLinkService.AuthenticateWithShortLink(&req)
|
||||
if err != nil {
|
||||
h.logger.Warn("Shortlink authentication failed",
|
||||
zap.Error(err),
|
||||
zap.String("token", req.ShortToken))
|
||||
|
||||
statusCode := http.StatusUnauthorized
|
||||
message := "Authentication failed"
|
||||
|
||||
switch err {
|
||||
case service.ErrShortLinkNotFound, service.ErrShortLinkExpired:
|
||||
message = "Short link not found or expired"
|
||||
case service.ErrInvalidDOB:
|
||||
message = "Invalid date of birth"
|
||||
case service.ErrTooManyAttempts:
|
||||
message = "Too many failed attempts"
|
||||
}
|
||||
|
||||
http.Error(w, message, statusCode)
|
||||
return
|
||||
}
|
||||
|
||||
// Log successful authentication
|
||||
h.logger.Info("Shortlink authentication successful",
|
||||
zap.String("token", req.ShortToken))
|
||||
|
||||
// Return response
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
Reference in New Issue
Block a user