add: login & token validation tapi belum connect ke DB

This commit is contained in:
mario
2025-05-05 11:50:36 +07:00
parent 297c9a6a01
commit 6c9ab574ce
16 changed files with 1009 additions and 41 deletions

View File

@@ -4,39 +4,84 @@ 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
logger *zap.Logger
authService *service.AuthService
}
// NewAuthHandler creates a new auth handler
func NewAuthHandler(logger *zap.Logger) *AuthHandler {
func NewAuthHandler(logger *zap.Logger, authService *service.AuthService) *AuthHandler {
return &AuthHandler{
logger: logger,
logger: logger,
authService: authService,
}
}
// Login handles user login - placeholder for future implementation
// Login handles user login
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
response := map[string]string{
"message": "Login functionality will be implemented in a future version",
// 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
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
}
// Return tokens and user info
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(response)
}
// Logout handles user logout - placeholder for future implementation
// 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) {
response := map[string]string{
"message": "Logout functionality will be implemented in a future version",
}
// 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(response)
json.NewEncoder(w).Encode(map[string]string{
"message": "Successfully logged out",
})
}

View File

@@ -0,0 +1,194 @@
package middleware
import (
"context"
"encoding/json"
"net/http"
"regexp"
"strings"
"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/api/service"
"go.uber.org/zap"
)
type contextKey string
const (
UserIDKey contextKey = "user_id"
UserRoleKey contextKey = "user_role"
UserEmailKey contextKey = "user_email"
)
// WhitelistedEndpoints contains paths that can be accessed without authentication
var WhitelistedEndpoints = []*regexp.Regexp{
// Study by UID
regexp.MustCompile(`^/dicomWeb/studies\?.*StudyInstanceUID=.+`),
// Frame endpoint
regexp.MustCompile(`^/dicomWeb/studies/[^/]+/series/[^/]+/instances/[^/]+/frames/\d+$`),
}
// Auth middleware authenticates requests using JWT tokens
func Auth(authService *service.AuthService, logger *zap.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Check if the request path is whitelisted
path := r.URL.Path
if r.URL.RawQuery != "" {
path = path + "?" + r.URL.RawQuery
}
for _, pattern := range WhitelistedEndpoints {
if pattern.MatchString(path) {
// Path is whitelisted, skip authentication
logger.Debug("Skipping authentication for whitelisted path", zap.String("path", path))
next.ServeHTTP(w, r)
return
}
}
// Get authorization header
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
logger.Warn("Missing Authorization header")
respondWithError(w, http.StatusUnauthorized, "missing authorization header")
return
}
// Extract token from Bearer token
bearerToken := strings.Split(authHeader, " ")
if len(bearerToken) != 2 || strings.ToLower(bearerToken[0]) != "bearer" {
logger.Warn("Invalid Authorization header format")
respondWithError(w, http.StatusUnauthorized, "invalid authorization format")
return
}
token := bearerToken[1]
// Validate token
claims, err := authService.ValidateToken(token)
if err != nil {
logger.Warn("Invalid or expired token", zap.Error(err))
respondWithError(w, http.StatusUnauthorized, "invalid or expired token")
return
}
// Check token type
if claims.TokenType != "access" {
logger.Warn("Invalid token type", zap.String("tokenType", claims.TokenType))
respondWithError(w, http.StatusUnauthorized, "invalid token type")
return
}
// Add user info to request context
ctx := context.WithValue(r.Context(), UserIDKey, claims.UserID)
ctx = context.WithValue(ctx, UserRoleKey, claims.Role)
ctx = context.WithValue(ctx, UserEmailKey, claims.Email)
// Log user info
logger.Info("Authenticated user", zap.String("userID", claims.UserID), zap.String("role", claims.Role), zap.String("email", claims.Email))
// Continue with the request
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
// RoleRequired middleware checks if user has the required role
func RoleRequired(roles ...string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Check if the request path is whitelisted first
path := r.URL.Path
if r.URL.RawQuery != "" {
path = path + "?" + r.URL.RawQuery
}
for _, pattern := range WhitelistedEndpoints {
if pattern.MatchString(path) {
// Path is whitelisted, skip role check
next.ServeHTTP(w, r)
return
}
}
// Get user role from context
userRole, ok := r.Context().Value(UserRoleKey).(string)
if !ok {
respondWithError(w, http.StatusUnauthorized, "user context not found")
return
}
// Check if user has one of the required roles
hasRole := false
for _, role := range roles {
if userRole == role {
hasRole = true
break
}
}
if !hasRole {
respondWithError(w, http.StatusForbidden, "insufficient permissions")
return
}
// Continue with the request
next.ServeHTTP(w, r)
})
}
}
// PatientViewRestriction middleware restricts patients to view only their studies
func PatientViewRestriction(logger *zap.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Check if the request path is whitelisted first
path := r.URL.Path
if r.URL.RawQuery != "" {
path = path + "?" + r.URL.RawQuery
}
for _, pattern := range WhitelistedEndpoints {
if pattern.MatchString(path) {
// Path is whitelisted, skip restrictions
logger.Debug("Skipping patient restrictions for whitelisted path", zap.String("path", path))
next.ServeHTTP(w, r)
return
}
}
// Get user role from context
userRole, ok := r.Context().Value(UserRoleKey).(string)
if !ok {
respondWithError(w, http.StatusUnauthorized, "user context not found")
return
}
// Only apply restrictions to patients
if userRole != "patient" {
next.ServeHTTP(w, r)
return
}
// TODO: Logic to restrict patients to only access their assigned studies
// For now, we're just letting the request through, but in a real
// implementation, you would check the study ID against the patient's
// assigned studies.
// TODO: Check if the requested study is assigned to the patient
// This would likely involve parsing the URL path to extract study ID
// and checking it against a database of patient assignments
next.ServeHTTP(w, r)
})
}
}
// Helper function to respond with JSON error
func respondWithError(w http.ResponseWriter, code int, message string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
json.NewEncoder(w).Encode(map[string]string{"error": message})
}

View File

@@ -0,0 +1,61 @@
package models
// User represents a system user
type User struct {
ID string `db:"id" json:"id"`
Email string `db:"email" json:"email"`
Password string `db:"password" json:"-"` // Never expose password in JSON
Role string `db:"role" json:"role"`
Name string `db:"name" json:"name"`
CreatedAt string `db:"created_at" json:"created_at"`
UpdatedAt string `db:"updated_at" json:"updated_at"`
}
// RefreshToken represents a refresh token stored in the database
type RefreshToken struct {
ID string `db:"id" json:"id"`
UserID string `db:"user_id" json:"user_id"`
Token string `db:"token" json:"token"`
ExpiresAt string `db:"expires_at" json:"expires_at"`
IsRevoked bool `db:"is_revoked" json:"is_revoked"`
CreatedAt string `db:"created_at" json:"created_at"`
}
// PatientDetails contains patient-specific data
type PatientDetails struct {
PatientID string `json:"patient_id"`
PatientName string `json:"patient_name"`
AccessionNumber string `json:"accession_number"`
StudyInstanceUID string `json:"study_instance_uid"`
}
// DoctorDetails contains doctor-specific data
type DoctorDetails struct {
DoctorID string `json:"doctor_id"`
DoctorName string `json:"doctor_name"`
Type string `json:"type"` // "ref_doctor" or "expertise_doctor"
}
// LoginRequest represents the login form data
type LoginRequest struct {
Email string `json:"email"`
Password string `json:"password"`
}
// LoginResponse is the response sent after successful login
type LoginResponse struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
User *User `json:"user"`
RedirectURL string `json:"redirect_url"`
}
// RefreshRequest represents the refresh token request
type RefreshRequest struct {
RefreshToken string `json:"refresh_token"`
}
// RefreshResponse is the response for a token refresh
type RefreshResponse struct {
AccessToken string `json:"access_token"`
}

View File

@@ -2,12 +2,15 @@ package api
import (
"net/http"
"time"
"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/config"
"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/api/handlers"
apiMiddleware "devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/api/middleware"
"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/api/service"
"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"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
@@ -18,29 +21,23 @@ import (
func SetupRouter(cfg *config.Config, logger *zap.Logger) http.Handler {
r := chi.NewRouter()
// Built-in Chi middleware
// Base middleware
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Recoverer)
r.Use(middleware.StripSlashes)
// Custom middleware
r.Use(apiMiddleware.Logger(logger))
// CORS middleware
// CORS configuration
r.Use(cors.Handler(cors.Options{
AllowedOrigins: cfg.AllowedOrigins,
AllowedOrigins: []string{"*"}, // In production, restrict this to your frontend domains
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
ExposedHeaders: []string{"Link"},
AllowCredentials: true,
MaxAge: 300,
MaxAge: 300, // Maximum value not ignored by any of major browsers
}))
// Setup health check
r.Get("/health", handlers.HealthCheck)
// Initialize Google auth client
// Initialize Google auth client for proxy
googleAuth, err := auth.NewGoogleClient(cfg.Google.CredentialsPath)
if err != nil {
logger.Fatal("Failed to initialize Google auth client", zap.Error(err))
@@ -49,23 +46,67 @@ func SetupRouter(cfg *config.Config, logger *zap.Logger) http.Handler {
// Initialize Healthcare API client
healthcareClient := proxy.NewClient(googleAuth, cfg.Google)
// DICOM Web routes - simplified approach
r.Route("/dicomWeb", func(r chi.Router) {
// Add audit logging middleware to DICOM routes
r.Use(apiMiddleware.AuditLog(logger))
// Initialize JWT auth service
jwtSecret := cfg.Auth.JWTSecret
if jwtSecret == "" {
jwtSecret = "vQ6PQqUyh7pBNOytClgN+Nw1XBq7F8Qo6VP3VwIqvHY=" // Default from your example, should be set in config
}
// Create single handler for all DICOM requests
dicomHandler := handlers.NewDicomHandler(healthcareClient, logger)
// Convert config values to time.Duration
accessExpiry := time.Duration(cfg.Auth.AccessTokenExpiry) * time.Minute
refreshExpiry := time.Duration(cfg.Auth.RefreshTokenExpiry) * time.Hour
// Catch all routes under /dicomWeb and forward them
r.HandleFunc("/*", dicomHandler.ForwardRequest)
// Create JWT manager with config values
jwtManager := auth.NewJWTManager(jwtSecret, accessExpiry, refreshExpiry)
authService := service.NewAuthService(jwtManager)
// Public routes that don't require authentication
r.Group(func(r chi.Router) {
// Health check
r.Get("/health", handlers.HealthCheck)
// Authentication endpoints
r.Route("/auth", func(r chi.Router) {
authHandler := handlers.NewAuthHandler(logger, authService)
r.Post("/login", authHandler.Login)
r.Post("/refresh", authHandler.RefreshToken)
r.Post("/logout", authHandler.Logout)
})
})
// Future auth routes for doctors
r.Route("/auth", func(r chi.Router) {
authHandler := handlers.NewAuthHandler(logger)
r.Post("/login", authHandler.Login)
r.Post("/logout", authHandler.Logout)
// Protected routes that require authentication
r.Group(func(r chi.Router) {
// Apply authentication middleware
r.Use(apiMiddleware.Auth(authService, logger))
// DICOM Web routes
r.Route("/dicomWeb", func(r chi.Router) {
// Add audit logging middleware to DICOM routes
r.Use(apiMiddleware.AuditLog(logger))
// Add patient view restriction for patient role
r.Use(apiMiddleware.PatientViewRestriction(logger))
// Create handler for all DICOM requests
dicomHandler := handlers.NewDicomHandler(healthcareClient, logger)
// Common routes for studies with role-specific handling
r.Route("/studies", func(r chi.Router) {
// StudyInstanceUID parameter routes - accessible by all roles
r.Get("/{studyInstanceUID}", dicomHandler.ForwardRequest) // Study details
r.Get("/{studyInstanceUID}/series", dicomHandler.ForwardRequest) // Series list for study
// Deep hierarchy routes - accessible by patients and all doctors
r.Get("/{studyInstanceUID}/series/{seriesUID}/metadata", dicomHandler.ForwardRequest)
r.Get("/{studyInstanceUID}/series/{seriesUID}/instances/{instanceUID}/frames/{frame}", dicomHandler.ForwardRequest)
// Query routes - accessible by all roles
r.Get("/", dicomHandler.ForwardRequest) // Study list with filters
})
// Expertise doctors have full access to all DICOM endpoints
r.With(apiMiddleware.RoleRequired("expertise_doctor")).HandleFunc("/*", dicomHandler.ForwardRequest)
})
})
return r

View File

@@ -0,0 +1,217 @@
package service
import (
"errors"
"time"
"golang.org/x/crypto/bcrypt"
"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/api/models"
"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/auth"
)
var (
ErrInvalidCredentials = errors.New("invalid credentials")
ErrUserNotFound = errors.New("user not found")
)
// AuthService handles authentication operations
type AuthService struct {
jwtManager *auth.JWTManager
// When you implement database connection, add a db client here
// db *sqlx.DB
}
// NewAuthService creates a new authentication service
func NewAuthService(jwtManager *auth.JWTManager) *AuthService {
return &AuthService{
jwtManager: jwtManager,
}
}
// Login authenticates a user and generates tokens
func (s *AuthService) Login(email, password string) (*models.LoginResponse, error) {
// For now, use hardcoded credentials
// TODO: In a real implementation, you would query the database
if email == "admin" && password == "admin" {
// Create a dummy user
user := &models.User{
ID: "1",
Email: "admin",
Role: "expertise_doctor",
Name: "Admin User",
CreatedAt: time.Now().Format(time.RFC3339),
UpdatedAt: time.Now().Format(time.RFC3339),
}
// Generate tokens
accessToken, err := s.jwtManager.GenerateAccessToken(user.ID, user.Email, user.Role)
if err != nil {
return nil, err
}
refreshToken, err := s.jwtManager.GenerateRefreshToken(user.ID, user.Email, user.Role)
if err != nil {
return nil, err
}
// TODO: In a real implementation, you would store the refresh token in the database
// For example:
// s.storeRefreshToken(user.ID, refreshToken)
// Determine redirect URL based on role
redirectURL := "/viewer"
if user.Role == "ref_doctor" || user.Role == "expertise_doctor" {
redirectURL = "/studylist"
}
return &models.LoginResponse{
AccessToken: accessToken,
RefreshToken: refreshToken,
User: user,
RedirectURL: redirectURL,
}, nil
} else if email == "patient" && password == "patient" {
// Create a patient user
user := &models.User{
ID: "2",
Email: "patient",
Role: "patient",
Name: "Patient User",
CreatedAt: time.Now().Format(time.RFC3339),
UpdatedAt: time.Now().Format(time.RFC3339),
}
// Generate tokens with patient-specific claims
accessToken, err := s.jwtManager.GenerateAccessToken(user.ID, user.Email, user.Role)
if err != nil {
return nil, err
}
refreshToken, err := s.jwtManager.GenerateRefreshToken(user.ID, user.Email, user.Role)
if err != nil {
return nil, err
}
return &models.LoginResponse{
AccessToken: accessToken,
RefreshToken: refreshToken,
User: user,
RedirectURL: "/viewer",
}, nil
} else if email == "doctor" && password == "doctor" {
// Create a referring doctor user
user := &models.User{
ID: "3",
Email: "doctor",
Role: "ref_doctor",
Name: "Doctor User",
CreatedAt: time.Now().Format(time.RFC3339),
UpdatedAt: time.Now().Format(time.RFC3339),
}
// Generate tokens
accessToken, err := s.jwtManager.GenerateAccessToken(user.ID, user.Email, user.Role)
if err != nil {
return nil, err
}
refreshToken, err := s.jwtManager.GenerateRefreshToken(user.ID, user.Email, user.Role)
if err != nil {
return nil, err
}
return &models.LoginResponse{
AccessToken: accessToken,
RefreshToken: refreshToken,
User: user,
RedirectURL: "/studylist",
}, nil
}
return nil, ErrInvalidCredentials
}
// RefreshToken generates a new access token using a refresh token
func (s *AuthService) RefreshToken(refreshToken string) (string, error) {
// Validate the refresh token
claims, err := s.jwtManager.ValidateToken(refreshToken)
if err != nil {
return "", err
}
// Check if token is a refresh token
if claims.TokenType != "refresh" {
return "", errors.New("invalid token type")
}
// TODO: In a real implementation, you would check if the token is in the database and not revoked
// Here we just generate a new access token
accessToken, err := s.jwtManager.GenerateAccessToken(claims.UserID, claims.Email, claims.Role)
if err != nil {
return "", err
}
return accessToken, nil
}
// ValidateToken validates a token and returns the claims
func (s *AuthService) ValidateToken(token string) (*auth.CustomClaims, error) {
return s.jwtManager.ValidateToken(token)
}
// HashPassword hashes a password using bcrypt
func HashPassword(password string) (string, error) {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(hashedPassword), nil
}
// CheckPassword compares a password with a hash
func CheckPassword(password, hash string) error {
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
}
// Below functions would be implemented when connecting to a real database
// storeRefreshToken stores a refresh token in the database
func (s *AuthService) storeRefreshToken(userID, token string) error {
// TODO: In a real implementation, this would insert a record in the database
// For example:
/*
refreshToken := &models.RefreshToken{
ID: uuid.New().String(),
UserID: userID,
Token: token,
ExpiresAt: time.Now().Add(7 * 24 * time.Hour).Format(time.RFC3339),
IsRevoked: false,
CreatedAt: time.Now().Format(time.RFC3339),
}
_, err := s.db.NamedExec(
`INSERT INTO refresh_tokens (id, user_id, token, expires_at, is_revoked, created_at)
VALUES (:id, :user_id, :token, :expires_at, :is_revoked, :created_at)`,
refreshToken,
)
return err
*/
return nil
}
// revokeRefreshToken marks a refresh token as revoked
func (s *AuthService) revokeRefreshToken(token string) error {
// TODO: In a real implementation, this would update a record in the database
// For example:
/*
_, err := s.db.Exec(
"UPDATE refresh_tokens SET is_revoked = true WHERE token = ?",
token,
)
return err
*/
return nil
}

View File

@@ -0,0 +1,130 @@
package service
import (
"database/sql"
"fmt"
"time"
"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/api/models"
_ "github.com/go-sql-driver/mysql" // Hanya menjalankan side-effectsnya saja dahulu
"github.com/google/uuid"
"github.com/jmoiron/sqlx"
)
// Repository provides an interface to the database
type Repository struct {
db *sqlx.DB
}
// NewRepository creates a new database repository
func NewRepository(dsn string) (*Repository, error) {
db, err := sqlx.Connect("mysql", dsn)
if err != nil {
return nil, fmt.Errorf("failed to connect to database: %w", err)
}
// Set connection pool settings
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(time.Hour)
return &Repository{
db: db,
}, nil
}
// GetUserByEmail retrieves a user by email
func (r *Repository) GetUserByEmail(email string) (*models.User, error) {
var user models.User
err := r.db.Get(&user, "SELECT * FROM users WHERE email = ?", email)
if err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
return nil, err
}
return &user, nil
}
// GetUserByID retrieves a user by ID
func (r *Repository) GetUserByID(id string) (*models.User, error) {
var user models.User
err := r.db.Get(&user, "SELECT * FROM users WHERE id = ?", id)
if err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
return nil, err
}
return &user, nil
}
// StoreRefreshToken saves a refresh token to the database
func (r *Repository) StoreRefreshToken(userID, token string, expiresAt time.Time) error {
refreshToken := models.RefreshToken{
ID: uuid.New().String(),
UserID: userID,
Token: token,
ExpiresAt: expiresAt.Format(time.RFC3339),
IsRevoked: false,
CreatedAt: time.Now().Format(time.RFC3339),
}
_, err := r.db.NamedExec(
`INSERT INTO refresh_tokens (id, user_id, token, expires_at, is_revoked, created_at)
VALUES (:id, :user_id, :token, :expires_at, :is_revoked, :created_at)`,
refreshToken,
)
return err
}
// GetRefreshToken retrieves a refresh token from the database
func (r *Repository) GetRefreshToken(token string) (*models.RefreshToken, error) {
var refreshToken models.RefreshToken
err := r.db.Get(&refreshToken, "SELECT * FROM refresh_tokens WHERE token = ?", token)
if err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
return nil, err
}
return &refreshToken, nil
}
// RevokeRefreshToken marks a refresh token as revoked
func (r *Repository) RevokeRefreshToken(token string) error {
_, err := r.db.Exec("UPDATE refresh_tokens SET is_revoked = true WHERE token = ?", token)
return err
}
// GetPatientDetails retrieves patient details for a user
func (r *Repository) GetPatientDetails(userID string) (*models.PatientDetails, error) {
var patientDetails models.PatientDetails
err := r.db.Get(&patientDetails, "SELECT * FROM patient_details WHERE user_id = ?", userID)
if err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
return nil, err
}
return &patientDetails, nil
}
// GetDoctorDetails retrieves doctor details for a user
func (r *Repository) GetDoctorDetails(userID string) (*models.DoctorDetails, error) {
var doctorDetails models.DoctorDetails
err := r.db.Get(&doctorDetails, "SELECT * FROM doctor_details WHERE user_id = ?", userID)
if err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
return nil, err
}
return &doctorDetails, nil
}
// Close closes the database connection
func (r *Repository) Close() error {
return r.db.Close()
}